diff options
Diffstat (limited to 'src/classes/share')
357 files changed, 191921 insertions, 0 deletions
diff --git a/src/classes/share/javax/media/j3d/Alpha.java b/src/classes/share/javax/media/j3d/Alpha.java new file mode 100644 index 0000000..d4dcdbb --- /dev/null +++ b/src/classes/share/javax/media/j3d/Alpha.java @@ -0,0 +1,1005 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The alpha NodeComponent object provides common methods for + * converting a time value into an alpha value (a value in the range 0 + * to 1). The Alpha object is effectively a function of time that + * generates alpha values in the range [0,1] when sampled: f(t) = + * [0,1]. A primary use of the Alpha object is to provide alpha + * values for Interpolator behaviors. The function f(t) and the + * characteristics of the Alpha object are determined by + * user-definable parameters: + * + * <p> + * <ul> + * + * <code>loopCount</code> -- This is the number of times to run this + * Alpha; a value of -1 specifies that the Alpha loops + * indefinitely.<p> + * + * <code>triggerTime</code> -- This is the time in milliseconds since + * the start time that this object first triggers. If (startTime + + * triggerTime >= currentTime) then the Alpha object starts running.<p> + * + * <code>phaseDelayDuration</code> -- This is an additional number of + * milliseconds to wait after triggerTime before actually starting + * this Alpha.<p> + * + * <code>mode</code> -- This can be set to INCREASING_ENABLE, + * DECREASING_ENABLE, or the Or'ed value of the two. + * INCREASING_ENABLE activates the increasing Alpha parameters listed + * below; DECREASING_ENABLE activates the decreasing Alpha parameters + * listed below.<p> + * + * </ul> Increasing Alpha parameters:<p> <ul> + * + * <code>increasingAlphaDuration</code> -- This is the period of time + * during which Alpha goes from zero to one. <p> + * + * <code>increasingAlphaRampDuration</code> -- This is the period of + * time during which the Alpha step size increases at the beginning of + * the increasingAlphaDuration and, correspondingly, decreases at the + * end of the increasingAlphaDuration. This parameter is clamped to + * half of increasingAlphaDuration. When this parameter is non-zero, + * one gets constant acceleration while it is in effect; constant + * positive acceleration at the beginning of the ramp and constant + * negative acceleration at the end of the ramp. If this parameter is + * zero, then the effective velocity of the Alpha value is constant + * and the acceleration is zero (ie, a linearly increasing alpha + * ramp).<p> + * + * <code>alphaAtOneDuration</code> -- This is the period of time that + * Alpha stays at one.<p> </ul> Decreasing Alpha parameters:<p> <ul> + * + * <code>decreasingAlphaDuration</code> -- This is the period of time + * during which Alpha goes from one to zero.<p> + * + * <code>decreasingAlphaRampDuration</code> -- This is the period of + * time during which the Alpha step size increases at the beginning of + * the decreasingAlphaDuration and, correspondingly, decreases at the + * end of the decreasingAlphaDuration. This parameter is clamped to + * half of decreasingAlphaDuration. When this parameter is non-zero, + * one gets constant acceleration while it is in effect; constant + * positive acceleration at the beginning of the ramp and constant + * negative acceleration at the end of the ramp. If this parameter is + * zero, the effective velocity of the Alpha value is constant and the + * acceleration is zero (i.e., a linearly-decreasing alpha ramp).<p> + * + * <code>alphaAtZeroDuration</code> -- This is the period of time that + * Alpha stays at zero. + * + * </ul> + * + * @see Interpolator + */ + +public class Alpha extends NodeComponent { + + // loopCount < -1 --> reserved + // loopCount == -1 --> repeat forever + // loopCount >= 0 --> repeat count + private int loopCount; + + /** + * Specifies that the increasing component of the alpha is used. + */ + public static final int INCREASING_ENABLE = 1; + + /** + * Specifies that the decreasing component of the alpha is used + */ + public static final int DECREASING_ENABLE = 2; + + /** + * This alpha's mode, specifies whether to process + * increasing and decreasing alphas. + */ + private int mode; + + private float triggerTime; + private float phaseDelay; + private float increasingAlpha; + private long increasingAlphaRamp; + private float incAlphaRampInternal; + private float alphaAtOne; + private float decreasingAlpha; + private long decreasingAlphaRamp; + private float decAlphaRampInternal; + private float alphaAtZero; + + // For pausing and resuming Alpha + private long pauseTime = 0L; + private boolean paused = false; + + // Stop time gets used only for loopCount > 0 + private float stopTime; + + //long startTime = 0L; Convert it to Seconds + // NOTE: Start Time is in milliseconds + private long startTime = MasterControl.systemStartTime; + + /** + * Constructs an Alpha object with default parameters. The default + * values are as follows: + * <ul> + * loopCount : -1<br> + * mode : INCREASING_ENABLE<br> + * startTime : system start time<br> + * triggerTime : 0<br> + * phaseDelayDuration : 0<br> + * increasingAlphaDuration : 1000<br> + * increasingAlphaRampDuration : 0<br> + * alphaAtOneDuration : 0<br> + * decreasingAlphaDuration : 0<br> + * decreasingAlphaRampDuration : 0<br> + * alphaAtZeroDuration : 0<br> + * isPaused : false<br> + * pauseTime : 0<br> + * </ul> + */ + public Alpha() { + loopCount = -1; + mode = INCREASING_ENABLE; + increasingAlpha = 1.0f; // converted to seconds internally + /* + // Java initialize them to zero by default + triggerTime = 0L; + phaseDelay = 0.0f; + increasingAlphaRamp = 0.0f; + alphaAtOne = 0.0f; + decreasingAlpha = 0.0f; + decreasingAlphaRamp = 0.0f; + alphaAtZero = 0.0f; + */ + } + + + /** + * This constructor takes all of the Alpha user-definable parameters. + * @param loopCount number of times to run this alpha; a value + * of -1 specifies that the alpha loops indefinitely + * @param mode indicates whether the increasing alpha parameters or + * the decreasing alpha parameters or both are active. This parameter + * accepts the following values, INCREASING_ENABLE or + * DECREASING_ENABLE, which may be ORed together to specify + * that both are active. + * The increasing alpha parameters are increasingAlphaDuration, + * increasingAlphaRampDuration, and alphaAtOneDuration. + * The decreasing alpha parameters are decreasingAlphaDuration, + * decreasingAlphaRampDuration, and alphaAtZeroDuration. + * @param triggerTime time in milliseconds since the start time + * that this object first triggers + * @param phaseDelayDuration number of milliseconds to wait after + * triggerTime before actually starting this alpha + * @param increasingAlphaDuration period of time during which alpha goes + * from zero to one + * @param increasingAlphaRampDuration period of time during which + * the alpha step size increases at the beginning of the + * increasingAlphaDuration and, correspondingly, decreases at the end + * of the increasingAlphaDuration. This value is clamped to half of + * increasingAlphaDuration. NOTE: a value of zero means that the alpha + * step size remains constant during the entire increasingAlphaDuration. + * @param alphaAtOneDuration period of time that alpha stays at one + * @param decreasingAlphaDuration period of time during which alpha goes + * from one to zero + * @param decreasingAlphaRampDuration period of time during which + * the alpha step size increases at the beginning of the + * decreasingAlphaDuration and, correspondingly, decreases at the end + * of the decreasingAlphaDuration. This value is clamped to half of + * decreasingAlphaDuration. NOTE: a value of zero means that the alpha + * step size remains constant during the entire decreasingAlphaDuration. + * @param alphaAtZeroDuration period of time that alpha stays at zero + */ + public Alpha(int loopCount, int mode, + long triggerTime, long phaseDelayDuration, + long increasingAlphaDuration, + long increasingAlphaRampDuration, + long alphaAtOneDuration, + long decreasingAlphaDuration, + long decreasingAlphaRampDuration, + long alphaAtZeroDuration) { + + this.loopCount = loopCount; + this.mode = mode; + this.triggerTime = (float) triggerTime * .001f; + phaseDelay = (float) phaseDelayDuration * .001f; + + increasingAlpha = (float) increasingAlphaDuration * .001f; + alphaAtOne = (float)alphaAtOneDuration * .001f; + increasingAlphaRamp = increasingAlphaRampDuration; + incAlphaRampInternal = increasingAlphaRampDuration * .001f; + if (incAlphaRampInternal > (0.5f * increasingAlpha)) { + incAlphaRampInternal = 0.5f * increasingAlpha; + } + + decreasingAlpha = (float)decreasingAlphaDuration * .001f; + alphaAtZero = (float)alphaAtZeroDuration * .001f; + decreasingAlphaRamp = decreasingAlphaRampDuration; + decAlphaRampInternal = decreasingAlphaRampDuration * .001f; + if (decAlphaRampInternal > (0.5f * decreasingAlpha)) { + decAlphaRampInternal = 0.5f * decreasingAlpha; + } + computeStopTime(); + } + + + /** + * Constructs a new Alpha object that assumes that the mode is + * INCREASING_ENABLE. + * + * @param loopCount number of times to run this alpha; a value + * of -1 specifies that the alpha loops indefinitely. + * @param triggerTime time in milliseconds since the start time + * that this object first triggers + * @param phaseDelayDuration number of milliseconds to wait after + * triggerTime before actually starting this alpha + * @param increasingAlphaDuration period of time during which alpha goes + * from zero to one + * @param increasingAlphaRampDuration period of time during which + * the alpha step size increases at the beginning of the + * increasingAlphaDuration and, correspondingly, decreases at the end + * of the increasingAlphaDuration. This value is clamped to half of + * increasingAlphaDuration. NOTE: a value of zero means that the alpha + * step size remains constant during the entire increasingAlphaDuration. + * @param alphaAtOneDuration period of time that alpha stays at one + */ + + public Alpha(int loopCount, + long triggerTime, long phaseDelayDuration, + long increasingAlphaDuration, + long increasingAlphaRampDuration, + long alphaAtOneDuration) { + this(loopCount, INCREASING_ENABLE, + triggerTime, phaseDelayDuration, + increasingAlphaDuration, increasingAlphaRampDuration, + alphaAtOneDuration, 0, 0, 0); + } + + + /** + * This constructor takes only the loopCount and increasingAlphaDuration + * as parameters and assigns the default values to all of the other + * parameters. + * @param loopCount number of times to run this alpha; a value + * of -1 specifies that the alpha loops indefinitely + * @param increasingAlphaDuration period of time during which alpha goes + * from zero to one + */ + public Alpha(int loopCount, long increasingAlphaDuration) { + // defaults + mode = INCREASING_ENABLE; + increasingAlpha = (float) increasingAlphaDuration * .001f; + this.loopCount = loopCount; + + if (loopCount >= 0) { + stopTime = loopCount*increasingAlpha; + } + } + + + /** + * Pauses this alpha object. The current system time when this + * method is called will be used in place of the actual current + * time when calculating subsequent alpha values. This has the + * effect of freezing the interpolator at the time the method is + * called. + * + * @since Java 3D 1.3 + */ + public void pause() { + pause(System.currentTimeMillis()); + } + + /** + * Pauses this alpha object as of the specified time. The specified + * time will be used in place of the actual current time when + * calculating subsequent alpha values. This has the effect of freezing + * the interpolator at the specified time. Note that specifying a + * time in the future (that is, a time greater than + * System.currentTimeMillis()) will cause the alpha to immediately + * advance to that point before pausing. Similarly, specifying a + * time in the past (that is, a time less than + * System.currentTimeMillis()) will cause the alpha to immediately + * revert to that point before pausing. + * + * @param time the time at which to pause the alpha + * + * @exception IllegalArgumentException if time <= 0 + * + * @since Java 3D 1.3 + */ + public void pause(long time) { + if (time <= 0L) { + throw new IllegalArgumentException(J3dI18N.getString("Alpha0")); + } + + paused = true; + pauseTime = time; + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Resumes this alpha object. If the alpha + * object was paused, the difference between the current + * time and the pause time will be used to adjust the startTime of + * this alpha. The equation is as follows: + * + * <ul> + * <code>startTime += System.currentTimeMillis() - pauseTime</code> + * </ul> + * + * Since the alpha object is no longer paused, this has the effect + * of resuming the interpolator as of the current time. If the + * alpha object is not paused when this method is called, then this + * method does nothing--the start time is not adjusted in this case. + * + * @since Java 3D 1.3 + */ + public void resume() { + resume(System.currentTimeMillis()); + } + + /** + * Resumes this alpha object as of the specified time. If the alpha + * object was paused, the difference between the specified + * time and the pause time will be used to adjust the startTime of + * this alpha. The equation is as follows: + * + * <ul><code>startTime += time - pauseTime</code></ul> + * + * Since the alpha object is no longer paused, this has the effect + * of resuming the interpolator as of the specified time. If the + * alpha object is not paused when this method is called, then this + * method does nothing--the start time is not adjusted in this case. + * + * @param time the time at which to resume the alpha + * + * @exception IllegalArgumentException if time <= 0 + * + * @since Java 3D 1.3 + */ + public void resume(long time) { + if (time <= 0L) { + throw new IllegalArgumentException(J3dI18N.getString("Alpha0")); + } + + if (paused) { + long newStartTime = startTime + time - pauseTime; + paused = false; + pauseTime = 0L; + setStartTime(newStartTime); + } + } + + /** + * Returns true if this alpha object is paused. + * @return true if this alpha object is paused, false otherwise + * + * @since Java 3D 1.3 + */ + public boolean isPaused() { + return paused; + } + + /** + * Returns the time at which this alpha was paused. + * @return the pause time; returns 0 if this alpha is not paused + * + * @since Java 3D 1.3 + */ + public long getPauseTime() { + return pauseTime; + } + + + /** + * This method returns a value between 0.0 and 1.0 inclusive, + * based on the current time and the time-to-alpha parameters + * established for this alpha. If this alpha object is paused, + * the value will be based on the pause time rather than the + * current time. + * This method will return the starting alpha value if the alpha + * has not yet started (that is, if the current time is less + * than startTime + triggerTime + phaseDelayDuration). This + * method will return the ending alpha value if the alpha has + * finished (that is, if the loop count has expired). + * + * @return a value between 0.0 and 1.0 based on the current time + */ + public float value() { + long currentTime = paused ? pauseTime : System.currentTimeMillis(); + return this.value(currentTime); + } + + /** + * This method returns a value between 0.0 and 1.0 inclusive, + * based on the specified time and the time-to-alpha parameters + * established for this alpha. + * This method will return the starting alpha value if the alpha + * has not yet started (that is, if the specified time is less + * than startTime + triggerTime + phaseDelayDuration). This + * method will return the ending alpha value if the alpha has + * finished (that is, if the loop count has expired). + * + * @param atTime The time for which we wish to compute alpha + * @return a value between 0.0 and 1.0 based on the specified time + */ + public float value(long atTime) { + float interpolatorTime + = (float)(atTime - startTime) * .001f; // startTime is in millisec + float alpha, a1, a2, dt, alphaRampDuration; + + // System.out.println("alpha mode: " + mode); + + // If non-looping and before start + // if ((loopCount != -1) && + // interpolatorTime <= ( triggerTime + phaseDelay)) { + // + // if (( mode & INCREASING_ENABLE ) == 0 && + // ( mode & DECREASING_ENABLE) != 0) + // alpha = 1.0f; + // else + // alpha = 0.0f; + // return alpha; + // } + + + // Case of {constantly} moving forward, snap back, forward again + if (( mode & INCREASING_ENABLE ) != 0 && + ( mode & DECREASING_ENABLE) == 0) { + + if(interpolatorTime <= (triggerTime + phaseDelay)) + return 0.0f; + + if((loopCount != -1) && (interpolatorTime >= stopTime)) + return 1.0f; + + // Constant velocity case + if (incAlphaRampInternal == 0.0f) { + + alpha = mfmod((interpolatorTime - triggerTime - phaseDelay) + + 6.0f*( increasingAlpha + alphaAtOne), + (increasingAlpha + alphaAtOne))/ increasingAlpha; + + if ( alpha > 1.0f) alpha = 1.0f; + return alpha; + } + + // Ramped velocity case + alphaRampDuration = incAlphaRampInternal; + + dt = mfmod((interpolatorTime - triggerTime - phaseDelay) + + 6.0f*( increasingAlpha + alphaAtOne), + ( increasingAlpha + alphaAtOne)); + if (dt >= increasingAlpha) { alpha = 1.0f; return alpha; } + + // Original equation kept to help understand + // computation logic - simplification saves + // a multiply and an add + // a1 = 1.0f/(alphaRampDuration*alphaRampDuration + + // ( increasingAlpha - 2*alphaRampDuration)* + // alphaRampDuration); + + a1 = 1.0f/(increasingAlpha * alphaRampDuration - + alphaRampDuration * alphaRampDuration); + + if (dt < alphaRampDuration) { + alpha = 0.5f*a1*dt*dt; + } else if (dt < increasingAlpha - alphaRampDuration) { + alpha = 0.5f*a1*alphaRampDuration* + alphaRampDuration + + (dt - alphaRampDuration)*a1* + alphaRampDuration; + } else { + alpha = a1*alphaRampDuration*alphaRampDuration + + ( increasingAlpha - 2.0f*alphaRampDuration)*a1* + alphaRampDuration - + 0.5f*a1*( increasingAlpha - dt)* + ( increasingAlpha - dt); + } + return alpha; + + } else + + + // Case of {constantly} moving backward, snap forward, backward + // again + if (( mode & INCREASING_ENABLE ) == 0 && + ( mode & DECREASING_ENABLE) != 0) { + + // If non-looping and past end + // if ((loopCount != -1) + // && (interpolatorTime + // >= (triggerTime + phaseDelay + decreasingAlpha))) { + // alpha = 0.0f; + // return alpha; + // } + + if(interpolatorTime <= (triggerTime + phaseDelay)) + return 1.0f; + + if((loopCount != -1) && (interpolatorTime >= stopTime) ) + return 0.0f; + + + + // Constant velocity case + if (decAlphaRampInternal == 0.0f) { + alpha = mfmod((interpolatorTime - triggerTime - + phaseDelay) + + 6.0f*( decreasingAlpha + alphaAtZero), + (decreasingAlpha + alphaAtZero))/ decreasingAlpha; + if ( alpha > 1.0f) { alpha = 0.0f; return alpha; } + alpha = 1.0f - alpha; + return alpha; + } + + // Ramped velocity case + alphaRampDuration = decAlphaRampInternal; + + dt = mfmod((interpolatorTime - triggerTime - phaseDelay) + + 6.0f*( decreasingAlpha + alphaAtZero), + ( decreasingAlpha + alphaAtZero)); + if (dt >= decreasingAlpha) { alpha = 0.0f; return alpha; } + + // Original equation kept to help understand + // computation logic - simplification saves + // a multiply and an add + // a1 = 1.0f/(alphaRampDuration*alphaRampDuration + + // ( decreasingAlpha - 2*alphaRampDuration)* + // alphaRampDuration); + + a1 = 1.0f/(decreasingAlpha * alphaRampDuration - + alphaRampDuration * alphaRampDuration); + + if (dt < alphaRampDuration) { + alpha = 0.5f*a1*dt*dt; + } else if (dt < decreasingAlpha - alphaRampDuration) { + alpha = 0.5f*a1*alphaRampDuration* + alphaRampDuration + + (dt - alphaRampDuration)*a1* + alphaRampDuration; + } else { + alpha = a1*alphaRampDuration*alphaRampDuration + + ( decreasingAlpha - 2.0f*alphaRampDuration)*a1* + alphaRampDuration - + 0.5f*a1*( decreasingAlpha - dt)* + ( decreasingAlpha - dt); + } + alpha = 1.0f - alpha; + return alpha; + + } else + + + // Case of {osscilating} increasing and decreasing alpha + if (( mode & INCREASING_ENABLE) != 0 && + ( mode & DECREASING_ENABLE) != 0) { + + // If non-looping and past end + // if ((loopCount != -1) && + // (interpolatorTime >= + // (triggerTime + phaseDelay + increasingAlpha + + // alphaAtOne + decreasingAlpha))) { + // alpha = 0.0f; + // return alpha; + // } + + + // If non-looping and past end, we always end up at zero since + // decreasing alpha has been requested. + if(interpolatorTime <= (triggerTime + phaseDelay)) + return 0.0f; + + if( (loopCount != -1) && (interpolatorTime >= stopTime)) + return 0.0f; + + // Constant velocity case + if (incAlphaRampInternal == 0.0f + && decAlphaRampInternal == 0.0f) { + dt = mfmod(interpolatorTime - triggerTime - phaseDelay + + 6.0f*(increasingAlpha + alphaAtOne + + decreasingAlpha + alphaAtZero), + increasingAlpha + alphaAtOne + + decreasingAlpha + alphaAtZero); + alpha = dt / increasingAlpha; + if ( alpha < 1.0f) return alpha; + // sub all increasing alpha time + dt -= increasingAlpha; + if (dt < alphaAtOne) { alpha = 1.0f; return alpha; } + // sub out alpha @ 1 time + dt -= alphaAtOne; + alpha = dt/ decreasingAlpha; + if ( alpha < 1.0f) alpha = 1.0f - alpha; + else alpha = 0.0f; + return alpha; + } + + // Ramped velocity case + alphaRampDuration = incAlphaRampInternal; + + // work around for bug 4308308 + if (alphaRampDuration == 0.0f) + alphaRampDuration = .00001f; + + dt = mfmod(interpolatorTime - triggerTime - phaseDelay + + 6.0f*( increasingAlpha + alphaAtOne + + decreasingAlpha + alphaAtZero), + increasingAlpha + alphaAtOne + + decreasingAlpha + alphaAtZero); + if (dt <= increasingAlpha) { + + // Original equation kept to help understand + // computation logic - simplification saves + // a multiply and an add + // a1 = 1.0f/(alphaRampDuration*alphaRampDuration + + // ( increasingAlpha - 2*alphaRampDuration)* + // alphaRampDuration); + + a1 = 1.0f/(increasingAlpha * alphaRampDuration - + alphaRampDuration * alphaRampDuration); + + if (dt < alphaRampDuration) { + alpha = 0.5f*a1*dt*dt; + } else if (dt < increasingAlpha - alphaRampDuration) { + alpha = 0.5f*a1*alphaRampDuration* + alphaRampDuration + + (dt - alphaRampDuration)*a1* + alphaRampDuration; + } else { + alpha = a1*alphaRampDuration*alphaRampDuration+ + ( increasingAlpha - 2.0f*alphaRampDuration)*a1* + alphaRampDuration - + 0.5f*a1*( increasingAlpha - dt)* + ( increasingAlpha - dt); + } + return alpha; + } + else if (dt <= increasingAlpha + alphaAtOne) { + alpha = 1.0f; return alpha; + } + else if (dt >= increasingAlpha + alphaAtOne + decreasingAlpha) { + alpha = 0.0f; return alpha; + } + else { + dt -= increasingAlpha + alphaAtOne; + + alphaRampDuration = decAlphaRampInternal; + + // work around for bug 4308308 + if (alphaRampDuration == 0.0f) + alphaRampDuration = .00001f; + + // Original equation kept to help understand + // computation logic - simplification saves + // a multiply and an add + // a1 = 1.0f/(alphaRampDuration*alphaRampDuration + + // ( decreasingAlpha - 2*alphaRampDuration)* + // alphaRampDuration); + + a1 = 1.0f/(decreasingAlpha * alphaRampDuration - + alphaRampDuration * alphaRampDuration); + + if (dt < alphaRampDuration) { + alpha = 0.5f*a1*dt*dt; + } else if (dt < decreasingAlpha - alphaRampDuration) { + alpha = 0.5f*a1*alphaRampDuration* + alphaRampDuration + + (dt - alphaRampDuration)*a1* + alphaRampDuration; + } else { + alpha = + a1*alphaRampDuration*alphaRampDuration + + (decreasingAlpha - 2.0f*alphaRampDuration)*a1* + alphaRampDuration - + 0.5f*a1*( decreasingAlpha - dt)* + (decreasingAlpha - dt); + } + alpha = 1.0f - alpha; + return alpha; + } + + } + return 0.0f; + } + + float mfmod(float a, float b) { + float fm, ta = (a), tb = (b); + int fmint; + if (tb < 0.0f) tb = -tb; + if (ta < 0.0f) ta = -ta; + + fmint =(int)( ta/tb); + fm = ta - (float)fmint * tb; + + if ((a) < 0.0f) return ((b) - fm); + else return fm; + } + + /** + * Retrieves this alpha's startTime, the base + * for all relative time specifications; the default value + * for startTime is the system start time. + * @return this alpha's startTime. + */ + public long getStartTime() { + return this.startTime; + } + + /** + * Sets this alpha's startTime to that specified in the argument; + * startTime sets the base (or zero) for all relative time + * computations; the default value for startTime is the system + * start time. + * @param startTime the new startTime value + */ + public void setStartTime(long startTime) { + this.startTime = startTime; + // This is used for passive wakeupOnElapsedFrame in + // Interpolator to restart behavior after alpha.finished() + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's loopCount. + * @return this alpha's loopCount. + */ + public int getLoopCount() { + return this.loopCount; + } + + /** + * Set this alpha's loopCount to that specified in the argument. + * @param loopCount the new loopCount value + */ + public void setLoopCount(int loopCount) { + this.loopCount = loopCount; + computeStopTime(); + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's mode. + * @return this alpha's mode: any combination of + * INCREASING_ENABLE and DECREASING_ENABLE + */ + public int getMode() { + return this.mode; + } + + /** + * Set this alpha's mode to that specified in the argument. + * @param mode indicates whether the increasing alpha parameters or + * the decreasing alpha parameters or both are active. This parameter + * accepts the following values, INCREASING_ENABLE or + * DECREASING_ENABLE, which may be ORed together to specify + * that both are active. + * The increasing alpha parameters are increasingAlphaDuration, + * increasingAlphaRampDuration, and alphaAtOneDuration. + * The decreasing alpha parameters are decreasingAlphaDuration, + * decreasingAlphaRampDuration, and alphaAtZeroDuration. + */ + public void setMode(int mode) { + this.mode = mode; + computeStopTime(); + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's triggerTime. + * @return this alpha's triggerTime. + */ + public long getTriggerTime() { + return (long) (this.triggerTime * 1000f); + } + + /** + * Set this alpha's triggerTime to that specified in the argument. + * @param triggerTime the new triggerTime + */ + public void setTriggerTime(long triggerTime) { + this.triggerTime = (float) triggerTime * .001f; + computeStopTime(); + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's phaseDelayDuration. + * @return this alpha's phaseDelayDuration. + */ + public long getPhaseDelayDuration() { + return (long)(this.phaseDelay * 1000f); + } + + /** + * Set this alpha's phaseDelayDuration to that specified in + * the argument. + * @param phaseDelayDuration the new phaseDelayDuration + */ + public void setPhaseDelayDuration(long phaseDelayDuration) { + this.phaseDelay = (float) phaseDelayDuration * .001f; + computeStopTime(); + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's increasingAlphaDuration. + * @return this alpha's increasingAlphaDuration. + */ + public long getIncreasingAlphaDuration() { + return (long)(this.increasingAlpha * 1000f); + } + + /** + * Set this alpha's increasingAlphaDuration to that specified in + * the argument. + * @param increasingAlphaDuration the new increasingAlphaDuration + */ + public void setIncreasingAlphaDuration(long increasingAlphaDuration) { + this.increasingAlpha = (float) increasingAlphaDuration * .001f; + computeStopTime(); + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's increasingAlphaRampDuration. + * @return this alpha's increasingAlphaRampDuration. + */ + public long getIncreasingAlphaRampDuration() { + return increasingAlphaRamp; + } + + /** + * Set this alpha's increasingAlphaRampDuration to that specified + * in the argument. + * @param increasingAlphaRampDuration the new increasingAlphaRampDuration + */ + public void setIncreasingAlphaRampDuration(long increasingAlphaRampDuration) { + increasingAlphaRamp = increasingAlphaRampDuration; + incAlphaRampInternal = (float) increasingAlphaRampDuration * .001f; + if (incAlphaRampInternal > (0.5f * increasingAlpha)) { + incAlphaRampInternal = 0.5f * increasingAlpha; + } + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's alphaAtOneDuration. + * @return this alpha's alphaAtOneDuration. + */ + public long getAlphaAtOneDuration() { + return (long)(this.alphaAtOne * 1000f); + } + + /** + * Set this alpha object's alphaAtOneDuration to the specified + * value. + * @param alphaAtOneDuration the new alphaAtOneDuration + */ + public void setAlphaAtOneDuration(long alphaAtOneDuration) { + this.alphaAtOne = (float) alphaAtOneDuration * .001f; + computeStopTime(); + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's decreasingAlphaDuration. + * @return this alpha's decreasingAlphaDuration. + */ + public long getDecreasingAlphaDuration() { + return (long)(this.decreasingAlpha * 1000f); + } + + /** + * Set this alpha's decreasingAlphaDuration to that specified in + * the argument. + * @param decreasingAlphaDuration the new decreasingAlphaDuration + */ + public void setDecreasingAlphaDuration(long decreasingAlphaDuration) { + this.decreasingAlpha = (float) decreasingAlphaDuration * .001f; + computeStopTime(); + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's decreasingAlphaRampDuration. + * @return this alpha's decreasingAlphaRampDuration. + */ + public long getDecreasingAlphaRampDuration() { + return decreasingAlphaRamp; + } + + /** + * Set this alpha's decreasingAlphaRampDuration to that specified + * in the argument. + * @param decreasingAlphaRampDuration the new decreasingAlphaRampDuration + */ + public void setDecreasingAlphaRampDuration(long decreasingAlphaRampDuration) { + decreasingAlphaRamp = decreasingAlphaRampDuration; + decAlphaRampInternal = (float) decreasingAlphaRampDuration * .001f; + if (decAlphaRampInternal > (0.5f * decreasingAlpha)) { + decAlphaRampInternal = 0.5f * decreasingAlpha; + } + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Retrieves this alpha's alphaAtZeroDuration. + * @return this alpha's alphaAtZeroDuration. + */ + public long getAlphaAtZeroDuration() { + return (long)(this.alphaAtZero * 1000f); + } + + /** + * Set this alpha object's alphaAtZeroDuration to the specified + * value. + * @param alphaAtZeroDuration the new alphaAtZeroDuration + */ + public void setAlphaAtZeroDuration(long alphaAtZeroDuration) { + this.alphaAtZero = (float) alphaAtZeroDuration * .001f; + computeStopTime(); + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + /** + * Query to test if this alpha object is past its activity window, + * that is, if it has finished looping. + * @return true if no longer looping, false otherwise + */ + public boolean finished() { + long currentTime = paused ? pauseTime : System.currentTimeMillis(); + return ((loopCount != -1) && + ((float)(currentTime - startTime) * .001f > stopTime)); + } + + final private void computeStopTime() { + if (loopCount >= 0) { + float sum = 0; + if (( mode & INCREASING_ENABLE ) != 0) { + sum = increasingAlpha+alphaAtOne; + } + if ((mode & DECREASING_ENABLE) != 0) { + sum += decreasingAlpha+alphaAtZero; + } + stopTime = this.triggerTime + phaseDelay + loopCount*sum; + } else { + stopTime = 0; + } + } + + /** + * This internal method returns a clone of the Alpha + * + * @return a duplicate of this Alpha + */ + Alpha cloneAlpha() { + Alpha a = new Alpha(); + a.setStartTime(getStartTime()); + a.setLoopCount(getLoopCount()); + a.setMode(getMode()); + a.setTriggerTime(getTriggerTime()); + a.setPhaseDelayDuration(getPhaseDelayDuration()); + a.setIncreasingAlphaDuration(getIncreasingAlphaDuration()); + a.setIncreasingAlphaRampDuration(getIncreasingAlphaRampDuration()); + a.setAlphaAtOneDuration(getAlphaAtOneDuration()); + a.setDecreasingAlphaDuration(getDecreasingAlphaDuration()); + a.setDecreasingAlphaRampDuration(getDecreasingAlphaRampDuration()); + a.setAlphaAtZeroDuration(getAlphaAtZeroDuration()); + return a; + } + +} diff --git a/src/classes/share/javax/media/j3d/AlternateAppearance.java b/src/classes/share/javax/media/j3d/AlternateAppearance.java new file mode 100644 index 0000000..d6b38ad --- /dev/null +++ b/src/classes/share/javax/media/j3d/AlternateAppearance.java @@ -0,0 +1,594 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; + + +/** + * The AlternateAppearance leaf node is used for overriding the + * Appearance component of selected nodes. It defines an Appearance + * component object and a region of influence in which this + * AlternateAppearance node is active. An AlternateAppearance node + * also contains a list of Group nodes that specifies the hierarchical + * scope of this AlternateAppearance. If the scope list is empty, + * then the AlternateAppearance node has universe scope: all nodes + * within the region of influence are affected by this + * AlternateAppearance node. If the scope list is non-empty, then + * only those Leaf nodes under the Group nodes in the scope list are + * affected by this AlternateAppearance node (subject to the + * influencing bounds). + * + * <p> + * An AlternateAppearance node affects Shape3D and Morph nodes by + * overriding their appearance component with the appearance + * component in this AlternateAppearance node. Only those Shape3D and + * Morph nodes that explicitly allow their appearance to be + * overridden are affected. The AlternateAppearance node has no + * effect on Shape3D and Morph nodes that do not allow their + * appearance to be overridden. + * + * <p> + * If the regions of influence of multiple AlternateAppearance nodes + * overlap, the Java 3D system will choose a single alternate + * appearance for those objects that lie in the intersection. This is + * done in an implementation-dependent manner, but in general, the + * AlternateAppearance node that is "closest" to the object is chosen. + * + * @since Java 3D 1.2 + */ + +public class AlternateAppearance extends Leaf { + /** + * Specifies that this AlternateAppearance node allows read access to its + * influencing bounds and bounds leaf information. + */ + public static final int ALLOW_INFLUENCING_BOUNDS_READ = + CapabilityBits.ALTERNATE_APPEARANCE_ALLOW_INFLUENCING_BOUNDS_READ; + + /** + * Specifies that this AlternateAppearance node allows write access to its + * influencing bounds and bounds leaf information. + */ + public static final int ALLOW_INFLUENCING_BOUNDS_WRITE = + CapabilityBits.ALTERNATE_APPEARANCE_ALLOW_INFLUENCING_BOUNDS_WRITE; + + /** + * Specifies that this AlternateAppearance node allows read access to + * its appearance information. + */ + public static final int ALLOW_APPEARANCE_READ = + CapabilityBits.ALTERNATE_APPEARANCE_ALLOW_APPEARANCE_READ; + + /** + * Specifies that this AlternateAppearance node allows write access to + * its appearance information. + * information. + */ + public static final int ALLOW_APPEARANCE_WRITE = + CapabilityBits.ALTERNATE_APPEARANCE_ALLOW_APPEARANCE_WRITE; + + /** + * Specifies that this AlternateAppearance node allows read access + * to its scope information at runtime. + */ + public static final int ALLOW_SCOPE_READ = + CapabilityBits.ALTERNATE_APPEARANCE_ALLOW_SCOPE_READ; + + /** + * Specifies that this AlternateAppearance node allows write access + * to its scope information at runtime. + */ + public static final int ALLOW_SCOPE_WRITE = + CapabilityBits.ALTERNATE_APPEARANCE_ALLOW_SCOPE_WRITE; + + + /** + * Constructs an AlternateAppearance node with default + * parameters. The default values are as follows: + * + * <ul> + * appearance : null<br> + * scope : empty (universe scope)<br> + * influencing bounds : null<br> + * influencing bounding leaf : null<br> + * </ul> + */ + public AlternateAppearance() { + // Just use the defaults + } + + + /** + * Creates the retained mode AlternateAppearanceRetained object that this + * Alternate Appearance component object will point to. + */ + void createRetained() { + this.retained = new AlternateAppearanceRetained(); + this.retained.setSource(this); + } + + + /** + * Constructs an AlternateAppearance node with the specified appearance. + * @param appearance the appearance that is used for those nodes affected + * by this AlternateAppearance node. + */ + public AlternateAppearance(Appearance appearance) { + ((AlternateAppearanceRetained)retained).initAppearance(appearance); + } + + + /** + * Sets the appearance of this AlternateAppearance node. + * This appearance overrides the appearance in those Shape3D and + * Morph nodes affected by this AlternateAppearance node. + * @param appearance the new appearance. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAppearance(Appearance appearance) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPEARANCE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance0")); + + if (isLive()) + ((AlternateAppearanceRetained)this.retained).setAppearance(appearance); + else + ((AlternateAppearanceRetained)this.retained).initAppearance(appearance); + } + + + /** + * Retrieves the appearance from this AlternateAppearance node. + * @return the current appearance. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Appearance getAppearance() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPEARANCE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance2")); + + return ((AlternateAppearanceRetained)this.retained).getAppearance(); + + } + + /** + * Sets the AlternateAppearance's influencing region to the specified + * bounds. + * This is used when the influencing bounding leaf is set to null. + * @param region the bounds that contains the AlternateAppearance's + * new influencing region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInfluencingBounds(Bounds region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance3")); + + + if (isLive()) + ((AlternateAppearanceRetained)this.retained).setInfluencingBounds(region); + else + ((AlternateAppearanceRetained)this.retained).initInfluencingBounds(region); + } + + /** + * Retrieves the AlternateAppearance node's influencing bounds. + * @return this AlternateAppearance's influencing bounds information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getInfluencingBounds() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance4")); + + + return ((AlternateAppearanceRetained)this.retained).getInfluencingBounds(); + } + + + /** + * Sets the AlternateAppearance's influencing region to the specified + * bounding leaf. + * When set to a value other than null, this overrides the influencing + * bounds object. + * @param region the bounding leaf node used to specify the + * AlternateAppearance node's new influencing region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInfluencingBoundingLeaf(BoundingLeaf region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance3")); + + + if (isLive()) + ((AlternateAppearanceRetained)this.retained).setInfluencingBoundingLeaf(region); + else + ((AlternateAppearanceRetained)this.retained).initInfluencingBoundingLeaf(region); + } + + + /** + * Retrieves the AlternateAppearance node's influencing bounding leaf. + * @return this AlternateAppearance's influencing bounding leaf information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BoundingLeaf getInfluencingBoundingLeaf() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance4")); + + + return ((AlternateAppearanceRetained)this.retained).getInfluencingBoundingLeaf(); + } + + + /** + * Replaces the node at the specified index in this + * AlternateAppearance node's + * list of scopes with the specified Group node. + * By default, AlternateAppearance nodes are scoped only by their + * influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be stored at the specified index. + * @param index the index of the Group node to be replaced. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void setScope(Group scope, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance7")); + + + + if (isLive()) + ((AlternateAppearanceRetained)this.retained).setScope(scope, index); + else + ((AlternateAppearanceRetained)this.retained).initScope(scope, index); + } + + + /** + * Retrieves the Group node at the specified index from + * this AlternateAppearance node's list of scopes. + * @param index the index of the Group node to be returned. + * @return the Group node at the specified index. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Group getScope(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance8")); + + + return ((AlternateAppearanceRetained)this.retained).getScope(index); + } + + + /** + * Inserts the specified Group node into this AlternateAppearance node's + * list of scopes at the specified index. + * By default, AlternateAppearance nodes are scoped only by their + * influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be inserted at the specified index. + * @param index the index at which the Group node is inserted. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void insertScope(Group scope, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance9")); + + + if (isLive()) + ((AlternateAppearanceRetained)this.retained).insertScope(scope, index); + else + ((AlternateAppearanceRetained)this.retained).initInsertScope(scope, index); + } + + + /** + * Removes the node at the specified index from this AlternateAppearance + * node's + * list of scopes. If this operation causes the list of scopes to + * become empty, then this AlternateAppearance will have universe scope: + * all nodes + * within the region of influence will be affected by this + * AlternateAppearance node. + * @param index the index of the Group node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the group node at the + * specified index is part of a compiled scene graph + */ + public void removeScope(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance10")); + + + if (isLive()) + ((AlternateAppearanceRetained)this.retained).removeScope(index); + else + ((AlternateAppearanceRetained)this.retained).initRemoveScope(index); + } + + + /** + * Returns an enumeration of this AlternateAppearance node's list + * of scopes. + * @return an Enumeration object containing all nodes in this + * AlternateAppearance node's list of scopes. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Enumeration getAllScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance11")); + + + return (Enumeration) ((AlternateAppearanceRetained)this.retained).getAllScopes(); + } + + + /** + * Appends the specified Group node to this AlternateAppearance node's + * list of scopes. + * By default, AlternateAppearance nodes are scoped only by their + * influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be appended. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void addScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance12")); + + + if (isLive()) + ((AlternateAppearanceRetained)this.retained).addScope(scope); + else + ((AlternateAppearanceRetained)this.retained).initAddScope(scope); + } + + + /** + * Returns the number of nodes in this AlternateAppearance node's list + * of scopes. + * If this number is 0, then the list of scopes is empty and this + * AlternateAppearance node has universe scope: all nodes within the + * region of + * influence are affected by this AlternateAppearance node. + * @return the number of nodes in this AlternateAppearance node's list + * of scopes. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int numScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance11")); + + + return ((AlternateAppearanceRetained)this.retained).numScopes(); + } + + + /** + * Retrieves the index of the specified Group node in this + * AlternateAppearance node's list of scopes. + * + * @param scope the Group node to be looked up. + * @return the index of the specified Group node; + * returns -1 if the object is not in the list. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int indexOfScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance8")); + + return ((AlternateAppearanceRetained)this.retained).indexOfScope(scope); + } + + + /** + * Removes the specified Group node from this AlternateAppearance + * node's list of scopes. If the specified object is not in the + * list, the list is not modified. If this operation causes the + * list of scopes to become empty, then this AlternateAppearance + * will have universe scope: all nodes within the region of + * influence will be affected by this AlternateAppearance node. + * + * @param scope the Group node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance10")); + + if (isLive()) + ((AlternateAppearanceRetained)this.retained).removeScope(scope); + else + ((AlternateAppearanceRetained)this.retained).initRemoveScope(scope); + } + + + /** + * Removes all Group nodes from this AlternateAppearance node's + * list of scopes. The AlternateAppearance node will then have + * universe scope: all nodes within the region of influence will + * be affected by this AlternateAppearance node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if any group node in this + * node's list of scopes is part of a compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeAllScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AlternateAppearance10")); + if (isLive()) + ((AlternateAppearanceRetained)this.retained).removeAllScopes(); + else + ((AlternateAppearanceRetained)this.retained).initRemoveAllScopes(); + } + + + /** + * Copies all AlternateAppearance information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + AlternateAppearanceRetained attr = (AlternateAppearanceRetained) + originalNode.retained; + AlternateAppearanceRetained rt = (AlternateAppearanceRetained) retained; + + rt.initAppearance((Appearance) getNodeComponent( + attr.getAppearance(), + forceDuplicate, + originalNode.nodeHashtable)); + + rt.initInfluencingBounds(attr.getInfluencingBounds()); + + Enumeration elm = attr.getAllScopes(); + while (elm.hasMoreElements()) { + // this reference will set correctly in updateNodeReferences() callback + rt.initAddScope((Group) elm.nextElement()); + } + + // correct value will set in updateNodeReferences + rt.initInfluencingBoundingLeaf(attr.getInfluencingBoundingLeaf()); + + } + + /** + * Callback used to allow a node to check if any nodes referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any node references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding Node in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * node is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + + AlternateAppearanceRetained rt = (AlternateAppearanceRetained) + retained; + + BoundingLeaf bl = rt.getInfluencingBoundingLeaf(); + + if (bl != null) { + Object o = referenceTable.getNewObjectReference(bl); + rt.initInfluencingBoundingLeaf((BoundingLeaf) o); + } + + int num = rt.numScopes(); + for (int i=0; i < num; i++) { + rt.initScope((Group) referenceTable. + getNewObjectReference(rt.getScope(i)), i); + } + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + AlternateAppearance app = new AlternateAppearance(); + app.duplicateNode(this, forceDuplicate); + return app; + } +} + diff --git a/src/classes/share/javax/media/j3d/AlternateAppearanceRetained.java b/src/classes/share/javax/media/j3d/AlternateAppearanceRetained.java new file mode 100644 index 0000000..692c4a2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/AlternateAppearanceRetained.java @@ -0,0 +1,862 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +import javax.vecmath.*; +import java.util.Enumeration; +import java.util.Vector; +import java.util.ArrayList; + + +class AlternateAppearanceRetained extends LeafRetained { + + + // Statics used when something in the alternate app changes + static final int APPEARANCE_CHANGED = 0x0001; + static final int SCOPE_CHANGED = 0x0002; + static final int BOUNDS_CHANGED = 0x0004; + static final int BOUNDINGLEAF_CHANGED = 0x0008; + static final int INIT_MIRROR = 0x0010; // setLive + static final int CLEAR_MIRROR = 0x0020; // clearLive + + + /** + * The Boundary object defining the lights's region of influence. + */ + Bounds regionOfInfluence = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * Vector of GroupRetained nodes that scopes this alternate app . + */ + Vector scopes = new Vector(); + + // This is true when this alternate app is referenced in an immediate mode context + boolean inImmCtx = false; + + // Target threads to be notified when light changes + static final int targetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + + // Boolean to indicate if this object is scoped (only used for mirror objects + boolean isScoped = false; + + // The object that contains the dynamic HashKey - a string type object + // Used in scoping + HashKey tempKey = new HashKey(250); + + /** + * The transformed value of the applicationRegion. + */ + Bounds region = null; + + /** + * mirror Alternate appearance + */ + AlternateAppearanceRetained mirrorAltApp = null; + + /** + * Appearance for this object + */ + AppearanceRetained appearance; + + /** + * A reference to the scene graph alternateApp + */ + AlternateAppearanceRetained sgAltApp = null; + + /** + * Is true, if the mirror altapp is viewScoped + */ + boolean isViewScoped = false; + + AlternateAppearanceRetained() { + this.nodeType = NodeRetained.ALTERNATEAPPEARANCE; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + /** + * Initializes the appearance + */ + void initAppearance(Appearance app) { + if (app != null) + appearance = (AppearanceRetained) app.retained; + else + appearance = null; + } + + + /** + * sets the appearance and send a message + */ + void setAppearance(Appearance app) { + if (appearance != null) + synchronized(appearance.liveStateLock) { + appearance.clearLive(refCount); + } + initAppearance(app); + if (appearance != null) { + synchronized(appearance.liveStateLock) { + appearance.setLive(inBackgroundGroup, refCount); + } + } + // There is no need to clone the appearance's mirror + sendMessage(APPEARANCE_CHANGED, + (appearance != null ? appearance.mirror: null)); + } + + + + Appearance getAppearance() { + return (appearance == null ? null: (Appearance) appearance.source); + } + + + /** + * Set the alternate's region of influence. + */ + void initInfluencingBounds(Bounds region) { + if (region != null) { + this.regionOfInfluence = (Bounds) region.clone(); + } else { + this.regionOfInfluence = null; + } + } + + /** + * Set the alternate's region of influence and send message + */ + void setInfluencingBounds(Bounds region) { + initInfluencingBounds(region); + sendMessage(BOUNDS_CHANGED, + (region != null ? region.clone() : null)); + } + + /** + * Get the alternate's region of Influence. + */ + Bounds getInfluencingBounds() { + return (regionOfInfluence != null ? + (Bounds) regionOfInfluence.clone() : null); + } + + /** + * Set the alternate's region of influence to the specified Leaf node. + */ + void initInfluencingBoundingLeaf(BoundingLeaf region) { + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + } else { + boundingLeaf = null; + } + } + + /** + * Set the alternate's region of influence to the specified Leaf node. + */ + void setInfluencingBoundingLeaf(BoundingLeaf region) { + if (boundingLeaf != null) + boundingLeaf.mirrorBoundingLeaf.removeUser(mirrorAltApp); + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + boundingLeaf.mirrorBoundingLeaf.addUser(mirrorAltApp); + } else { + boundingLeaf = null; + } + sendMessage(BOUNDINGLEAF_CHANGED, + (boundingLeaf != null ? + boundingLeaf.mirrorBoundingLeaf : null)); + } + + /** + * Get the alternate's region of influence. + */ + BoundingLeaf getInfluencingBoundingLeaf() { + return (boundingLeaf != null ? + (BoundingLeaf)boundingLeaf.source : null); + } + + + + /** + * Replaces the specified scope with the scope provided. + * @param scope the new scope + * @param index which scope to replace + */ + void initScope(Group scope, int index) { + scopes.setElementAt((GroupRetained)(scope.retained), index); + + } + + /** + * Replaces the specified scope with the scope provided. + * @param scope the new scope + * @param index which scope to replace + */ + void setScope(Group scope, int index) { + + ArrayList removeScopeList = new ArrayList(); + GroupRetained group; + ArrayList addScopeList = new ArrayList(); + Object[] scopeInfo = new Object[3]; + + group = (GroupRetained) scopes.get(index); + tempKey.reset(); + group.removeAllNodesForScopedAltApp(mirrorAltApp, removeScopeList, tempKey); + + group = (GroupRetained)scope.retained; + initScope(scope, index); + tempKey.reset(); + + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + group.addAllNodesForScopedAltApp(mirrorAltApp,addScopeList, tempKey); + scopeInfo[0] = addScopeList; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE:Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + Group getScope(int index) { + return (Group)(((GroupRetained)(scopes.elementAt(index))).source); + } + + + /** + * Inserts the specified scope at specified index.before the + * alt app is live + * @param scope the new scope + * @param index position to insert new scope at + */ + void initInsertScope(Node scope, int index) { + GroupRetained group = (GroupRetained)scope.retained; + scopes.insertElementAt((GroupRetained)(scope.retained), index); + group.setAltAppScope(); + } + + /** + * Inserts the specified scope at specified index and sends + * a message + * @param scope the new scope + * @param index position to insert new scope at + */ + void insertScope(Node scope, int index) { + Object[] scopeInfo = new Object[3]; + ArrayList addScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scope.retained; + + initInsertScope(scope, index); + group = (GroupRetained)scope.retained; + tempKey.reset(); + group.addAllNodesForScopedAltApp(mirrorAltApp,addScopeList, tempKey); + + scopeInfo[0] = addScopeList; + scopeInfo[1] = null; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + + + void initRemoveScope(int index) { + GroupRetained group = (GroupRetained)scopes.elementAt(index); + scopes.removeElementAt(index); + group.removeAltAppScope(); + + } + + void removeScope(int index) { + + Object[] scopeInfo = new Object[3]; + ArrayList removeScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scopes.elementAt(index); + + tempKey.reset(); + group.removeAllNodesForScopedAltApp(mirrorAltApp, removeScopeList, tempKey); + + initRemoveScope(index); + scopeInfo[0] = null; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + /** + * Removes the specified Group node from this node's list of scopes. + * Method is a no-op if the + * specified node is not found + * @param The Group node to be removed + */ + void removeScope(Group scope) { + int ind = indexOfScope(scope); + if(ind >= 0) + removeScope(ind); + } + + void initRemoveScope(Group scope) { + int ind = indexOfScope(scope); + if(ind >= 0) + initRemoveScope(ind); + } + + void removeAllScopes() { + GroupRetained group; + ArrayList removeScopeList = new ArrayList(); + int n = scopes.size(); + for(int index = n-1; index >= 0; index--) { + group = (GroupRetained)scopes.elementAt(index); + tempKey.reset(); + group.removeAllNodesForScopedAltApp(mirrorAltApp, removeScopeList, tempKey); + initRemoveScope(index); + } + Object[] scopeInfo = new Object[3]; + scopeInfo[0] = null; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + void initRemoveAllScopes() { + int n = scopes.size(); + for(int i = n-1; i >= 0; i--) + initRemoveScope(i); + } + + /** + * Returns an enumeration object of the scoperen. + * @return an enumeration object of the scoperen + */ + Enumeration getAllScopes() { + Enumeration elm = scopes.elements(); + Vector v = new Vector(scopes.size()); + while (elm.hasMoreElements()) { + v.add( ((GroupRetained) elm.nextElement()).source); + } + return v.elements(); + } + + /** + * Returns the index of the specified Group node in this node's list of scopes. + * @param scope the Group node whose index is needed + */ + int indexOfScope(Group scope) { + if(scope != null) + return scopes.indexOf((GroupRetained)scope.retained); + else + return scopes.indexOf(null); + } + + /** + * Appends the specified scope to this node's list of scopes before + * the alt app is alive + * @param scope the scope to add to this node's list of scopes + */ + void initAddScope(Group scope) { + GroupRetained group = (GroupRetained)scope.retained; + scopes.addElement((GroupRetained)(scope.retained)); + group.setAltAppScope(); + } + + /** + * Appends the specified scope to this node's list of scopes. + * @param scope the scope to add to this node's list of scopes + */ + void addScope(Group scope) { + + Object[] scopeInfo = new Object[3]; + ArrayList addScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scope.retained; + + initAddScope(scope); + tempKey.reset(); + group.addAllNodesForScopedAltApp(mirrorAltApp,addScopeList, tempKey); + scopeInfo[0] = addScopeList; + scopeInfo[1] = null; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + + + /** + * Returns a count of this nodes' scopes. + * @return the number of scopes descendant from this node + */ + int numScopes() { + return scopes.size(); + } + + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return (inImmCtx); + } + + boolean isScoped() { + return (scopes != null); + } + + + void updateImmediateMirrorObject(Object[] objs) { + GroupRetained group; + Vector currentScopes; + int i, nscopes; + Transform3D trans; + + int component = ((Integer)objs[1]).intValue(); + if ((component & APPEARANCE_CHANGED) != 0) { + mirrorAltApp.appearance = (AppearanceRetained)objs[2]; + } + if ((component & BOUNDS_CHANGED) != 0) { + mirrorAltApp.regionOfInfluence = (Bounds) objs[2]; + if (mirrorAltApp.boundingLeaf == null) { + if (objs[2] != null) { + mirrorAltApp.region = (Bounds)mirrorAltApp.regionOfInfluence.copy(mirrorAltApp.region); + mirrorAltApp.region.transform( + mirrorAltApp.regionOfInfluence, + getCurrentLocalToVworld()); + } + else { + mirrorAltApp.region = null; + } + } + } + else if ((component & BOUNDINGLEAF_CHANGED) != 0) { + mirrorAltApp.boundingLeaf = (BoundingLeafRetained)objs[2]; + if (objs[2] != null) { + mirrorAltApp.region = (Bounds)mirrorAltApp.boundingLeaf.transformedRegion; + } + else { + if (mirrorAltApp.regionOfInfluence != null) { + mirrorAltApp.region = mirrorAltApp.regionOfInfluence.copy(mirrorAltApp.region); + mirrorAltApp.region.transform( + mirrorAltApp.regionOfInfluence, + getCurrentLocalToVworld()); + } + else { + mirrorAltApp.region = null; + } + + } + } + else if ((component & SCOPE_CHANGED) != 0) { + Object[] scopeList = (Object[])objs[2]; + ArrayList addList = (ArrayList)scopeList[0]; + ArrayList removeList = (ArrayList)scopeList[1]; + boolean isScoped = ((Boolean)scopeList[2]).booleanValue(); + + if (addList != null) { + mirrorAltApp.isScoped = isScoped; + for (i = 0; i < addList.size(); i++) { + Shape3DRetained obj = ((GeometryAtom)addList.get(i)).source; + obj.addAltApp(mirrorAltApp); + } + } + + if (removeList != null) { + mirrorAltApp.isScoped = isScoped; + for (i = 0; i < removeList.size(); i++) { + Shape3DRetained obj = ((GeometryAtom)removeList.get(i)).source; + obj.removeAltApp(mirrorAltApp); + } + } + } + + + } + + + /** Note: This routine will only be called on + * the mirror object - will update the object's + * cached region and transformed region + */ + + void updateBoundingLeaf() { + if (boundingLeaf != null && boundingLeaf.switchState.currentSwitchOn) { + region = boundingLeaf.transformedRegion; + } else { + if (regionOfInfluence != null) { + region = regionOfInfluence.copy(region); + region.transform(regionOfInfluence, getCurrentLocalToVworld()); + } else { + region = null; + } + } + } + + void setLive(SetLiveState s) { + GroupRetained group; + Vector currentScopes; + int i, nscopes; + TransformGroupRetained[] tlist; + + if (inImmCtx) { + throw new IllegalSharingException(J3dI18N.getString("AlternateAppearanceRetained13")); + } + + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("AlternateAppearanceRetained15")); + } + + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("AlternateAppearanceRetained16")); + } + + super.doSetLive(s); + + if (appearance != null) { + if (appearance.getInImmCtx()) { + throw new IllegalSharingException(J3dI18N.getString("AlternateAppearanceRetained14")); + } + synchronized(appearance.liveStateLock) { + appearance.setLive(inBackgroundGroup, s.refCount); + } + } + + // Create the mirror object + // Initialization of the mirror object during the INSERT_NODE + // message (in updateMirrorObject) + if (mirrorAltApp == null) { + mirrorAltApp = (AlternateAppearanceRetained)this.clone(); + // Assign the bounding leaf of this mirror object as null + // it will later be assigned to be the mirror of the alternate app + // bounding leaf object + mirrorAltApp.boundingLeaf = null; + mirrorAltApp.sgAltApp = this; + } + // If bounding leaf is not null, add the mirror object as a user + // so that any changes to the bounding leaf will be received + if (boundingLeaf != null) { + boundingLeaf.mirrorBoundingLeaf.addUser(mirrorAltApp); + } + + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(mirrorAltApp); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(mirrorAltApp); + } + + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(mirrorAltApp, + Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + // process switch leaf + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(mirrorAltApp, Targets.ENV_TARGETS); + } + mirrorAltApp.switchState = (SwitchState)s.switchStates.get(0); + + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + + // At the end make it live + super.markAsLive(); + + // Initialize the mirror object, this needs to be done, when + // renderBin is not accessing any of the fields + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.ALTERNATEAPPEARANCE_CHANGED; + createMessage.args[0] = this; + // a snapshot of all attributes that needs to be initialized + // in the mirror object + createMessage.args[1]= new Integer(INIT_MIRROR); + ArrayList addScopeList = new ArrayList(); + for (i = 0; i < scopes.size(); i++) { + group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.addAllNodesForScopedAltApp(mirrorAltApp, addScopeList, tempKey); + } + Object[] scopeInfo = new Object[2]; + scopeInfo[0] = ((scopes.size() > 0) ? Boolean.TRUE:Boolean.FALSE); + scopeInfo[1] = addScopeList; + createMessage.args[2] = scopeInfo; + if (appearance != null) { + createMessage.args[3] = appearance.mirror; + } + else { + createMessage.args[3] = null; + } + Object[] obj = new Object[2]; + obj[0] = boundingLeaf; + obj[1] = (regionOfInfluence != null?regionOfInfluence.clone():null); + createMessage.args[4] = obj; + VirtualUniverse.mc.processMessage(createMessage); + + + + + + } + + /** + * This is called on the parent object + */ + void initMirrorObject(Object[] args) { + Shape3DRetained shape; + Object[] scopeInfo = (Object[]) args[2]; + Boolean scoped = (Boolean)scopeInfo[0]; + ArrayList shapeList = (ArrayList)scopeInfo[1]; + AppearanceRetained app = (AppearanceRetained)args[3]; + BoundingLeafRetained bl=(BoundingLeafRetained)((Object[])args[4])[0]; + Bounds bnds = (Bounds)((Object[])args[4])[1]; + + for (int i = 0; i < shapeList.size(); i++) { + shape = ((GeometryAtom)shapeList.get(i)).source; + shape.addAltApp(mirrorAltApp); + } + mirrorAltApp.isScoped = scoped.booleanValue(); + + if (app != null) + mirrorAltApp.appearance = app; + + if (bl != null) { + mirrorAltApp.boundingLeaf = bl.mirrorBoundingLeaf; + mirrorAltApp.region = boundingLeaf.transformedRegion; + } else { + mirrorAltApp.boundingLeaf = null; + mirrorAltApp.region = null; + } + + if (bnds != null) { + mirrorAltApp.regionOfInfluence = bnds; + if (mirrorAltApp.region == null) { + mirrorAltApp.region = (Bounds)regionOfInfluence.clone(); + mirrorAltApp.region.transform(regionOfInfluence, getLastLocalToVworld()); + } + } + else { + mirrorAltApp.regionOfInfluence = null; + } + + } + + void clearMirrorObject(Object[] args) { + Shape3DRetained shape; + ArrayList shapeList = (ArrayList)args[2]; + ArrayList removeScopeList = new ArrayList(); + + for (int i = 0; i < shapeList.size(); i++) { + shape = ((GeometryAtom)shapeList.get(i)).source; + shape.removeAltApp(mirrorAltApp); + } + mirrorAltApp.isScoped = false; + + + + } + + + /** + * This clearLive routine first calls the superclass's method, then + * it removes itself to the list of alt app + */ + void clearLive(SetLiveState s) { + int i, j; + GroupRetained group; + + if (appearance != null) { + synchronized(appearance.liveStateLock) { + appearance.clearLive(s.refCount); + } + } + + super.clearLive(s); + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + + // Remove this mirror light as users of the bounding leaf + if (mirrorAltApp.boundingLeaf != null) + mirrorAltApp.boundingLeaf.removeUser(mirrorAltApp); + + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(mirrorAltApp); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(mirrorAltApp); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(mirrorAltApp, + Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(mirrorAltApp, Targets.ENV_TARGETS); + } + + + if (scopes.size() > 0) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.ALTERNATEAPPEARANCE_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(CLEAR_MIRROR); + ArrayList removeScopeList = new ArrayList(); + for (i = 0; i < scopes.size(); i++) { + group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.removeAllNodesForScopedAltApp(mirrorAltApp, removeScopeList, tempKey); + } + createMessage.args[2] = removeScopeList; + VirtualUniverse.mc.processMessage(createMessage); + } + } + + + + void updateTransformChange() { + } + + /** + * Called on mirror object + */ + void updateImmediateTransformChange() { + // If bounding leaf is null, tranform the bounds object + if (boundingLeaf == null) { + if (regionOfInfluence != null) { + region = regionOfInfluence.copy(region); + region.transform(regionOfInfluence, + sgAltApp.getCurrentLocalToVworld()); + } + + } + } + + final void sendMessage(int attrMask, Object attr) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.universe = universe; + createMessage.type = J3dMessage.ALTERNATEAPPEARANCE_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + VirtualUniverse.mc.processMessage(createMessage); + } + + + void getMirrorObjects(ArrayList leafList, HashKey key) { + leafList.add(mirrorAltApp); + } + + + /** + * Copies all AlternateAppearance information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + throw new RuntimeException("method not implemented"); + +// super.duplicateAttributes(originalNode, forceDuplicate); + +// AlternateAppearance alternate appearance = (AlternateAppearance) originalNode; + +// // TODO: clone appearance + +// setInfluencingBounds(alternate appearance.getInfluencingBounds()); + +// Enumeration elm = alternate appearance.getAllScopes(); +// while (elm.hasMoreElements()) { +// // this reference will set correctly in updateNodeReferences() callback +// addScope((Group) elm.nextElement()); +// } + +// // this reference will set correctly in updateNodeReferences() callback +// setInfluencingBoundingLeaf(alternate appearance.getInfluencingBoundingLeaf()); + } + +// /** +// * Callback used to allow a node to check if any nodes referenced +// * by that node have been duplicated via a call to <code>cloneTree</code>. +// * This method is called by <code>cloneTree</code> after all nodes in +// * the sub-graph have been duplicated. The cloned Leaf node's method +// * will be called and the Leaf node can then look up any node references +// * by using the <code>getNewObjectReference</code> method found in the +// * <code>NodeReferenceTable</code> object. If a match is found, a +// * reference to the corresponding Node in the newly cloned sub-graph +// * is returned. If no corresponding reference is found, either a +// * DanglingReferenceException is thrown or a reference to the original +// * node is returned depending on the value of the +// * <code>allowDanglingReferences</code> parameter passed in the +// * <code>cloneTree</code> call. +// * <p> +// * NOTE: Applications should <i>not</i> call this method directly. +// * It should only be called by the cloneTree method. +// * +// * @param referenceTable a NodeReferenceTableObject that contains the +// * <code>getNewObjectReference</code> method needed to search for +// * new object instances. +// * @see NodeReferenceTable +// * @see Node#cloneTree +// * @see DanglingReferenceException +// */ +// public void updateNodeReferences(NodeReferenceTable referenceTable) { +// throw new RuntimeException("method not implemented"); +// +// Object o; +// +// BoundingLeaf bl = getInfluencingBoundingLeaf(); +// if (bl != null) { +// o = referenceTable.getNewObjectReference(bl); +// setInfluencingBoundingLeaf((BoundingLeaf) o); +// } +// +// for (int i=0; i < numScopes(); i++) { +// o = referenceTable.getNewObjectReference(getScope(i)); +// setScope((Group) o, i); +// } +// } + +} diff --git a/src/classes/share/javax/media/j3d/AmbientLight.java b/src/classes/share/javax/media/j3d/AmbientLight.java new file mode 100644 index 0000000..71106fd --- /dev/null +++ b/src/classes/share/javax/media/j3d/AmbientLight.java @@ -0,0 +1,90 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; + +/** + * An ambient light source object. Ambient light is that light + * that seems to come from all directions. The AmbientLight object + * has the same attributes as a Light node, including color, + * influencing bounds, scopes, and + * a flag indicating whether this light source is on or off. + * Ambient reflections do not depend on the orientation or + * position of a surface. + * Ambient light has only an ambient reflection component. + * It does not have diffuse or specular reflection components. + * <p> + * For more information on Java 3D lighting, see the class description + * for Light. + * <p> + */ + +public class AmbientLight extends Light { + /** + * Constructs and initializes an ambient light using default parameters. + */ + public AmbientLight() { + } + + + /** + * Constructs and initializes an ambient light using the specified + * parameters. + * @param color the color of the light source. + */ + public AmbientLight(Color3f color) { + super(color); + } + + + /** + * Constructs and initializes an ambient light using the specified + * parameters. + * @param lightOn flag indicating whether this light is on or off. + * @param color the color of the light source. + */ + public AmbientLight(boolean lightOn, Color3f color) { + super(lightOn, color); + } + + /** + * Creates the retained mode AmbientLightRetained object that this + * AmbientLight component object will point to. + */ + void createRetained() { + this.retained = new AmbientLightRetained(); + this.retained.setSource(this); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + AmbientLight a = new AmbientLight(); + a.duplicateNode(this, forceDuplicate); + return a; + } + +} diff --git a/src/classes/share/javax/media/j3d/AmbientLightRetained.java b/src/classes/share/javax/media/j3d/AmbientLightRetained.java new file mode 100644 index 0000000..098566f --- /dev/null +++ b/src/classes/share/javax/media/j3d/AmbientLightRetained.java @@ -0,0 +1,39 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * An ambient light source object. + */ + +class AmbientLightRetained extends LightRetained { + + AmbientLightRetained() { + this.nodeType = NodeRetained.AMBIENTLIGHT; + lightType = 1; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + void setLive(SetLiveState s) { + super.setLive(s); + J3dMessage createMessage = super.initMessage(7); + VirtualUniverse.mc.processMessage(createMessage); + } + + void update(long ctx, int lightSlot, double scale) { + } +} diff --git a/src/classes/share/javax/media/j3d/Appearance.java b/src/classes/share/javax/media/j3d/Appearance.java new file mode 100644 index 0000000..ed5f12b --- /dev/null +++ b/src/classes/share/javax/media/j3d/Appearance.java @@ -0,0 +1,927 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Hashtable; + +/** + * The Appearance object defines all rendering state that can be set + * as a component object of a Shape3D node. The rendering state + * consists of the following:<p> + * <ul> + * <li>Coloring attributes - defines attributes used in color selection + * and shading. These attributes are defined in a ColoringAttributes + * object.</li><p> + * + * <li>Line attributes - defines attributes used to define lines, including + * the pattern, width, and whether antialiasing is to be used. These + * attributes are defined in a LineAttributes object.</li><p> + * + * <li>Point attributes - defines attributes used to define points, + * including the size and whether antialiasing is to be used. These + * attributes are defined in a PointAttributes object.</li><p> + * + * <li>Polygon attributes - defines the attributes used to define + * polygons, including culling, rasterization mode (filled, lines, + * or points), constant offset, offset factor, and whether back + * back facing normals are flipped. These attributes are defined + * in a PolygonAttributes object.</li><p> + * + * <li>Rendering attributes - defines rendering operations, + * including the alpha test function and test value, the raster + * operation, whether vertex colors are ignored, whether invisible + * objects are rendered, and whether the depth buffer is enabled. + * These attributes are defined in a RenderingAttributes + * object.</li><p> + * + * <li>Transparency attributes - defines the attributes that affect + * transparency of the object, such as the transparency mode + * (blended, screen-door), blending function (used in transparency + * and antialiasing operations), and a blend value that defines + * the amount of transparency to be applied to this Appearance + * component object.</li><p> + * + * <li>Material - defines the appearance of an object under illumination, + * such as the ambient color, diffuse color, specular color, emissive + * color, and shininess. These attributes are defined in a Material + * object.</li><p> + * + * <li>Texture - defines the texture image and filtering + * parameters used when texture mapping is enabled. These attributes + * are defined in a Texture object.</li><p> + * + * <li>Texture attributes - defines the attributes that apply to + * texture mapping, such as the texture mode, texture transform, + * blend color, and perspective correction mode. These attributes + * are defined in a TextureAttributes object.</li><p> + * + * <li>Texture coordinate generation - defines the attributes + * that apply to texture coordinate generation, such as whether + * texture coordinate generation is enabled, coordinate format + * (2D or 3D coordinates), coordinate generation mode (object + * linear, eye linear, or spherical reflection mapping), and the + * R, S, and T coordinate plane equations. These attributes + * are defined in a TexCoordGeneration object.</li><p> + * + * <li>Texture unit state - array that defines texture state for each + * of <i>N</i> separate texture units. This allows multiple textures + * to be applied to geometry. Each TextureUnitState object contains a + * Texture object, TextureAttributes, and TexCoordGeneration object + * for one texture unit. If the length of the texture unit state + * array is greater than 0, then the array is used for all texture + * state; the individual Texture, TextureAttributes, and + * TexCoordGeneration objects in this Appearance object are not used + * and and must not be set by an application. If the length of the + * texture unit state array is 0, the multi-texture is disabled and + * the Texture, TextureAttributes, and TexCoordGeneration objects + * in the Appearance object are used. If the application sets the + * existing Texture, TextureAttributes, and TexCoordGeneration + * objects to non-null values, they effectively define the state + * for texture unit 0. If the TextureUnitState array is set to a + * non-null, non-empty array, the individual TextureUnitState + * objects define the state for texture units 0 through <i>n</i> + * -1. If both the old and new values are set, an exception is thrown. + * + * </li> + * </ul> + * + * @see ColoringAttributes + * @see LineAttributes + * @see PointAttributes + * @see PolygonAttributes + * @see RenderingAttributes + * @see TransparencyAttributes + * @see Material + * @see Texture + * @see TextureAttributes + * @see TexCoordGeneration + * @see TextureUnitState + */ +public class Appearance extends NodeComponent { + + /** + * Specifies that this Appearance object + * allows reading its coloringAttributes component + * information. + */ + public static final int + ALLOW_COLORING_ATTRIBUTES_READ = CapabilityBits.APPEARANCE_ALLOW_COLORING_ATTRIBUTES_READ; + + /** + * Specifies that this Appearance object + * allows writing its coloringAttributes component + * information. + */ + public static final int + ALLOW_COLORING_ATTRIBUTES_WRITE = CapabilityBits.APPEARANCE_ALLOW_COLORING_ATTRIBUTES_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its transparency component + * information. + */ + public static final int + ALLOW_TRANSPARENCY_ATTRIBUTES_READ = CapabilityBits.APPEARANCE_ALLOW_TRANSPARENCY_ATTRIBUTES_READ; + + /** + * Specifies that this Appearance object + * allows writing its transparency component + * information. + */ + public static final int + ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE = CapabilityBits.APPEARANCE_ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its rendering/rasterization component + * information. + */ + public static final int + ALLOW_RENDERING_ATTRIBUTES_READ = CapabilityBits.APPEARANCE_ALLOW_RENDERING_ATTRIBUTES_READ; + + /** + * Specifies that this Appearance object + * allows writing its rendering/rasterization component + * information. + */ + public static final int + ALLOW_RENDERING_ATTRIBUTES_WRITE = CapabilityBits.APPEARANCE_ALLOW_RENDERING_ATTRIBUTES_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its polygon component + * information. + */ + public static final int + ALLOW_POLYGON_ATTRIBUTES_READ = CapabilityBits.APPEARANCE_ALLOW_POLYGON_ATTRIBUTES_READ; + + /** + * Specifies that this Appearance object + * allows writing its polygon component + * information. + */ + public static final int + ALLOW_POLYGON_ATTRIBUTES_WRITE = CapabilityBits.APPEARANCE_ALLOW_POLYGON_ATTRIBUTES_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its line component + * information. + */ + public static final int + ALLOW_LINE_ATTRIBUTES_READ = CapabilityBits.APPEARANCE_ALLOW_LINE_ATTRIBUTES_READ; + + /** + * Specifies that this Appearance object + * allows writing its line component + * information. + */ + public static final int + ALLOW_LINE_ATTRIBUTES_WRITE = CapabilityBits.APPEARANCE_ALLOW_LINE_ATTRIBUTES_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its point component + * information. + */ + public static final int + ALLOW_POINT_ATTRIBUTES_READ = CapabilityBits.APPEARANCE_ALLOW_POINT_ATTRIBUTES_READ; + + /** + * Specifies that this Appearance object + * allows writing its point component + * information. + */ + public static final int + ALLOW_POINT_ATTRIBUTES_WRITE = CapabilityBits.APPEARANCE_ALLOW_POINT_ATTRIBUTES_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its material component information. + */ + public static final int + ALLOW_MATERIAL_READ = CapabilityBits.APPEARANCE_ALLOW_MATERIAL_READ; + + /** + * Specifies that this Appearance object + * allows writing its material component information. + */ + public static final int + ALLOW_MATERIAL_WRITE = CapabilityBits.APPEARANCE_ALLOW_MATERIAL_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its texture component information. + */ + public static final int + ALLOW_TEXTURE_READ = CapabilityBits.APPEARANCE_ALLOW_TEXTURE_READ; + + /** + * Specifies that this Appearance object + * allows writing its texture component information. + */ + public static final int + ALLOW_TEXTURE_WRITE = CapabilityBits.APPEARANCE_ALLOW_TEXTURE_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its textureAttributes component + * information. + */ + public static final int + ALLOW_TEXTURE_ATTRIBUTES_READ = CapabilityBits.APPEARANCE_ALLOW_TEXTURE_ATTRIBUTES_READ; + + /** + * Specifies that this Appearance object + * allows writing its textureAttributes component + * information. + */ + public static final int + ALLOW_TEXTURE_ATTRIBUTES_WRITE = CapabilityBits.APPEARANCE_ALLOW_TEXTURE_ATTRIBUTES_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its texture coordinate generation component + * information. + */ + public static final int + ALLOW_TEXGEN_READ = CapabilityBits.APPEARANCE_ALLOW_TEXGEN_READ; + + /** + * Specifies that this Appearance object + * allows writing its texture coordinate generation component + * information. + */ + public static final int + ALLOW_TEXGEN_WRITE = CapabilityBits.APPEARANCE_ALLOW_TEXGEN_WRITE; + + /** + * Specifies that this Appearance object + * allows reading its texture unit state component + * information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_TEXTURE_UNIT_STATE_READ = + CapabilityBits.APPEARANCE_ALLOW_TEXTURE_UNIT_STATE_READ; + + /** + * Specifies that this Appearance object + * allows writing its texture unit state component + * information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_TEXTURE_UNIT_STATE_WRITE = + CapabilityBits.APPEARANCE_ALLOW_TEXTURE_UNIT_STATE_WRITE; + + + /** + * Constructs an Appearance component object using defaults for all + * state variables. All component object references are initialized + * to null. + */ + public Appearance() { + // Just use default values + } + + /** + * Creates the retained mode AppearanceRetained object that this + * Appearance component object will point to. + */ + void createRetained() { + this.retained = new AppearanceRetained(); + this.retained.setSource(this); + } + + /** + * Sets the material object to the specified object. + * Setting it to null disables lighting. + * @param material object that specifies the desired material + * properties + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setMaterial(Material material) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MATERIAL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance0")); + ((AppearanceRetained)this.retained).setMaterial(material); + } + + /** + * Retrieves the current material object. + * @return the material object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Material getMaterial() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MATERIAL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance1")); + return ((AppearanceRetained)this.retained).getMaterial(); + } + + /** + * Sets the coloringAttributes object to the specified object. + * Setting it to null will result in default attribute usage. + * @param coloringAttributes object that specifies the desired + * coloringAttributes parameters + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setColoringAttributes(ColoringAttributes coloringAttributes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLORING_ATTRIBUTES_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance6")); + ((AppearanceRetained)this.retained).setColoringAttributes(coloringAttributes); + } + + /** + * Retrieves the current coloringAttributes object. + * @return the coloringAttributes object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public ColoringAttributes getColoringAttributes() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLORING_ATTRIBUTES_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance7")); + return ((AppearanceRetained)this.retained).getColoringAttributes(); + } + + /** + * Sets the transparencyAttributes object to the specified object. + * Setting it to null will result in default attribute usage. + * @param transparencyAttributes object that specifies the desired + * transparencyAttributes parameters + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setTransparencyAttributes(TransparencyAttributes transparencyAttributes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance8")); + ((AppearanceRetained)this.retained).setTransparencyAttributes(transparencyAttributes); + } + + /** + * Retrieves the current transparencyAttributes object. + * @return the transparencyAttributes object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public TransparencyAttributes getTransparencyAttributes() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TRANSPARENCY_ATTRIBUTES_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance9")); + return ((AppearanceRetained)this.retained).getTransparencyAttributes(); + } + + /** + * Sets the renderingAttributes object to the specified object. + * Setting it to null will result in default attribute usage. + * @param renderingAttributes object that specifies the desired + * renderingAttributes parameters + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setRenderingAttributes(RenderingAttributes renderingAttributes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_RENDERING_ATTRIBUTES_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance10")); + ((AppearanceRetained)this.retained).setRenderingAttributes(renderingAttributes); + } + + /** + * Retrieves the current renderingAttributes object. + * @return the renderingAttributes object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public RenderingAttributes getRenderingAttributes() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_RENDERING_ATTRIBUTES_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance11")); + return ((AppearanceRetained)this.retained).getRenderingAttributes(); + } + + /** + * Sets the polygonAttributes object to the specified object. + * Setting it to null will result in default attribute usage. + * @param polygonAttributes object that specifies the desired + * polygonAttributes parameters + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPolygonAttributes(PolygonAttributes polygonAttributes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_POLYGON_ATTRIBUTES_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance12")); + ((AppearanceRetained)this.retained).setPolygonAttributes(polygonAttributes); + } + + /** + * Retrieves the current polygonAttributes object. + * @return the polygonAttributes object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public PolygonAttributes getPolygonAttributes() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_POLYGON_ATTRIBUTES_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance13")); + return ((AppearanceRetained)this.retained).getPolygonAttributes(); + } + + /** + * Sets the lineAttributes object to the specified object. + * Setting it to null will result in default attribute usage. + * @param lineAttributes object that specifies the desired + * lineAttributes parameters + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setLineAttributes(LineAttributes lineAttributes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_LINE_ATTRIBUTES_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance14")); + ((AppearanceRetained)this.retained).setLineAttributes(lineAttributes); + } + + /** + * Retrieves the current lineAttributes object. + * @return the lineAttributes object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public LineAttributes getLineAttributes() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_LINE_ATTRIBUTES_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance15")); + return ((AppearanceRetained)this.retained).getLineAttributes(); + } + + /** + * Sets the pointAttributes object to the specified object. + * Setting it to null will result in default attribute usage. + * @param pointAttributes object that specifies the desired + * pointAttributes parameters + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPointAttributes(PointAttributes pointAttributes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_POINT_ATTRIBUTES_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance16")); + ((AppearanceRetained)this.retained).setPointAttributes(pointAttributes); + } + + /** + * Retrieves the current pointAttributes object. + * @return the pointAttributes object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public PointAttributes getPointAttributes() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_POINT_ATTRIBUTES_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance17")); + return ((AppearanceRetained)this.retained).getPointAttributes(); + } + + /** + * Sets the texture object to the specified object. + * Setting it to null disables texture mapping. + * + * <p> + * Applications must not set individual texture component objects + * (texture, textureAttributes, or texCoordGeneration) and + * the texture unit state array in the same Appearance object. + * Doing so will result in an exception being thrown. + * + * @param texture object that specifies the desired texture + * map and texture parameters + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalStateException if the specified texture + * object is non-null and the texture unit state array in this + * appearance object is already non-null. + */ + public void setTexture(Texture texture) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance2")); + ((AppearanceRetained)this.retained).setTexture(texture); + } + + /** + * Retrieves the current texture object. + * @return the texture object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Texture getTexture() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance3")); + return ((AppearanceRetained)this.retained).getTexture(); + } + + /** + * Sets the textureAttributes object to the specified object. + * Setting it to null will result in default attribute usage. + * + * <p> + * Applications must not set individual texture component objects + * (texture, textureAttributes, or texCoordGeneration) and + * the texture unit state array in the same Appearance object. + * Doing so will result in an exception being thrown. + * + * @param textureAttributes object that specifies the desired + * textureAttributes map and textureAttributes parameters + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalStateException if the specified textureAttributes + * object is non-null and the texture unit state array in this + * appearance object is already non-null. + */ + public void setTextureAttributes(TextureAttributes textureAttributes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_ATTRIBUTES_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance4")); + ((AppearanceRetained)this.retained).setTextureAttributes(textureAttributes); + } + + /** + * Retrieves the current textureAttributes object. + * @return the textureAttributes object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public TextureAttributes getTextureAttributes() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_ATTRIBUTES_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance5")); + return ((AppearanceRetained)this.retained).getTextureAttributes(); + } + + /** + * Sets the texCoordGeneration object to the specified object. + * Setting it to null disables texture coordinate generation. + * + * <p> + * Applications must not set individual texture component objects + * (texture, textureAttributes, or texCoordGeneration) and + * the texture unit state array in the same Appearance object. + * Doing so will result in an exception being thrown. + * + * @param texCoordGeneration object that specifies the texture coordinate + * generation parameters + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalStateException if the specified texCoordGeneration + * object is non-null and the texture unit state array in this + * appearance object is already non-null. + */ + public void setTexCoordGeneration(TexCoordGeneration texCoordGeneration) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXGEN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance18")); + ((AppearanceRetained)this.retained).setTexCoordGeneration(texCoordGeneration); + } + + /** + * Retrieves the current texCoordGeneration object. + * @return the texCoordGeneration object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public TexCoordGeneration getTexCoordGeneration() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXGEN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance19")); + return ((AppearanceRetained)this.retained).getTexCoordGeneration(); + } + + /** + * Sets the texture unit state array for this appearance object to the + * specified array. A shallow copy of the array of references to + * the TextureUnitState objects is made. If the specified array + * is null or if the length of the array is 0, multi-texture is + * disabled. Within the array, a null TextureUnitState element + * disables the corresponding texture unit. + * + * <p> + * Applications must not set individual texture component objects + * (texture, textureAttributes, or texCoordGeneration) and + * the texture unit state array in the same Appearance object. + * Doing so will result in an exception being thrown. + * + * @param stateArray array of TextureUnitState objects that + * specify the desired texture state for each unit. The length of + * this array specifies the maximum number of texture units that + * will be used by this appearance object. The texture units are + * numbered from <code>0</code> through + * <code>stateArray.length-1</code>. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalStateException if the specified array is + * non-null and any of the texture object, textureAttributes + * object, or texCoordGeneration object in this appearance object + * is already non-null. + * + * @since Java 3D 1.2 + */ + public void setTextureUnitState(TextureUnitState[] stateArray) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_UNIT_STATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance20")); + + ((AppearanceRetained)this.retained).setTextureUnitState(stateArray); + } + + /** + * Sets the texture unit state object at the specified index + * within the texture unit state array to the specified object. + * If the specified object is null, the corresponding texture unit + * is disabled. The index must be within the range + * <code>[0, stateArray.length-1]</code>. + * + * @param index the array index of the object to be set + * + * @param state new texture unit state object + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception NullPointerException if the texture unit state array is + * null. + * @exception ArrayIndexOutOfBoundsException if <code>index >= + * stateArray.length</code>. + * + * @since Java 3D 1.2 + */ + public void setTextureUnitState(int index, TextureUnitState state) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_UNIT_STATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance20")); + + ((AppearanceRetained)this.retained).setTextureUnitState(index, state); + } + + /** + * Retrieves the array of texture unit state objects from this + * Appearance object. A shallow copy of the array of references to + * the TextureUnitState objects is returned. + * + * @return the array of texture unit state objects + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public TextureUnitState[] getTextureUnitState() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_UNIT_STATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance21")); + + return ((AppearanceRetained)this.retained).getTextureUnitState(); + } + + /** + * Retrieves the texture unit state object at the specified + * index within the texture unit state array. The index must be + * within the range <code>[0, stateArray.length-1]</code>. + * + * @param index the array index of the object to be retrieved + * + * @return the texture unit state object at the specified index + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public TextureUnitState getTextureUnitState(int index) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_UNIT_STATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance21")); + + return ((AppearanceRetained)this.retained).getTextureUnitState(index); + } + + /** + * Retrieves the length of the texture unit state array from + * this appearance object. The length of this array specifies the + * maximum number of texture units that will be used by this + * appearance object. If the array is null, a count of 0 is + * returned. + * + * @return the length of the texture unit state array + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getTextureUnitCount() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TEXTURE_UNIT_STATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Appearance21")); + + return ((AppearanceRetained)this.retained).getTextureUnitCount(); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + Appearance a = new Appearance(); + a.duplicateNodeComponent(this); + return a; + } + + /** + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @deprecated replaced with duplicateNodeComponent( + * NodeComponent originalNodeComponent, boolean forceDuplicate) + */ + public void duplicateNodeComponent(NodeComponent originalNodeComponent) { + checkDuplicateNodeComponent(originalNodeComponent); + } + + /** + * Copies all Appearance information from + * <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + Hashtable hashtable = originalNodeComponent.nodeHashtable; + + AppearanceRetained app = (AppearanceRetained) originalNodeComponent.retained; + + AppearanceRetained rt = (AppearanceRetained) retained; + + rt.setMaterial((Material) getNodeComponent(app.getMaterial(), + forceDuplicate, + hashtable)); + + rt.setColoringAttributes((ColoringAttributes) getNodeComponent( + app.getColoringAttributes(), + forceDuplicate, + hashtable)); + + + rt.setTransparencyAttributes((TransparencyAttributes) getNodeComponent( + app.getTransparencyAttributes(), + forceDuplicate, + hashtable)); + + + rt.setRenderingAttributes((RenderingAttributes) getNodeComponent( + app.getRenderingAttributes(), + forceDuplicate, + hashtable)); + + + rt.setPolygonAttributes((PolygonAttributes) getNodeComponent( + app.getPolygonAttributes(), + forceDuplicate, + hashtable)); + + + rt.setLineAttributes((LineAttributes) getNodeComponent( + app.getLineAttributes(), + forceDuplicate, + hashtable)); + + + rt.setPointAttributes((PointAttributes) getNodeComponent( + app.getPointAttributes(), + forceDuplicate, + hashtable)); + + rt.setTexture((Texture) getNodeComponent(app.getTexture(), + forceDuplicate, + hashtable)); + + rt.setTextureAttributes((TextureAttributes) getNodeComponent( + app.getTextureAttributes(), + forceDuplicate, + hashtable)); + + rt.setTexCoordGeneration((TexCoordGeneration) getNodeComponent( + app.getTexCoordGeneration(), + forceDuplicate, + hashtable)); + + TextureUnitState state[] = app.getTextureUnitState(); + if (state != null) { + rt.setTextureUnitState(state); + for (int i=0; i < state.length; i++) { + rt.setTextureUnitState(i, (TextureUnitState) + getNodeComponent(state[i], + forceDuplicate, + hashtable)); + } + } + + } + + /** + * This function is called from getNodeComponent() to see if any of + * the sub-NodeComponents duplicateOnCloneTree flag is true. + * If it is the case, current NodeComponent needs to + * duplicate also even though current duplicateOnCloneTree flag is false. + * This should be overwrite by NodeComponent which contains sub-NodeComponent. + */ + boolean duplicateChild() { + if (getDuplicateOnCloneTree()) + return true; + + AppearanceRetained rt = (AppearanceRetained) retained; + + NodeComponent nc; + + nc = rt.getMaterial(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getColoringAttributes(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getTransparencyAttributes(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getPolygonAttributes(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getLineAttributes(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getPointAttributes(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getTexture(); + if ((nc != null) && nc.duplicateChild()) + return true; + + nc = rt.getTextureAttributes(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getTexCoordGeneration(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + // TODO: TextureUnitState + + return false; + } + +} diff --git a/src/classes/share/javax/media/j3d/AppearanceRetained.java b/src/classes/share/javax/media/j3d/AppearanceRetained.java new file mode 100644 index 0000000..a5afcee --- /dev/null +++ b/src/classes/share/javax/media/j3d/AppearanceRetained.java @@ -0,0 +1,1391 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; +import java.util.BitSet; +import java.util.ArrayList; + + +/** + * The Appearance object defines all rendering state that can be set + * as a component object of a Shape3D node. + */ +class AppearanceRetained extends NodeComponentRetained { + + // + // State variables: these should all be initialized to approproate + // Java 3D defaults. + // + + // Material object used when lighting is enabled + MaterialRetained material = null; + + // Texture object used to apply a texture map to an object + TextureRetained texture = null; + + // Texture coordinate generation object + TexCoordGenerationRetained texCoordGeneration = null; + + // Texture Attributes bundle object + TextureAttributesRetained textureAttributes = null; + + TextureUnitStateRetained texUnitState[] = null; + + // Coloring Attributes bundle object + ColoringAttributesRetained coloringAttributes = null; + + // Transparency Attributes bundle object + TransparencyAttributesRetained transparencyAttributes = null; + + // Rendering Attributes bundle object + RenderingAttributesRetained renderingAttributes = null; + + // Polygon Attributes bundle object + PolygonAttributesRetained polygonAttributes = null; + + // Line Attributes bundle object + LineAttributesRetained lineAttributes = null; + + // Point Attributes bundle object + PointAttributesRetained pointAttributes = null; + + + // Lock used for synchronization of live state + Object liveStateLock = new Object(); + + // NOTE: Consider grouping random state into common objects + + // Cache used during compilation. If map == compState, then + // mapAppearance can be used for this appearance + CompileState map = null; + AppearanceRetained mapAppearance = null; + + static final int MATERIAL = 0x0001; + static final int TEXTURE = 0x0002; + static final int TEXCOORD_GEN = 0x0004; + static final int TEXTURE_ATTR = 0x0008; + static final int COLOR = 0x0010; + static final int TRANSPARENCY = 0x0020; + static final int RENDERING = 0x0040; + static final int POLYGON = 0x0080; + static final int LINE = 0x0100; + static final int POINT = 0x0200; + static final int TEXTURE_UNIT_STATE = 0x0400; + + static final int ALL_COMPONENTS = (MATERIAL|TEXTURE|TEXCOORD_GEN|TEXTURE_ATTR|COLOR|TRANSPARENCY| + RENDERING|POLYGON|LINE|POINT|TEXTURE_UNIT_STATE); + + static final int ALL_SOLE_USERS = 0; + + // A pointer to the scene graph appearance object + AppearanceRetained sgApp = null; + + // The object level hashcode for this appearance + // int objHashCode = super.hashCode(); + + /** + * Set the material object to the specified object. + * @param material object that specifies the desired material + * @exception IllegalSharingException + * properties + */ + void setMaterial(Material material) { + + synchronized(liveStateLock) { + if (source.isLive()) { + + if (this.material != null) { + this.material.clearLive(refCount); + this.material.removeMirrorUsers(this); + } + if (material != null) { + ((MaterialRetained)material.retained).setLive(inBackgroundGroup, refCount); + // If appearance is live, then copy all the users of this + // appaearance as users of this material + ((MaterialRetained)material.retained).copyMirrorUsers(this); + } + sendMessage(MATERIAL, + (material != null ? + ((MaterialRetained)material.retained).mirror : null), true); + } + if (material == null) { + this.material = null; + } else { + this.material = (MaterialRetained)material.retained; + } + } + } + + /** + * Retrieve the current material object. + * @return the material object + */ + Material getMaterial() { + return (material == null ? null : (Material)material.source); + } + + /** + * Sets the texture object to the specified object. + * @param texture object that specifies the desired texture + * map and texture parameters + */ + void setTexture(Texture texture) { + + + synchronized(liveStateLock) { + if (source.isLive()) { + + if (this.texture != null) { + this.texture.clearLive(refCount); + this.texture.removeMirrorUsers(this); + } + + if (texture != null) { + ((TextureRetained)texture.retained).setLive(inBackgroundGroup, refCount); + ((TextureRetained)texture.retained).copyMirrorUsers(this); + } + sendMessage(TEXTURE, + (texture != null ? + ((TextureRetained)texture.retained).mirror : null), true); + + } + + + if (texture == null) { + this.texture = null; + } else { + this.texture = (TextureRetained)texture.retained; + } + } + } + + /** + * Retrieves the current texture object. + * @return the texture object + */ + Texture getTexture() { + return (texture == null ? null : (Texture)texture.source); + } + + /** + * Sets the textureAttrbutes object to the specified object. + * @param textureAttributes object that specifies the desired texture + * attributes + */ + void setTextureAttributes(TextureAttributes textureAttributes) { + + synchronized(liveStateLock) { + if (source.isLive()) { + + if (this.textureAttributes != null) { + this.textureAttributes.clearLive(refCount); + this.textureAttributes.removeMirrorUsers(this); + } + + if (textureAttributes != null) { + ((TextureAttributesRetained)textureAttributes.retained).setLive(inBackgroundGroup, refCount); + ((TextureAttributesRetained)textureAttributes.retained).copyMirrorUsers(this); + } + sendMessage(TEXTURE_ATTR, + (textureAttributes != null ? + ((TextureAttributesRetained)textureAttributes.retained).mirror: + null), true); + + } + + + if (textureAttributes == null) { + this.textureAttributes = null; + } else { + this.textureAttributes = (TextureAttributesRetained)textureAttributes.retained; + } + } + } + + /** + * Retrieves the current textureAttributes object. + * @return the textureAttributes object + */ + TextureAttributes getTextureAttributes() { + return (textureAttributes == null ? null : + (TextureAttributes)textureAttributes.source); + } + + /** + * Sets the coloringAttrbutes object to the specified object. + * @param coloringAttributes object that specifies the desired texture + * attributes + */ + void setColoringAttributes(ColoringAttributes coloringAttributes) { + + synchronized(liveStateLock) { + if (source.isLive()) { + + if (this.coloringAttributes != null) { + this.coloringAttributes.clearLive(refCount); + this.coloringAttributes.removeMirrorUsers(this); + } + + if (coloringAttributes != null) { + ((ColoringAttributesRetained)coloringAttributes.retained).setLive(inBackgroundGroup, refCount); + ((ColoringAttributesRetained)coloringAttributes.retained).copyMirrorUsers(this); + } + sendMessage(COLOR, + (coloringAttributes != null ? + ((ColoringAttributesRetained)coloringAttributes.retained).mirror: + null), true); + } + + + if (coloringAttributes == null) { + this.coloringAttributes = null; + } else { + this.coloringAttributes = (ColoringAttributesRetained)coloringAttributes.retained; + } + } + } + + /** + * Retrieves the current coloringAttributes object. + * @return the coloringAttributes object + */ + ColoringAttributes getColoringAttributes() { + return (coloringAttributes == null ? null : + (ColoringAttributes)coloringAttributes.source); + } + + /** + * Sets the transparencyAttrbutes object to the specified object. + * @param transparencyAttributes object that specifies the desired texture + * attributes + */ + void setTransparencyAttributes(TransparencyAttributes transparencyAttributes) { + + synchronized(liveStateLock) { + if (source.isLive()) { + + if (this.transparencyAttributes != null) { + this.transparencyAttributes.clearLive(refCount); + this.transparencyAttributes.removeMirrorUsers(this); + } + + if (transparencyAttributes != null) { + ((TransparencyAttributesRetained)transparencyAttributes.retained).setLive(inBackgroundGroup, refCount); + ((TransparencyAttributesRetained)transparencyAttributes.retained).copyMirrorUsers(this); + } + + sendMessage(TRANSPARENCY, + (transparencyAttributes != null ? + ((TransparencyAttributesRetained)transparencyAttributes.retained).mirror: null), true); + } + + + if (transparencyAttributes == null) { + this.transparencyAttributes = null; + } else { + this.transparencyAttributes = (TransparencyAttributesRetained)transparencyAttributes.retained; + + } + } + } + + /** + * Retrieves the current transparencyAttributes object. + * @return the transparencyAttributes object + */ + TransparencyAttributes getTransparencyAttributes() { + return (transparencyAttributes == null ? null : + (TransparencyAttributes)transparencyAttributes.source); + } + + /** + * Sets the renderingAttrbutes object to the specified object. + * @param renderingAttributes object that specifies the desired texture + * attributes + */ + void setRenderingAttributes(RenderingAttributes renderingAttributes) { + + synchronized(liveStateLock) { + if (source.isLive()) { + if (this.renderingAttributes != null) { + this.renderingAttributes.clearLive(refCount); + this.renderingAttributes.removeMirrorUsers(this); + } + + if (renderingAttributes != null) { + ((RenderingAttributesRetained)renderingAttributes.retained).setLive(inBackgroundGroup, refCount); + ((RenderingAttributesRetained)renderingAttributes.retained).copyMirrorUsers(this); + } + Object m = null; + boolean v = true; + if (renderingAttributes != null) { + m = ((RenderingAttributesRetained)renderingAttributes.retained).mirror; + v = ((RenderingAttributesRetained)renderingAttributes.retained).visible; + } + sendMessage(RENDERING,m, v); + // Also need to send a message to GeometryStructure. + sendRenderingAttributesChangedMessage( v); + } + if (renderingAttributes == null) { + this.renderingAttributes = null; + } else { + this.renderingAttributes = (RenderingAttributesRetained)renderingAttributes.retained; + + } + } + } + + /** + * Retrieves the current renderingAttributes object. + * @return the renderingAttributes object + */ + RenderingAttributes getRenderingAttributes() { + if (renderingAttributes == null) + return null; + + return (RenderingAttributes)renderingAttributes.source; + } + + /** + * Sets the polygonAttrbutes object to the specified object. + * @param polygonAttributes object that specifies the desired texture + * attributes + */ + void setPolygonAttributes(PolygonAttributes polygonAttributes) { + + synchronized(liveStateLock) { + if (source.isLive()) { + if (this.polygonAttributes != null) { + this.polygonAttributes.clearLive(refCount); + this.polygonAttributes.removeMirrorUsers(this); + } + + if (polygonAttributes != null) { + ((PolygonAttributesRetained)polygonAttributes.retained).setLive(inBackgroundGroup, refCount); + ((PolygonAttributesRetained)polygonAttributes.retained).copyMirrorUsers(this); + } + sendMessage(POLYGON, + (polygonAttributes != null ? + ((PolygonAttributesRetained)polygonAttributes.retained).mirror : + null), true); + + } + + if (polygonAttributes == null) { + this.polygonAttributes = null; + } else { + this.polygonAttributes = (PolygonAttributesRetained)polygonAttributes.retained; + } + } + } + + /** + * Retrieves the current polygonAttributes object. + * @return the polygonAttributes object + */ + PolygonAttributes getPolygonAttributes() { + return (polygonAttributes == null ? null: + (PolygonAttributes)polygonAttributes.source); + } + + /** + * Sets the lineAttrbutes object to the specified object. + * @param lineAttributes object that specifies the desired texture + * attributes + */ + void setLineAttributes(LineAttributes lineAttributes) { + + synchronized(liveStateLock) { + if (source.isLive()) { + + if (this.lineAttributes != null) { + this.lineAttributes.clearLive(refCount); + this.lineAttributes.removeMirrorUsers(this); + } + + if (lineAttributes != null) { + ((LineAttributesRetained)lineAttributes.retained).setLive(inBackgroundGroup, refCount); + ((LineAttributesRetained)lineAttributes.retained).copyMirrorUsers(this); + } + sendMessage(LINE, + (lineAttributes != null ? + ((LineAttributesRetained)lineAttributes.retained).mirror: null), true); + } + + + if (lineAttributes == null) { + this.lineAttributes = null; + } else { + this.lineAttributes = (LineAttributesRetained)lineAttributes.retained; + } + } + } + + /** + * Retrieves the current lineAttributes object. + * @return the lineAttributes object + */ + LineAttributes getLineAttributes() { + return (lineAttributes == null ? null : + (LineAttributes)lineAttributes.source); + } + + /** + * Sets the pointAttrbutes object to the specified object. + * @param pointAttributes object that specifies the desired texture + * attributes + */ + void setPointAttributes(PointAttributes pointAttributes) { + + synchronized(liveStateLock) { + if (source.isLive()) { + + if (this.pointAttributes != null) { + this.pointAttributes.clearLive(refCount); + this.pointAttributes.removeMirrorUsers(this); + } + if (pointAttributes != null) { + ((PointAttributesRetained)pointAttributes.retained).setLive(inBackgroundGroup, refCount); + ((PointAttributesRetained)pointAttributes.retained).copyMirrorUsers(this); + } + sendMessage(POINT, + (pointAttributes != null ? + ((PointAttributesRetained)pointAttributes.retained).mirror: + null), true); + } + + + if (pointAttributes == null) { + this.pointAttributes = null; + } else { + this.pointAttributes = (PointAttributesRetained)pointAttributes.retained; + } + } + } + + /** + * Retrieves the current pointAttributes object. + * @return the pointAttributes object + */ + PointAttributes getPointAttributes() { + return (pointAttributes == null? null : (PointAttributes)pointAttributes.source); + } + + /** + * Sets the texCoordGeneration object to the specified object. + * @param texCoordGeneration object that specifies the texture coordinate + * generation parameters + */ + void setTexCoordGeneration(TexCoordGeneration texGen) { + + synchronized(liveStateLock) { + if (source.isLive()) { + + if (this.texCoordGeneration != null) { + this.texCoordGeneration.clearLive(refCount); + this.texCoordGeneration.removeMirrorUsers(this); + } + + if (texGen != null) { + ((TexCoordGenerationRetained)texGen.retained).setLive(inBackgroundGroup, refCount); + ((TexCoordGenerationRetained)texGen.retained).copyMirrorUsers(this); + } + sendMessage(TEXCOORD_GEN, + (texGen != null ? + ((TexCoordGenerationRetained)texGen.retained).mirror : null), true); + } + + if (texGen == null) { + this.texCoordGeneration = null; + } else { + this.texCoordGeneration = (TexCoordGenerationRetained)texGen.retained; + } + } + } + + /** + * Retrieves the current texCoordGeneration object. + * @return the texCoordGeneration object + */ + TexCoordGeneration getTexCoordGeneration() { + return (texCoordGeneration == null ? null : + (TexCoordGeneration)texCoordGeneration.source); + } + + + /** + * Sets the texture unit state array to the specified array. + * @param textureUnitState array that specifies the texture unit state + */ + void setTextureUnitState(TextureUnitState[] stateArray) { + + int i; + + synchronized(liveStateLock) { + if (source.isLive()) { + + // remove the existing texture unit states from this appearance + if (this.texUnitState != null) { + for (i = 0; i < this.texUnitState.length; i++) { + if (this.texUnitState[i] != null) { + this.texUnitState[i].clearLive(refCount); + this.texUnitState[i].removeMirrorUsers(this); + } + } + } + + // add the specified texture unit states to this appearance + // also make a copy of the array of references to the units + if (stateArray != null) { + + Object [] args = new Object[2]; + + // -1 index means the entire array is to be set + args[0] = new Integer(-1); + + // make a copy of the array for the message, + TextureUnitStateRetained mirrorStateArray[] = + new TextureUnitStateRetained[stateArray.length]; + + args[1] = (Object) mirrorStateArray; + + for (i = 0; i < stateArray.length; i++) { + TextureUnitState tu = stateArray[i]; + if (tu != null) { + ((TextureUnitStateRetained)tu.retained).setLive( + inBackgroundGroup, refCount); + ((TextureUnitStateRetained)tu.retained).copyMirrorUsers( + this); + mirrorStateArray[i] = (TextureUnitStateRetained) + ((TextureUnitStateRetained)tu.retained).mirror; + } else { + mirrorStateArray[i] = null; + } + } + sendMessage(TEXTURE_UNIT_STATE, args, true); + + } else { + sendMessage(TEXTURE_UNIT_STATE, null, true); + } + } + + // assign the retained copy of the texture unit state to the + // appearance + if (stateArray == null) { + this.texUnitState = null; + } else { + + // make another copy of the array for the retained object + // itself if it doesn't have a copy or the array size is + // not the same + if ((this.texUnitState == null) || + (this.texUnitState.length != stateArray.length)) { + this.texUnitState = new TextureUnitStateRetained[ + stateArray.length]; + } + for (i = 0; i < stateArray.length; i++) { + if (stateArray[i] != null) { + this.texUnitState[i] = + (TextureUnitStateRetained)stateArray[i].retained; + } else { + this.texUnitState[i] = null; + } + } + } + } + } + + void setTextureUnitState(int index, TextureUnitState state) { + + synchronized(liveStateLock) { + if (source.isLive()) { + + // remove the existing texture unit states from this appearance + // Note: Let Java throw an exception if texUnitState is null + // or index is >= texUnitState.length. + if (this.texUnitState[index] != null) { + this.texUnitState[index].clearLive(refCount); + this.texUnitState[index].removeMirrorUsers(this); + } + + // add the specified texture unit states to this appearance + // also make a copy of the array of references to the units + Object args[] = new Object[2]; + args[0] = new Integer(index); + + if (state != null) { + ((TextureUnitStateRetained)state.retained).setLive( + inBackgroundGroup, refCount); + ((TextureUnitStateRetained)state.retained).copyMirrorUsers(this); + args[1] = ((TextureUnitStateRetained)state.retained).mirror; + sendMessage(TEXTURE_UNIT_STATE, args, true); + } else { + args[1] = null; + sendMessage(TEXTURE_UNIT_STATE, args, true); + } + } + + // assign the retained copy of the texture unit state to the + // appearance + if (state != null) { + this.texUnitState[index] = (TextureUnitStateRetained)state.retained; + } else { + this.texUnitState[index] = null; + } + } + } + + + + /** + * Retrieves the array of texture unit state objects from this + * Appearance object. A shallow copy of the array of references to + * the TextureUnitState objects is returned. + * + */ + TextureUnitState[] getTextureUnitState() { + if (texUnitState == null) { + return null; + } else { + TextureUnitState tus[] = + new TextureUnitState[texUnitState.length]; + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null) { + tus[i] = (TextureUnitState) texUnitState[i].source; + } else { + tus[i] = null; + } + } + return tus; + } + } + + /** + * Retrieves the texture unit state object at the specified + * index within the texture unit state array. + */ + TextureUnitState getTextureUnitState(int index) { + + // let Java throw an exception if texUnitState == null or + // index is >= length + if (texUnitState[index] != null) + return (TextureUnitState)texUnitState[index].source; + else + return null; + } + + + /** + * Retrieves the length of the texture unit state array from + * this appearance object. The length of this array specifies the + * maximum number of texture units that will be used by this + * appearance object. If the array is null, a count of 0 is + * returned. + */ + + int getTextureUnitCount() { + if (texUnitState == null) + return 0; + else + return texUnitState.length; + } + + + synchronized void createMirrorObject() { + if (mirror == null) { + // we can't check isStatic() since it sub-NodeComponent + // create a new one, we should create a + // new AppearanceRetained() even though isStatic() = true. + // For simplicity, always create a retained side. + mirror = new AppearanceRetained(); + } + initMirrorObject(); + } + + /** + * This routine updates the mirror appearance for this appearance. + * It also calls the update method for each node component if it + * is not null. + */ + synchronized void initMirrorObject() { + + AppearanceRetained mirrorApp = (AppearanceRetained)mirror; + + mirrorApp.source = source; + mirrorApp.sgApp = this; + if (material != null) { + mirrorApp.material = (MaterialRetained)material.mirror; + } else { + mirrorApp.material = null; + } + + if (texture != null) { + mirrorApp.texture = (TextureRetained)texture.mirror; + } else { + mirrorApp.texture = null; + } + if (texCoordGeneration != null) { + mirrorApp.texCoordGeneration = (TexCoordGenerationRetained)texCoordGeneration.mirror; + } else { + mirrorApp.texCoordGeneration = null; + } + + if (textureAttributes != null) { + mirrorApp.textureAttributes = (TextureAttributesRetained)textureAttributes.mirror; + } else { + mirrorApp.textureAttributes = null; + } + + // TextureUnitState supercedes the single texture interface + if (texUnitState != null && texUnitState.length > 0) { + mirrorApp.texUnitState = + new TextureUnitStateRetained[texUnitState.length]; + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null) { + mirrorApp.texUnitState[i] = + (TextureUnitStateRetained)texUnitState[i].mirror; + } + } + } else if (mirrorApp.texture != null || + mirrorApp.textureAttributes != null || + mirrorApp.texCoordGeneration != null) { + + mirrorApp.texUnitState = new TextureUnitStateRetained[1]; + mirrorApp.texUnitState[0] = new TextureUnitStateRetained(); + mirrorApp.texUnitState[0].set( + mirrorApp.texture, + mirrorApp.textureAttributes, + mirrorApp.texCoordGeneration); + } + + if (coloringAttributes != null) { + mirrorApp.coloringAttributes = (ColoringAttributesRetained)coloringAttributes.mirror; + } else { + mirrorApp.coloringAttributes = null; + } + if (transparencyAttributes != null) { + mirrorApp.transparencyAttributes = (TransparencyAttributesRetained)transparencyAttributes.mirror; + } else { + mirrorApp.transparencyAttributes = null; + } + + if (renderingAttributes != null) { + mirrorApp.renderingAttributes = (RenderingAttributesRetained)renderingAttributes.mirror; + } else { + mirrorApp.renderingAttributes = null; + } + + if (polygonAttributes != null) { + mirrorApp.polygonAttributes = (PolygonAttributesRetained)polygonAttributes.mirror; + } else { + mirrorApp.polygonAttributes = null; + } + + if (lineAttributes != null) { + mirrorApp.lineAttributes = (LineAttributesRetained)lineAttributes.mirror; + } else { + mirrorApp.lineAttributes = null; + } + + if (pointAttributes != null) { + mirrorApp.pointAttributes = (PointAttributesRetained)pointAttributes.mirror; + } else { + mirrorApp.pointAttributes = null; + } + } + + /** + * Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + AppearanceRetained mirrorApp = (AppearanceRetained)mirror; + if ((component & MATERIAL) != 0) { + mirrorApp.material = (MaterialRetained)value; + } + else if ((component & TEXTURE) != 0) { + if (mirrorApp.texUnitState == null) { + mirrorApp.texUnitState = new TextureUnitStateRetained[1]; + mirrorApp.texUnitState[0] = new TextureUnitStateRetained(); + } + mirrorApp.texUnitState[0].texture = (TextureRetained)value; + } + else if ((component & TEXCOORD_GEN) != 0) { + if (mirrorApp.texUnitState == null) { + mirrorApp.texUnitState = new TextureUnitStateRetained[1]; + mirrorApp.texUnitState[0] = new TextureUnitStateRetained(); + } + mirrorApp.texUnitState[0].texGen = (TexCoordGenerationRetained)value; + } + else if ((component & TEXTURE_ATTR) != 0) { + if (mirrorApp.texUnitState == null) { + mirrorApp.texUnitState = new TextureUnitStateRetained[1]; + mirrorApp.texUnitState[0] = new TextureUnitStateRetained(); + } + mirrorApp.texUnitState[0].texAttrs = (TextureAttributesRetained)value; + } + else if ((component & TEXTURE_UNIT_STATE) != 0) { + Object [] args = (Object [])value; + + if (args == null) { + mirrorApp.texUnitState = null; + } else { + int index = ((Integer)args[0]).intValue(); + if (index == -1) { + mirrorApp.texUnitState = + (TextureUnitStateRetained [])args[1]; + } else { + mirrorApp.texUnitState[index] = + (TextureUnitStateRetained)args[1]; + } + } + } + else if ((component & COLOR) != 0) { + mirrorApp.coloringAttributes = (ColoringAttributesRetained)value; + } + else if ((component & TRANSPARENCY) != 0) { + mirrorApp.transparencyAttributes = (TransparencyAttributesRetained)value; + } + else if ((component & RENDERING) != 0) { + mirrorApp.renderingAttributes = (RenderingAttributesRetained)value; + } + else if ((component & POLYGON) != 0) { + mirrorApp.polygonAttributes = (PolygonAttributesRetained)value; + } + else if ((component & LINE) != 0) { + mirrorApp.lineAttributes = (LineAttributesRetained)value; + } + else if ((component & POINT) != 0) { + mirrorApp.pointAttributes = (PointAttributesRetained)value; + } + + } + + /** + * This setLive routine first calls the superclass's method, then + * it adds itself to the list of lights + */ + void setLive(boolean backgroundGroup, int refCount) { + + if (material != null) { + + material.setLive(backgroundGroup, refCount); + } + + if (texture != null) { + + texture.setLive(backgroundGroup, refCount); + } + + if (texCoordGeneration != null) { + + texCoordGeneration.setLive(backgroundGroup, refCount); + } + + if (textureAttributes != null) { + + textureAttributes.setLive(backgroundGroup, refCount); + } + + if (texUnitState != null) { + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null) + texUnitState[i].setLive(backgroundGroup, refCount); + } + } + + + if (coloringAttributes != null) { + coloringAttributes.setLive(backgroundGroup, refCount); + } + + if (transparencyAttributes != null) { + transparencyAttributes.setLive(backgroundGroup, refCount); + } + + if (renderingAttributes != null) { + renderingAttributes.setLive(backgroundGroup, refCount); + } + + if (polygonAttributes != null) { + polygonAttributes.setLive(backgroundGroup, refCount); + } + + if (lineAttributes != null) { + lineAttributes.setLive(backgroundGroup, refCount); + } + + if (pointAttributes != null) { + pointAttributes.setLive(backgroundGroup, refCount); + } + + + // Increment the reference count and initialize the appearance + // mirror object + super.doSetLive(backgroundGroup, refCount); + super.markAsLive(); + } + + /** + * This clearLive routine first calls the superclass's method, then + * it removes itself to the list of lights + */ + void clearLive(int refCount) { + super.clearLive(refCount); + + if (texture != null) { + texture.clearLive(refCount); + } + + if (texCoordGeneration != null) { + texCoordGeneration.clearLive(refCount); + } + + if (textureAttributes != null) { + textureAttributes.clearLive(refCount); + } + + if (texUnitState != null) { + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null) + texUnitState[i].clearLive(refCount); + } + } + + if (coloringAttributes != null) { + coloringAttributes.clearLive(refCount); + } + + if (transparencyAttributes != null) { + transparencyAttributes.clearLive(refCount); + } + + if (renderingAttributes != null) { + renderingAttributes.clearLive(refCount); + } + + if (polygonAttributes != null) { + polygonAttributes.clearLive(refCount); + } + + if (lineAttributes != null) { + lineAttributes.clearLive(refCount); + } + + if (pointAttributes != null) { + pointAttributes.clearLive(refCount); + } + + if (material != null) { + material.clearLive(refCount); + } + } + + + boolean isStatic() { + boolean flag; + + flag = (source.capabilityBitsEmpty() && + ((texture == null) || + texture.source.capabilityBitsEmpty()) && + ((texCoordGeneration == null) || + texCoordGeneration.source.capabilityBitsEmpty()) && + ((textureAttributes == null) || + textureAttributes.source.capabilityBitsEmpty()) && + ((coloringAttributes == null) || + coloringAttributes.source.capabilityBitsEmpty()) && + ((transparencyAttributes == null) || + transparencyAttributes.source.capabilityBitsEmpty()) && + ((renderingAttributes == null) || + renderingAttributes.source.capabilityBitsEmpty()) && + ((polygonAttributes == null) || + polygonAttributes.source.capabilityBitsEmpty()) && + ((lineAttributes == null) || + lineAttributes.source.capabilityBitsEmpty()) && + ((pointAttributes == null) || + pointAttributes.source.capabilityBitsEmpty()) && + ((material == null) || + material.source.capabilityBitsEmpty())); + + if (!flag) + return flag; + + if (texUnitState != null) { + for (int i = 0; i < texUnitState.length && flag; i++) { + if (texUnitState[i] != null) { + flag = flag && texUnitState[i].isStatic(); + } + } + } + + return flag; + } + /* + // Simply pass along to the NodeComponents + void compile(CompileState compState) { + setCompiled(); + + if (texture != null) { + texture.compile(compState); + } + + if (texCoordGeneration != null) { + texCoordGeneration.compile(compState); + } + + if (textureAttributes != null) { + textureAttributes.compile(compState); + } + + if (texUnitState != null) { + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null) + texUnitState[i].compile(compState); + } + } + + if (coloringAttributes != null) { + coloringAttributes.compile(compState); + } + + if (transparencyAttributes != null) { + transparencyAttributes.compile(compState); + } + + if (renderingAttributes != null) { + renderingAttributes.compile(compState); + } + + if (polygonAttributes != null) { + polygonAttributes.compile(compState); + } + + if (lineAttributes != null) { + lineAttributes.compile(compState); + } + + if (pointAttributes != null) { + pointAttributes.compile(compState); + } + + if (material != null) { + material.compile(compState); + } + } + */ + + /** + * Returns the hashcode for this object. + * hashcode should be constant for object but same for two objects + * if .equals() is true. For an appearance (where .equals() is going + * to use the values in the appearance), the only way to have a + * constant value is for all appearances to have the same hashcode, so + * we use the hashcode of the class obj. + * + * Since hashCode is only used by AppearanceMap (at present) we may be + * able to improve efficency by calcing a hashCode from the values. + */ + public int hashCode() { + return getClass().hashCode(); + } + + public boolean equals(Object obj) { + return ((obj instanceof AppearanceRetained) && + equals((AppearanceRetained) obj)); + } + + boolean equals(AppearanceRetained app) { + boolean flag; + + flag = (app == this) || + ((app != null) && + (((material == app.material) || + ((material != null) && material.equivalent(app.material))) && + ((texture == app.texture) || + ((texture != null) && texture.equals(app.texture))) && + ((renderingAttributes == app.renderingAttributes) || + ((renderingAttributes != null) && + renderingAttributes.equivalent( + app.renderingAttributes))) && + ((polygonAttributes == app.polygonAttributes) || + ((polygonAttributes != null) && + polygonAttributes.equivalent(app.polygonAttributes))) && + ((texCoordGeneration == app.texCoordGeneration) || + ((texCoordGeneration != null) && + texCoordGeneration.equivalent(app.texCoordGeneration))) && + ((textureAttributes == app.textureAttributes) || + ((textureAttributes != null) && + textureAttributes.equivalent(app.textureAttributes))) && + ((coloringAttributes == app.coloringAttributes) || + ((coloringAttributes != null) && + coloringAttributes.equivalent(app.coloringAttributes))) && + ((transparencyAttributes == app.transparencyAttributes) || + ((transparencyAttributes != null) && + transparencyAttributes.equivalent( + app.transparencyAttributes))) && + ((lineAttributes == app.lineAttributes) || + ((lineAttributes != null) && + lineAttributes.equivalent(app.lineAttributes))) && + ((pointAttributes == app.pointAttributes) || + ((pointAttributes != null) && + pointAttributes.equivalent(app.pointAttributes))))); + + if (!flag) + return (flag); + + if (texUnitState == app.texUnitState) + return (flag); + + if (texUnitState == null || app.texUnitState == null || + texUnitState.length != app.texUnitState.length) + return (false); + + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] == app.texUnitState[i]) + continue; + + if (texUnitState[i] == null || app.texUnitState[i] == null || + !texUnitState[i].equals(app.texUnitState[i])) + return (false); + } + return (true); + } + + + + + synchronized void addAMirrorUser(Shape3DRetained shape) { + + super.addAMirrorUser(shape); + if (material != null) + material.addAMirrorUser(shape); + + if (texture != null) + texture.addAMirrorUser(shape); + if (texCoordGeneration != null) + texCoordGeneration.addAMirrorUser(shape); + if (textureAttributes != null) + textureAttributes.addAMirrorUser(shape); + + if (texUnitState != null) { + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null) + texUnitState[i].addAMirrorUser(shape); + } + } + + if (coloringAttributes != null) + coloringAttributes.addAMirrorUser(shape); + if (transparencyAttributes != null) + transparencyAttributes.addAMirrorUser(shape); + if (renderingAttributes != null) + renderingAttributes.addAMirrorUser(shape); + if (polygonAttributes != null) + polygonAttributes.addAMirrorUser(shape); + if (lineAttributes != null) + lineAttributes.addAMirrorUser(shape); + if (pointAttributes != null) + pointAttributes.addAMirrorUser(shape); + } + + synchronized void removeAMirrorUser(Shape3DRetained shape) { + super.removeAMirrorUser(shape); + if (material != null) + material.removeAMirrorUser(shape); + if (texture != null) + texture.removeAMirrorUser(shape); + if (texCoordGeneration != null) + texCoordGeneration.removeAMirrorUser(shape); + if (textureAttributes != null) + textureAttributes.removeAMirrorUser(shape); + + if (texUnitState != null) { + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null) + texUnitState[i].removeAMirrorUser(shape); + } + } + + if (coloringAttributes != null) + coloringAttributes.removeAMirrorUser(shape); + if (transparencyAttributes != null) + transparencyAttributes.removeAMirrorUser(shape); + if (renderingAttributes != null) + renderingAttributes.removeAMirrorUser(shape); + if (polygonAttributes != null) + polygonAttributes.removeAMirrorUser(shape); + if (lineAttributes != null) + lineAttributes.removeAMirrorUser(shape); + if (pointAttributes != null) + pointAttributes.removeAMirrorUser(shape); + } + + // 3rd argument used only when Rendering Attr comp changes + final void sendMessage(int attrMask, Object attr, boolean visible) { + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.APPEARANCE_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + + VirtualUniverse.mc.processMessage(createMessage); + + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.APPEARANCE_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + // Send the value itself, since Geometry Structure cannot rely on the + // mirror (which may be updated lazily) + if (attrMask == RENDERING) { + if (attr != null) { + createMessage.args[4] = visible?Boolean.TRUE:Boolean.FALSE; + } + else { + createMessage.args[4] = Boolean.TRUE; + } + } + VirtualUniverse.mc.processMessage(createMessage); + } + } + + + + final void sendRenderingAttributesChangedMessage(boolean visible) { + + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_GEOMETRY; + createMessage.type = J3dMessage.RENDERINGATTRIBUTES_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1] = null; // Sync with RenderingAttrRetained sendMessage + createMessage.args[2]= visible?Boolean.TRUE:Boolean.FALSE; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + VirtualUniverse.mc.processMessage(createMessage); + } + } + + boolean isOpaque(int geoType) { + TransparencyAttributesRetained ta; + int i; + + ta = transparencyAttributes; + if (ta != null && + ta.transparencyMode != TransparencyAttributes.NONE && + (VirtualUniverse.mc.isD3D() || + (!VirtualUniverse.mc.isD3D() && + (ta.transparencyMode != + TransparencyAttributes.SCREEN_DOOR)))) { + return(false); + } + + switch (geoType) { + case GeometryRetained.GEO_TYPE_POINT_SET: + case GeometryRetained.GEO_TYPE_INDEXED_POINT_SET: + if ((pointAttributes != null) && + pointAttributes.pointAntialiasing) { + return (false); + } + break; + case GeometryRetained.GEO_TYPE_LINE_SET: + case GeometryRetained.GEO_TYPE_LINE_STRIP_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_STRIP_SET: + if ((lineAttributes != null) && + lineAttributes.lineAntialiasing) { + return (false); + } + break; + case GeometryRetained.GEO_TYPE_RASTER: + case GeometryRetained.GEO_TYPE_COMPRESSED: + break; + default: + if (polygonAttributes != null) { + if((polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_POINT) && + (pointAttributes != null) && + pointAttributes.pointAntialiasing) { + return (false); + } else if ((polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_LINE) && + (lineAttributes != null) && + lineAttributes.lineAntialiasing) { + return (false); + } + } + break; + } + + return(true); + } + + void handleFrequencyChange(int bit) { + int mask = 0; + if (bit == Appearance.ALLOW_COLORING_ATTRIBUTES_WRITE) + mask = COLOR; + else if(bit == Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE) + mask = TRANSPARENCY; + else if(bit == Appearance.ALLOW_RENDERING_ATTRIBUTES_WRITE) + mask = RENDERING; + else if (bit == Appearance.ALLOW_POLYGON_ATTRIBUTES_WRITE) + mask = POLYGON; + else if (bit == Appearance.ALLOW_LINE_ATTRIBUTES_WRITE) + mask = LINE; + else if (bit == Appearance.ALLOW_POINT_ATTRIBUTES_WRITE) + mask = POINT; + else if (bit == Appearance.ALLOW_MATERIAL_WRITE) + mask = MATERIAL; + else if (bit == Appearance.ALLOW_TEXTURE_WRITE) + mask = TEXTURE; + else if (bit == Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE) + mask = TEXTURE_ATTR; + else if (bit == Appearance.ALLOW_TEXGEN_WRITE) + mask = TEXCOORD_GEN; + else if (bit == Appearance.ALLOW_TEXTURE_UNIT_STATE_WRITE) + mask = TEXTURE_UNIT_STATE; + + if (mask != 0) + setFrequencyChangeMask(bit, mask); + } +} + + diff --git a/src/classes/share/javax/media/j3d/AssertionFailureException.java b/src/classes/share/javax/media/j3d/AssertionFailureException.java new file mode 100644 index 0000000..8d8cbda --- /dev/null +++ b/src/classes/share/javax/media/j3d/AssertionFailureException.java @@ -0,0 +1,35 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates an assertion failure. + */ + +class AssertionFailureException extends RuntimeException { + + /** + * Create the exception object with default values. + */ + AssertionFailureException() { + } + + /** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + AssertionFailureException(String str) { + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/AttributeBin.java b/src/classes/share/javax/media/j3d/AttributeBin.java new file mode 100644 index 0000000..c686d27 --- /dev/null +++ b/src/classes/share/javax/media/j3d/AttributeBin.java @@ -0,0 +1,475 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; + +/** + * The AttributeBin manages a collection of TextureBin objects. + * All objects in the AttributeBin share the same RenderingAttributes + */ + +class AttributeBin extends Object implements ObjectUpdate { + + /** + * The RenderingAttributes for this AttributeBin + */ + RenderingAttributesRetained definingRenderingAttributes = null; + + /** + * The EnvirionmentSet that this AttributeBin resides + */ + EnvironmentSet environmentSet = null; + + /** + * The references to the next and previous AttributeBins in the + * list. + */ + AttributeBin next = null; + AttributeBin prev = null; + + /** + * The list of TextureBins in this AttributeBin + */ + TextureBin textureBinList = null; + + + /** + * The list of TextureBins to be added for the next frame + */ + ArrayList addTextureBins = new ArrayList(); + + /** + * List of TextureBins to be added next frame + */ + ArrayList addTBs = new ArrayList(); + + + /** + * If the RenderingAttribute component of the appearance will be changed + * frequently, then confine it to a separate bin + */ + boolean soleUser = false; + AppearanceRetained app = null; + + /** + * List of TextureBins to be removeded next frame + */ + ArrayList removeTBs = new ArrayList(); + + int onUpdateList = 0; + static int ON_OBJ_UPDATE_LIST = 0x1; + static int ON_CHANGED_FREQUENT_UPDATE_LIST = 0x2; + + // Cache it outside, to avoid the "if" check in renderMethod + // for whether the definingRendering attrs is non-null; + boolean ignoreVertexColors = false; + + // TODO: use definingMaterial etc. instead of these + // when sole user is completely implement + RenderingAttributesRetained renderingAttrs; + + int numEditingTextureBins = 0; + + + + AttributeBin(AppearanceRetained app, RenderingAttributesRetained renderingAttributes, RenderBin rBin) { + reset(app, renderingAttributes, rBin); + } + + void reset(AppearanceRetained app, RenderingAttributesRetained renderingAttributes, RenderBin rBin) { + prev = null; + next = null; + textureBinList = null; + onUpdateList = 0; + numEditingTextureBins = 0; + renderingAttrs = renderingAttributes; + + if (app != null) { + soleUser = ((app.changedFrequent & AppearanceRetained.RENDERING) != 0); + } + else { + soleUser = false; + } + // System.out.println("soleUser = "+soleUser+" renderingAttributes ="+renderingAttributes); + // Set the appearance only for soleUser case + if (soleUser) + this.app = app; + else + app = null; + + if (renderingAttributes != null) { + if (renderingAttributes.changedFrequent != 0) { + definingRenderingAttributes = renderingAttributes; + if ((onUpdateList & ON_CHANGED_FREQUENT_UPDATE_LIST) == 0 ) { + rBin.aBinUpdateList.add(this); + onUpdateList |= AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST; + } + } + else { + if (definingRenderingAttributes != null) { + definingRenderingAttributes.set(renderingAttributes); + } + else { + definingRenderingAttributes = (RenderingAttributesRetained)renderingAttributes.clone(); + } + } + ignoreVertexColors = definingRenderingAttributes.ignoreVertexColors; + } else { + definingRenderingAttributes = null; + ignoreVertexColors = false; + } + } + + + /** + * This tests if the given attributes match this AttributeBin + */ + boolean equals(RenderingAttributesRetained renderingAttributes, RenderAtom ra) { + + // If the any reference to the appearance components that is cached renderMolecule + // can change frequently, make a separate bin + // If the any reference to the appearance components that is cached renderMolecule + // can change frequently, make a separate bin + if (soleUser || (ra.geometryAtom.source.appearance != null && + ((ra.geometryAtom.source.appearance.changedFrequent & AppearanceRetained.RENDERING) != 0))) { + if (app == (Object)ra.geometryAtom.source.appearance) { + + // if this AttributeBin is currently on a zombie state, + // we'll need to put it on the update list to reevaluate + // the state, because while it is on a zombie state, + // rendering attributes reference could have been changed. + // Example, application could have detached an appearance, + // made changes to the reference, and then + // reattached the appearance. In this case, the rendering + // attributes reference change would not have reflected to + // the AttributeBin + + if (numEditingTextureBins == 0) { + if ((onUpdateList & ON_CHANGED_FREQUENT_UPDATE_LIST) == 0) { + environmentSet.renderBin.aBinUpdateList.add(this); + onUpdateList |= + AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST; + } + } + return true; + } + else { + return false; + } + + } + // Either a changedFrequent or a null case + // and the incoming one is not equal or null + // then return; + // This check also handles null == null case + if (definingRenderingAttributes != null) { + if ((this.definingRenderingAttributes.changedFrequent != 0) || + (renderingAttributes !=null && renderingAttributes.changedFrequent != 0)) + if (definingRenderingAttributes == renderingAttributes) { + if (definingRenderingAttributes.compChanged != 0) { + if ((onUpdateList & ON_CHANGED_FREQUENT_UPDATE_LIST) == 0 ) { + environmentSet.renderBin.aBinUpdateList.add(this); + onUpdateList |= AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST; + } + } + } + else { + return false; + } + else if (!definingRenderingAttributes.equivalent(renderingAttributes)) { + return false; + } + } + else if (renderingAttributes != null) { + return false; + } + + + + return (true); + } + + + public void updateObject() { + TextureBin t; + int i; + + if (addTBs.size() > 0) { + t = (TextureBin)addTBs.get(0); + if (textureBinList == null) { + textureBinList = t; + + } + else { + // Look for a TextureBin that has the same texture + insertTextureBin(t); + } + for (i = 1; i < addTBs.size() ; i++) { + t = (TextureBin)addTBs.get(i); + // Look for a TextureBin that has the same texture + insertTextureBin(t); + + } + } + addTBs.clear(); + onUpdateList &= ~ON_OBJ_UPDATE_LIST; + } + + void insertTextureBin(TextureBin t) { + TextureBin tb; + int i; + TextureRetained texture = null; + + if (t.texUnitState != null && t.texUnitState.length > 0) { + if (t.texUnitState[0] != null) { + texture = t.texUnitState[0].texture; + } + } + + // use the texture in the first texture unit as the sorting criteria + if (texture != null) { + tb = textureBinList; + while (tb != null) { + if (tb.texUnitState == null || tb.texUnitState[0] == null || + tb.texUnitState[0].texture != texture) { + tb = tb.next; + } else { + // put it here + t.next = tb; + t.prev = tb.prev; + if (tb.prev == null) { + textureBinList = t; + } + else { + tb.prev.next = t; + } + tb.prev = t; + return; + } + } + } + // Just put it up front + t.prev = null; + t.next = textureBinList; + textureBinList.prev = t; + textureBinList = t; + + t.tbFlag &= ~TextureBin.RESORT; + } + + + /** + * reInsert textureBin if the first texture is different from + * the previous bin and different from the next bin + */ + void reInsertTextureBin(TextureBin tb) { + + TextureRetained texture = null, + prevTexture = null, + nextTexture = null; + + if (tb.texUnitState != null && tb.texUnitState[0] != null) { + texture = tb.texUnitState[0].texture; + } + + if (tb.prev != null && tb.prev.texUnitState != null) { + prevTexture = tb.prev.texUnitState[0].texture; + } + + if (texture != prevTexture) { + if (tb.next != null && tb.next.texUnitState != null) { + nextTexture = tb.next.texUnitState[0].texture; + } + if (texture != nextTexture) { + if (tb.prev != null && tb.next != null) { + tb.prev.next = tb.next; + tb.next.prev = tb.prev; + insertTextureBin(tb); + } + } + } + } + + + /** + * Adds the given TextureBin to this AttributeBin. + */ + void addTextureBin(TextureBin t, RenderBin rb, RenderAtom ra) { + int i; + t.attributeBin = this; + AppearanceRetained raApp = ra.geometryAtom.source.appearance; + RenderingAttributesRetained rAttrs = + (raApp == null)? null : raApp.renderingAttributes; + if (!soleUser && renderingAttrs != rAttrs) { + // no longer sole user + renderingAttrs = definingRenderingAttributes; + } + addTBs.add(t); + if ((onUpdateList & ON_OBJ_UPDATE_LIST) == 0) { + onUpdateList |= ON_OBJ_UPDATE_LIST; + rb.objUpdateList.add(this); + } + + } + + /** + * Removes the given TextureBin from this AttributeBin. + */ + void removeTextureBin(TextureBin t) { + + int i; + TextureRetained tex; + + t.attributeBin = null; + // If the TextureBin being remove is contained in addTBs, then + // remove the TextureBin from the addList + if (addTBs.contains(t)) { + addTBs.remove(addTBs.indexOf(t)); + } + else { + if (t.prev == null) { // At the head of the list + textureBinList = t.next; + if (t.next != null) { + t.next.prev = null; + } + } else { // In the middle or at the end. + t.prev.next = t.next; + if (t.next != null) { + t.next.prev = t.prev; + } + } + } + t.prev = null; + t.next = null; + + t.clear(); + + environmentSet.renderBin.textureBinFreelist.add(t); + + if (textureBinList == null && addTBs.size() == 0 ) { + // Note: Removal of this attributebin as a user of the rendering + // atttrs is done during removeRenderAtom() in RenderMolecule.java + environmentSet.removeAttributeBin(this); + } + } + + /** + * Renders this AttributeBin + */ + void render(Canvas3D cv) { + + TextureBin t; + + boolean visible = (definingRenderingAttributes == null || + definingRenderingAttributes.visible); + + if ( (environmentSet.renderBin.view.viewCache.visibilityPolicy + == View.VISIBILITY_DRAW_VISIBLE && !visible) || + (environmentSet.renderBin.view.viewCache.visibilityPolicy + == View.VISIBILITY_DRAW_INVISIBLE && visible)) { + return; + } + + + // include this AttributeBin to the to-be-updated list in Canvas + cv.setStateToUpdate(Canvas3D.ATTRIBUTEBIN_BIT, this); + + t = textureBinList; + while (t != null) { + t.render(cv); + t = t.next; + } + } + + + void updateAttributes(Canvas3D cv) { + if ((cv.canvasDirty & Canvas3D.ATTRIBUTEBIN_DIRTY) != 0) { + // Update Attribute Bundles + if (definingRenderingAttributes == null) { + cv.resetRenderingAttributes(cv.ctx, + cv.depthBufferWriteEnableOverride, + cv.depthBufferEnableOverride); + } else { + definingRenderingAttributes.updateNative( + cv.ctx, + cv.depthBufferWriteEnableOverride, + cv.depthBufferEnableOverride); + } + cv.renderingAttrs = renderingAttrs; + } + + else if (cv.renderingAttrs != renderingAttrs && + cv.attributeBin != this) { + // Update Attribute Bundles + if (definingRenderingAttributes == null) { + cv.resetRenderingAttributes( + cv.ctx, + cv.depthBufferWriteEnableOverride, + cv.depthBufferEnableOverride); + } else { + definingRenderingAttributes.updateNative( + cv.ctx, + cv.depthBufferWriteEnableOverride, + cv.depthBufferEnableOverride); + } + cv.renderingAttrs = renderingAttrs; + } + cv.attributeBin = this; + cv.canvasDirty &= ~Canvas3D.ATTRIBUTEBIN_DIRTY; + } + + void updateNodeComponent() { + // May be in the freelist already (due to freq bit changing) + // if so, don't update anything + if ((onUpdateList & ON_CHANGED_FREQUENT_UPDATE_LIST) != 0) { + if (soleUser) { + boolean cloned = definingRenderingAttributes != null && definingRenderingAttributes != renderingAttrs; + renderingAttrs = app.renderingAttributes; + + if (renderingAttrs == null) { + definingRenderingAttributes = null; + ignoreVertexColors = false; + } + else { + if (renderingAttrs.changedFrequent != 0) { + definingRenderingAttributes = renderingAttrs; + } + else { + if (cloned) { + definingRenderingAttributes.set(renderingAttrs); + } + else { + definingRenderingAttributes = (RenderingAttributesRetained)renderingAttrs.clone(); + } + } + ignoreVertexColors = definingRenderingAttributes.ignoreVertexColors; + } + } + else { + ignoreVertexColors = definingRenderingAttributes.ignoreVertexColors; + } + } + + onUpdateList &= ~ON_CHANGED_FREQUENT_UPDATE_LIST; + } + + void incrActiveTextureBin() { + numEditingTextureBins++; + } + + void decrActiveTextureBin() { + numEditingTextureBins--; + } +} diff --git a/src/classes/share/javax/media/j3d/AudioDevice.java b/src/classes/share/javax/media/j3d/AudioDevice.java new file mode 100644 index 0000000..314c7de --- /dev/null +++ b/src/classes/share/javax/media/j3d/AudioDevice.java @@ -0,0 +1,257 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The AudioDevice Class defines and encapsulates the + * audio device's basic information and characteristics. + * <P> + * A Java3D application running on a particular machine could have one of + * several options available to it for playing the audio image created by the + * sound renderer. Perhaps the machine Java3D is executing on has more than + * one sound card (e.g., one that is a Wave Table Synthesis card and the other + * with accelerated sound spatialization hardware). Furthermore, suppose there + * are Java3D audio device drivers that execute Java3D audio methods on each of + * these specific cards. In such a case the application would have at least two + * audio device drivers through which the audio could be produced. For such a + * case the Java3D application must choose the audio device driver with which + * sound rendering is to be performed. Once this audio device is chosen, the + * application can additionally select the type of audio playback type the + * rendered sound image is to be output on. The playback device (headphones or + * speaker(s)) is physically connected to the port the selected device driver + * outputs to. + *<P> + * AudioDevice Interface + *<P> + *<UL> The selection of this device driver is done through methods in the + * PhysicalEnvironment object - see PhysicalEnvironment class. + * The application would query how many audio devices are available. For + * each device, the user can get the AudioDevice object that describes it + * and query its characteristics. Once a decision is made about which of + * the available audio devices to use for a PhysicalEnvironment, the + * particular device is set into this PhysicalEnvironment's fields. Each + * PhysicalEnvironment object may use only a single audio device. + *<P> + * The AudioDevice object interface specifies an abstract input device + * that creators of Java3D class libraries would implement for a particular + * device. Java3D's uses several methods to interact with specific devices. + * Since all audio devices implement this consistent interface, the user + * could have a portable means of initialize, set particular audio device + * elements and query generic characteristics for any audio device. + *<P> + *Initialization + *<P><UL> + * Each audio device driver must be initialized. + * The chosen device driver should be initialized before any Java3D + * Sound methods are executed because the implementation of the Sound + * methods, in general, are potentially device driver dependent.</UL> + *<P> + * Audio Playback Type + *<P><UL> + * These methods set and retrieve the audio playback type used to output + * the analog audio from rendering Java3D Sound nodes. + * The audio playback type specifies that playback will be through: + * stereo headphones, a monaural speaker, or a pair of speakers. + * For the stereo speakers, it is assumed that the two output speakers are + * equally distant from the listener, both at same angle from the head + * axis (thus oriented symmetrically about the listener), and at the same + * elevation. + * The type of playback chosen affects the sound image generated. + * Cross-talk cancellation is applied to the audio image if playback over + * stereo speakers is selected.</UL> + *<P> + * Distance to Speaker + *<P><UL> + * These methods set and retrieve the distance in meters from the center + * ear (the midpoint between the left and right ears) and one of the + * speakers in the listener's environment. For monaural speaker playback, + * a typical distance from the listener to the speaker in a workstation + * cabinet is 0.76 meters. For stereo speakers placed at the sides of the + * display, this might be 0.82 meters.</UL> + *<P> + * Angular Offset of Speakers + *<P><UL> + * These methods set and retrieve the angle in radians between the vectors + * from the center ear to each of the speaker transducers and the vectors + * from the center ear parallel to the head coordinate's Z axis. Speakers + * placed at the sides of the computer display typically range between + * 0.28 to 0.35 radians (between 10 and 20 degrees).</UL> + *<P> + * Device Driver Specific Data + *<P><UL> + * While the sound image created for final output to the playback system + * is either only mono or stereo (for this version of Java3D) most device + * driver implementations will mix the left and right image signals + * generated for each rendered sound source before outputting the final + * playback image. Each sound source will use N input channels of this + * internal mixer. Each implemented Java3D audio device driver will have + * its own limitations and driver-specific characteristics. These include + * channel availability and usage (during rendering). Methods for + * querying these device-driver specific characteristics are provided.</UL></UL> + *<P> + * Instantiating and Registering a New Device + *<P> + *<UL> A browser or applications developer must instantiate whatever system- + * specific audio devices that he or she needs and that exist on the system. + * This device information typically exists in a site configuration file. + * The browser or application will instantiate the physical environment as + * requested by the end-user. + *<P> + * The API for instantiating devices is site-specific, but it consists of + * a device object with a constructor and at least all of the methods + * specified in the AudioDevice interface. + *<P> + * Once instantiated, the browser or application must register the device + * with the Java3D sound scheduler by associating this device with a + * PhysicalEnvironment. The setAudioDevice method introduces new devices + * to the Java3D environment and the allAudioDevices method produces an + * enumeration that allows examining all available devices within a Java3D + * environment. See PhysicalEnvironment class for more details.</UL> + * <P> + * General Rules for calling AudioDevice methods: + * It is illegal for an application to call any non-query AudioDevice method + * if the AudioDevice is created then explicitly assigned to a + * PhysicalEnvironment using PhysicalEnvironment.setAudioDevice(); + * When either PhysicalEnvironment.setAudioDevice() is called - including + * when implicitly called by SimpleUniverse.getViewer().createAudioDevice() + * - the Core creates a SoundScheduler thread which makes calls to + * the AudioDevice. + * <P> + * If an application creates it's own instance of an AudioDevice and + * initializes it directly, rather than using PhysicalEnvironment. + * setAudioDevice(), that application may make <i>any</i> AudioDevice3D methods calls + * without fear of the Java 3D Core also trying to control the AudioDevice. + * Under this condition it is safe to call AudioDevice non-query methods. + */ + +public interface AudioDevice { + + /** ************* + * + * Constants + * + ****************/ + /** + * Audio Playback Types + * + * Types of audio output device Java3D sound is played over: + * Headphones, MONO_SPEAKER, STEREO_SPEAKERS + */ + /** + * Choosing Headphones as the audio playback type + * specifies that the audio playback will be through stereo headphones. + */ + public static final int HEADPHONES = 0; + + /** + * Choosing a + * single near-field monoaural speaker + * as the audio playback type + * specifies that the audio playback will be through a single speaker + * some supplied distance away from the listener. + */ + public static final int MONO_SPEAKER = 1; + + /** + * Choosing a + * two near-field stereo speakers + * as the audio playback type + * specifies that the audio playback will be through stereo speakers + * some supplied distance away from, and at some given angle to + * the listener. + */ + public static final int STEREO_SPEAKERS = 2; + + /** + * Initialize the audio device. + * Exactly what occurs during initialization is implementation dependent. + * This method provides explicit control by the user over when this + * initialization occurs. + * Initialization must be initiated before any other AudioDevice + * methods are called. + * @return true if initialization was successful without errors + */ + public abstract boolean initialize(); + + /** + * Code to close the device and release resources. + * @return true if close of device was successful without errors + */ + public abstract boolean close(); + + /** + * Set Type of Audio Playback physical transducer(s) sound is output to. + * Valid types are HEADPHONES, MONO_SPEAKER, STEREO_SPEAKERS + * @param type audio playback type + */ + public abstract void setAudioPlaybackType(int type); + + /** + * Get Type of Audio Playback Output Device. + * @return audio playback type + */ + public abstract int getAudioPlaybackType(); + + /** + * Set Distance from interaural mid-point between Ears to a Speaker. + * @param distance from interaural midpoint between the ears to closest speaker + */ + public abstract void setCenterEarToSpeaker(float distance); + + /** + * Get Distance from interaural mid-point between Ears to a Speaker. + * @return distance from interaural midpoint between the ears to closest speaker + */ + public abstract float getCenterEarToSpeaker(); + + /** + * Set Angle Offset (in radians) To Speaker. + * @param angle in radians from head Z axis and vector from center ear to speaker + */ + public abstract void setAngleOffsetToSpeaker(float angle); + + /** + * Get Angle Offset (in radians) To Speaker. + * @return angle in radians from head Z axis and vector from center ear to speaker + */ + public abstract float getAngleOffsetToSpeaker(); + + /** + * Query total number of channels available for sound rendering + * for this audio device. This returns the maximum number of channels + * available for Java3D sound rendering for all sound sources. + * @return total number of channels that can be used for this audio device + */ + public abstract int getTotalChannels(); + + /** + * Query number of channels currently available for use. + * During rendering, when sound nodes are playing, this method returns the + * number of channels still available to Java3D for rendering additional + * sound nodes. + * @return total number of channels current available + */ + public abstract int getChannelsAvailable(); + + /** + * Query number of channels that are used, or would be used to render + * a particular sound node. This method returns the number of channels + * needed to render a particular Sound node. The return value is the same + * no matter if the Sound is currently active and enabled (being played) or + * is inactive. + * @return number of channels a particular Sound node is using or would used + * if enabled and activated (rendered). + */ + public abstract int getChannelsUsedForSound(Sound node); +} diff --git a/src/classes/share/javax/media/j3d/AudioDevice3D.java b/src/classes/share/javax/media/j3d/AudioDevice3D.java new file mode 100644 index 0000000..4ae2fcc --- /dev/null +++ b/src/classes/share/javax/media/j3d/AudioDevice3D.java @@ -0,0 +1,515 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + + +/** + * The AudioDevice3D class defines a 3D audio device that is used to set + * sound and aural attributes. + *<P> + * After the application chooses the AudioDevice3D that Java3D sound + * is to be rendered on, the Java 3D Sound Scheduler will call these + * methods for all active sounds to render them on the audio device. + *<P> + * The intent is for this interface to be implemented by AudioDevice Driver + * developers using a software or hardware sound engine of their choice. + *<P> + * Methods in this interface provide the Java3D Core a generic way to + * set and query the audio device the application has chosen audio rendering + * to be performed on. Methods in this interface include: + * <UL> + * Set up and clear the sound as a sample on the device. + * <P> + * Start, stop, pause, unpause, mute, and unmute of sample on the device. + * <P> + * Set parameters for each sample corresponding to the fields in the + * Sound node. + * <P> + * Set the current active aural parameters that affect all positional samples. + * </UL> + * <P> + * Sound Types + * <P> + * Sound types match the Sound node classes defined for Java 3D core + * for BackgroundSound, PointSound, and ConeSound. The type of sound + * a sample is loaded as determines which methods affect it. + * + * <P> + * Sound Data Types + * <P> + * Samples can be processed as streaming or buffered data. + * Fully spatializing sound sources may require data to be buffered. + * + */ + +public interface AudioDevice3D extends AudioDevice { + + /** + * Specifies the sound type as background sound. + */ + public static final int BACKGROUND_SOUND = 1; + + /** + * Specifies the sound type as point sound. + */ + public static final int POINT_SOUND = 2; + + /** + * Specifies the sound type as cone sound. + */ + + public static final int CONE_SOUND = 3; + + /** + * Sound data specified as Streaming is not copied by the AudioDevice + * driver implementation. It is up to the application to ensure that + * this data is continuously accessible during sound rendering. + * Furthermore, full sound spatialization may not be possible, for + * all AudioDevice3D implementations on unbuffered sound data. + */ + public static final int STREAMING_AUDIO_DATA = 1; + /** + * Sound data specified as Buffered is copied by the AudioDevice + * driver implementation. + */ + public static final int BUFFERED_AUDIO_DATA = 2; + + + /** + * Accepts a reference to the current View. + * Passes reference to current View Object. The PhysicalEnvironment + * parameters (with playback type and speaker placement), and the + * PhysicalBody parameters (position/orientation of ears) + * can be obtained from this object, and the transformations to/from + * ViewPlatform coordinate (space the listener's head is in) and + * Virtual World coordinates (space sounds are in). + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param reference the current View + */ + public abstract void setView(View reference); + + /** + * Accepts a reference to the MediaContainer + * which contains a reference to sound data and information about the + * type of data it is. A "sound type" input parameter denotes if the + * Java 3D sound associated with this sample is a Background, Point, or + * Cone Sound node. + * Depending on the type of MediaContainer the sound data is and on the + * implementation of the AudioDevice used, sound data preparation could + * consist of opening, attaching, or loading sound data into the device. + * Unless the cached flag is true, this sound data should NOT be copied, + * if possible, into host or device memory. + *<P> + * Once this preparation is complete for the sound sample, an AudioDevice + * specific index, used to reference the sample in future method calls, + * is returned. All the rest of the methods described below require + * this index as a parameter. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param soundType defines the type of Sound Node: Background, Point, and + * Cone + * @param soundData reference to MediaContainer sound data and cached flag + * @return device specific sample index used for referencing this sound + */ + public abstract int prepareSound(int soundType, MediaContainer soundData); + + /** + * Requests that the AudioDevice free all + * resources associated with sample with index id. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + */ + public abstract void clearSound(int index); + + /** + * Returns the duration in milliseconds of the sound sample, + * if this information can be determined. + * For non-cached + * streams, this method returns Sound.DURATION_UNKNOWN. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @return sound duration in milliseconds if this can be determined, + * otherwise (for non-cached streams) Sound.DURATION_UNKNOWN is returned + */ + public abstract long getSampleDuration(int index); + + /** + * + * Retrieves the number of channels (on executing audio device) that + * this sound is using, if it is playing, or is expected to use + * if it were begun to be played. This form of this method takes the + * sound's current state (including whether it is muted or unmuted) + * into account. + *<P> + * For some AudioDevice3D implementations: + *<UL> + * Muted sound take channels up on the systems mixer (because they're + * rendered as samples playing with gain zero. + *<P> + * A single sound could be rendered using multiple samples, each taking + * up mixer channels. + *</UL> + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @return number of channels used by sound if it were playing + */ + public abstract int getNumberOfChannelsUsed(int index); + + /** + * + * Retrieves the number of channels (on executing audio device) that + * this sound is using, if it is playing, or is projected to use if + * it were to be started playing. Rather than using the actual current + * muted/unmuted state of the sound, the muted parameter is used in + * making the determination. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @param muted flag to use as the current muted state ignoring current + * mute state + * @return number of channels used by sound if it were playing + */ + public abstract int getNumberOfChannelsUsed(int index, boolean muted); + + /** + * Begins a sound playing on the AudioDevice. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @return flag denoting if sample was started; 1 if true, 0 if false + */ + public abstract int startSample(int index); + + /** + * Returns the system time of when the sound + * was last "started". Note that this start time will be as accurate + * as the AudioDevice implementation can make it - but that it is not + * guaranteed to be exact. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @return system time in milliseconds of the last time sound was started + */ + public abstract long getStartTime(int index); + + /** + * Stops the sound on the AudioDevice. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * associated with sound data to be played + * @return flag denoting if sample was stopped; 1 if true, 0 if false + */ + public abstract int stopSample(int index); + + /** + * Sets the overall gain scale factor applied to data associated with this + * source to increase or decrease its overall amplitude. + * The gain scale factor value passed into this method is the combined value + * of the Sound node's Initial Gain and the current AuralAttribute Gain + * scale factors. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @param scaleFactor amplitude (gain) scale factor + */ + public abstract void setSampleGain(int index, float scaleFactor); + + /** + * Sets a sound's loop count. + * A full description of this parameter and how it is used is in + * the documentation for Sound.setLoop. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @param count number of times sound is looped during play + * @see Sound#setLoop + */ + public abstract void setLoop(int index, int count); + + /** + * Passes a reference to the concatenated transformation to be applied to + * local sound position and direction parameters. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @param trans transformation matrix applied to local coordinate parameters + */ + public abstract void setVworldXfrm(int index, Transform3D trans); + + + /** + * Sets this sound's location (in Local coordinates) from specified + * Point. The form of the position parameter matches those of the PointSound + * method of the same name. + * A full description of this parameter and how it is used is in + * the documentation for PointSound class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @param position location of Point or Cone Sound in Virtual World + * coordinates + * @see PointSound#setPosition(float x, float y, float z) + * @see PointSound#setPosition(Point3f position) + */ + public abstract void setPosition(int index, Point3d position); + + /** + * Sets this sound's distance gain elliptical attenuation (not + * including filter cutoff frequency) by defining corresponding + * arrays containing distances from the sound's origin and gain + * scale factors applied to all active positional sounds. + * Gain scale factor is applied to sound based on the distance + * the listener + * is from sound source. + * These attenuation parameters are ignored for BackgroundSound nodes. + * The back attenuation parameter is ignored for PointSound nodes. + * <P> + * The form of the attenuation parameters match that of the ConeSound method + * of the same name. + * A full description of this parameter and how it is used is in + * the documentation for ConeSound class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @param frontDistance defines an array of distance along positive axis + * through which ellipses pass + * @param frontAttenuationScaleFactor gain scale factors + * @param backDistance defines an array of distance along the negative axis + * through which ellipses pass + * @param backAttenuationScaleFactor gain scale factors + * @see ConeSound#setDistanceGain(float[] frontDistance, float[] frontGain, + * float[] backDistance, float[] backGain) + * @see ConeSound#setDistanceGain(Point2f[] frontAttenuation, + * Point2f[] backAttenuation) + */ + public abstract void setDistanceGain(int index, + double[] frontDistance, float[] frontAttenuationScaleFactor, + double[] backDistance, float[] backAttenuationScaleFactor); + /** + * Sets this sound's direction from the local coordinate vector provided. + * The form of the direction parameter matches that of the ConeSound method + * of the same name. + * A full description of this parameter and how it is used is in + * the documentation for the ConeSound class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @param direction the new direction vector in local coordinates + * @see ConeSound#setDirection(float x, float y, float z) + * @see ConeSound#setDirection(Vector3f direction) + */ + public abstract void setDirection(int index, Vector3d direction); + + /** + * Sets this sound's angular gain attenuation (including filter) + * by defining corresponding arrays containing angular offsets from + * the sound's axis, gain scale factors, and frequency cutoff applied + * to all active directional sounds. + * Gain scale factor is applied to sound based on the angle between the + * sound's axis and the ray from the sound source origin to the listener. + * The form of the attenuation parameter matches that of the ConeSound + * method of the same name. + * A full description of this parameter and how it is used is in + * the documentation for the ConeSound class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + * @param filterType describes type (if any) of filtering defined by attenuation + * @param angle array containing angular distances from sound axis + * @param attenuationScaleFactor array containing gain scale factor + * @param filterCutoff array containing filter cutoff frequencies. + * The filter values for each tuples can be set to Sound.NO_FILTER. + * @see ConeSound#setAngularAttenuation(float[] distance, float[] gain, + * float[] filter) + * @see ConeSound#setAngularAttenuation(Point3f[] attenuation) + * @see ConeSound#setAngularAttenuation(Point2f[] attenuation) + */ + public abstract void setAngularAttenuation(int index, int filterType, + double[] angle, float[] attenuationScaleFactor, float[] filterCutoff); + + /** + * Changes the speed of sound factor. + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param rolloff atmospheric gain scale factor (changing speed of sound) + * @see AuralAttributes#setRolloff + */ + public abstract void setRolloff(float rolloff); + + /** + * Sets the Reflective Coefficient scale factor applied to distinct + * low-order early reflections of sound off the surfaces in the region + * defined by the current listening region. + * <P> + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param coefficient reflection/absorption factor applied to reverb + * @see AuralAttributes#setReflectionCoefficient + */ + public abstract void setReflectionCoefficient(float coefficient); + + /** + * Sets the reverberation delay time. + * In this form, while reverberation is being rendered, the parameter + * specifies the delay time between each order of late reflections + * explicitly given in milliseconds. + * A value for delay time of 0.0 disables + * reverberation. + * <P> + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param reverbDelay time between each order of late reflection + * @see AuralAttributes#setReverbDelay(float reverbDelay) + */ + public abstract void setReverbDelay(float reverbDelay); + + /** + * Sets the reverberation order of reflections. + * The reverbOrder parameter specifies the number of times reflections are added to + * reverberation being calculated. A value of -1 specifies an unbounded + * number of reverberations. + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param reverbOrder number of times reflections added to reverb signal + * @see AuralAttributes#setReverbOrder + */ + public abstract void setReverbOrder(int reverbOrder); + + /** + * Sets Distance Filter corresponding arrays containing distances and + * frequency cutoff applied to all active positional sounds. + * Gain scale factor is applied to sound based on the distance the listener + * is from the sound source. + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param filterType denotes the type of filtering to be applied + * @param distance array of offset distances from sound origin + * @param filterCutoff array of frequency cutoff + * @see AuralAttributes#setDistanceFilter(float[] distance, + * float[] frequencyCutoff) + * @see AuralAttributes#setDistanceFilter(Point2f[] attenuation) + */ + public abstract void setDistanceFilter(int filterType, + double[] distance, float[] filterCutoff); + + /** + * Specifies a scale factor applied to the frequency (or + * wavelength). A value less than 1.0 will result of slowing the playback + * rate of the sample. A value greater than 1.0 will increase the playback + * rate. + * This parameter is also used to expand or contract the usual + * frequency shift applied to the sound source due to Doppler effect + * calculations. Valid values are >= 0.0. + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param frequencyScaleFactor factor applied to change of frequency + * @see AuralAttributes#setFrequencyScaleFactor + */ + public abstract void setFrequencyScaleFactor(float frequencyScaleFactor); + + /** + * Sets the Velocity scale factor applied during Doppler Effect calculation. + * This parameter specifies a scale factor applied to the velocity of sound + * relative to the listener's position and movement in relation to the sound's + * position and movement. This scale factor is multipled by the calculated + * velocity portion of the Doppler effect equation used during sound rendering. + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param velocityScaleFactor applied to velocity of sound in relation + * to listener + * @see AuralAttributes#setVelocityScaleFactor + */ + public abstract void setVelocityScaleFactor(float velocityScaleFactor); + + /** + * Makes the sample 'play silently'. + * This method implements (as efficiently as possible) the muting + * of a playing sound sample. Ideally this is implemented by + * stopping a sample and freeing channel resources (rather than + * just setting the gain of the sample to zero). + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + */ + public abstract void muteSample(int index); + + /** + * Makes a silently playing sample audible. + * In the ideal, this restarts a muted sample by offset from the + * beginning by the number of milliseconds since the time the sample + * began playing (rather than setting gain to current non-zero gain). + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + */ + public abstract void unmuteSample(int index); + + /** + * Temporarily stops a cached sample from playing without resetting the + * sample's current pointer back to the beginning of the sound data so + * that it can be unpaused at a later time from the same location in the + * sample when the pause was initiated. Pausing a streaming, non-cached + * sound sample will be treated as a mute. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + */ + public abstract void pauseSample(int index); + + /** + * Restarts the paused sample from the location in the sample where + * paused. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + */ + public abstract void unpauseSample(int index); + + /** + * + * Explicitly updates a Sample. + * This method is called when a Sound is to be explicitly updated. + * It is only called when all a sounds parameters are known to have + * been passed to the audio device. In this way, an implementation + * can choose to perform lazy-evaluation of a sample, rather than + * updating the rendering state of the sample after every individual + * parameter changed. + * This method can be left as a null method if the implementor so chooses. + * <P> + * This method should only be called by Java3D Core and NOT by any application. + * @param index device specific reference number to device driver sample + */ + public abstract void updateSample(int index); + +} diff --git a/src/classes/share/javax/media/j3d/AudioDevice3DL2.java b/src/classes/share/javax/media/j3d/AudioDevice3DL2.java new file mode 100644 index 0000000..2ba4c6a --- /dev/null +++ b/src/classes/share/javax/media/j3d/AudioDevice3DL2.java @@ -0,0 +1,305 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * Extends AudioDevice3D to include reverb and environmental audio parameters + * that are defined in the MIDI Manufactures' Association Interactive Audio + * Special Interest Group (MMA IASIG) Level 2 Specification. + *<P> + * The reverberation methods of AudioDevice3DL2 interface augment the + * reverberation methods defined in AudioDevice3D. + *<P> + * The intent is for this interface to be implemented by AudioDevice Driver + * developers using a software or hardware sound engine of their choice. + *<P> + * Methods in this interface provide the Java3D Core a generic way to + * set and query the audio device the application has chosen audio rendering + * to be performed on. + *<P> + * The non-query methods of this interface should only be called by + * an application if the AudioDevice instance + * is not referenced by any PhysicalEnvironment + * explicitly with .setAudioDevice() or implicitly through Universe + * utility method in which case these are called by Core Java 3D + * Sound classes and Sound Scheduler thread(s). + *<P> + * After the application chooses the AudioDevice3DL2 implementation + * that Java3D sound is to be rendered on, the Java 3D Sound Scheduler + * will call these methods for all active sounds to render them on the + * audio device. + *<P> + * The AudioDevice3DL2 methods should not be call by any application if the + * audio device is associated with a Physical Environment and thus used by + * Java3D Core. + *<P> + * Filtering for this extended AudioDevice interface is defined uniformly as + * a simple low-pass filter defined by a cutoff frequency. This will allow the + * same type of high-frequency attenuation that the MMA IASIG Level 2 filtering + * model with its 'reference frequency' and 'attenuation ratio' parameters + * affords. Use of a cutoff frequency is consistent with the filtering type + * for distance and angular attenuation for ConeSound and AuralAttributes. + * The filter methods will likely be overloaded in some future extension of this + * interface. + * + * @see Sound + * @see AuralAttributes + * @see AudioDevice3D + * @since Java 3D 1.3 + */ + +public interface AudioDevice3DL2 extends AudioDevice3D { + + /** + * Pause audio device engine (thread/server) without closing the device. + * Causes all cached sounds to be paused and all streaming sounds to be + * stopped. + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * This method will be implicitly called when View (associated with this + * device) is deactivated. + */ + public abstract void pause(); + + /** + * Resumes audio device engine (if previously paused) without reinitializing + * the device. + * Causes all paused cached sounds to be resumed and all streaming sounds + * restarted. + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * This method will be implicitly called when View (associated with this + * device) is actived. + */ + public abstract void resume(); + + /** + * Set overall gain control of all sounds playing on the audio device. + * Default: 1.0f = no attenuation. + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * @param scaleFactor scale factor applied to calculated amplitudes for + * all sounds playing on this device + */ + public abstract void setGain(float scaleFactor); + + /** + * Set scale factor applied to sample playback rate for a particular sound + * associated with the audio device. + * Changing the device sample rate affects both the pitch and speed. + * This scale factor is applied to ALL sound types. + * Changes (scales) the playback rate of a sound independent of + * Doppler rate changes. + * Default: 1.0f = original sample rate is unchanged + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * @param sampleId device specific reference number to device driver sample + * @param scaleFactor non-negative factor applied to calculated + * amplitudes for all sounds playing on this device + */ + public abstract void setRateScaleFactor(int sampleId, float scaleFactor); + + + /** + * Set late reflection (referred to as 'reverb') attenuation. + * This scale factor is applied to iterative, indistinguishable + * late reflections that constitute the tail of reverberated sound in + * the aural environment. + * This parameter, along with the early reflection coefficient, defines + * the reflective/absorptive characteristic of the surfaces in the + * current listening region. + * A coefficient value of 0 disables reverberation. + * Valid values of parameters range from 0.0 to 1.0. + * Default: 0.0f. + * <P> + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * @param coefficient late reflection attenuation factor + * @see AuralAttributes#setReverbCoefficient + */ + public abstract void setReverbCoefficient(float coefficient); + + + /** + * Sets the early reflection delay time. + * In this form, the parameter specifies the delay time between each order + * of reflection (while reverberation is being rendered) explicitly given + * in milliseconds. + * Valid values are non-negative floats. + * There may be limitations imposed by the device on how small or large this + * value can be made. + * A value of 0.0 would result in early reflections being added as soon as + * possible after the sound begins. + * Default = 20.0 milliseconds. + * <P> + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * @param reflectionDelay time between each order of early reflection + * @see AuralAttributes#setReflectionDelay + */ + public abstract void setReflectionDelay(float reflectionDelay); + + /** + * Set reverb decay time. + * Defines the reverberation decay curve. + * Default: 1000.0 milliseconds. + * <P> + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * @param time decay time in milliseconds + * @see AuralAttributes#setDecayTime + */ + public abstract void setDecayTime(float time); + + /** + * Set reverb decay filter. + * This provides for frequencies above the given cutoff frequency to be + * attenuated during reverb decay at a different rate than frequencies + * below this value. Thus, defining a different reverb decay curve for + * frequencies above the cutoff value. + * Default: 1.0 decay is uniform for all frequencies. + * <P> + * There is no corresponding Core AuralAttributes method at this time. + * Until high frequency attenuation is supported by new Core API, + * this will be set by the Core with the value 1.0. + * It is highly recommended that this method should NOT be + * called by any application if the audio device is associated with + * a Physical Environment used by Java3D Core. + * @param frequencyCutoff value of frequencies in Hertz above which a + * low-pass filter is applied. + * @see AuralAttributes#setDecayFilter + */ + public abstract void setDecayFilter(float frequencyCutoff); + + /** + * Set reverb diffusion. + * This defines the echo dispersement (also referred to as 'echo density'). + * The value of this reverb parameter is expressed as a percent of the + * audio device's minimum-to-maximum values. + * Default: 1.0f - maximum diffusion on device. + * <P> + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * @param diffusion percentage expressed within the range of 0.0 and 1.0 + * @see AuralAttributes#setDiffusion + */ + public abstract void setDiffusion(float diffusion); + + /** + * Set reverb density. + * This defines the modal density (also referred to as 'spectral + * coloration'). + * The value of this parameter is expressed as a percent of the audio + * device's minimum-to-maximum values for this reverb parameter. + * Default: 1.0f - maximum density on device. + * <P> + * A full description of this parameter and how it is used is in + * the documentation for the AuralAttributes class. + * <P> + * This method should NOT be called by any application if the audio device + * is associated with a Physical Environment used by Java3D Core. + * @param density reverb density expressed as a percentage, + * within the range of 0.0 and 1.0 + * @see AuralAttributes#setDensity + */ + public abstract void setDensity(float density); + + + /** + * Set the obstruction gain control. This method allows for attenuating + * sound waves traveling between the sound source and the listener + * obstructed by objects. Direct sound signals/waves for obstructed sound + * source are attenuated but not indirect (reflected) waves. + * Default: 1.0 - gain is not attenuated; obstruction is not occurring. + * <P> + * There is no corresponding Core AuralAttributes method at this time. + * Even so, this method should NOT be called by any application if the + * audio device is associated with a Physical Environment used by Java3D + * Core. + * @param sampleId device specific reference number to device driver sample + * @param scaleFactor non-negative factor applied to direct sound gain + */ + public abstract void setObstructionGain(int sampleId, float scaleFactor); + + /** + * Set the obstruction filter control. + * This provides for frequencies above the given cutoff frequency + * to be attenuated, during while the gain of an obstruction signal + * is being calculated, at a different rate than frequencies + * below this value. + * Default: 1.0 - filtering is uniform for all frequencies. + * <P> + * There is no corresponding Core AuralAttributes method at this time. + * Until high frequency attenuation is supported by new Core API + * this will be set by the Core with the value 1.0. + * It is highly recommended that this method should NOT be + * called by any application if the audio device is associated with + * a Physical Environment used by Java3D Core. + * @param frequencyCutoff value of frequencies in Hertz above which a + * low-pass filter is applied. + */ + + public abstract void setObstructionFilter(int sampleId, float frequencyCutoff); + + /** + * Set the occlusion gain control. This method allows for attenuating + * sound waves traveling between the sound source and the listener + * occluded by objects. Both direct and indirect sound signals/waves + * for occluded sound sources are attenuated. + * Default: 1.0 - gain is not attenuated; occlusion is not occurring. + * <P> + * There is no corresponding Core AuralAttributes method at this time. + * Even so, this method should NOT be called by any application if the + * audio device is associated with a Physical Environment used by Java3D + * Core. + * @param sampleId device specific reference number to device driver sample + * @param scaleFactor non-negative factor applied to direct sound gain + */ + public abstract void setOcclusionGain(int sampleId, float scaleFactor); + + /** + * Set the occlusion filter control. + * This provides for frequencies above the given cutoff frequency + * to be attenuated, during while the gain of an occluded signal + * is being calculated, at a different rate than frequencies below + * this value. + * Default: 1.0 - filtering is uniform for all frequencies. + * <P> + * There is no corresponding Core AuralAttributes method at this time. + * Until high frequency attenuation is supported by new Core API + * this will be set by the Core with the value 1.0. + * It is highly recommended that this method should NOT be + * called by any application if the audio device is associated with + * a Physical Environment used by Java3D Core. + * @param frequencyCutoff value of frequencies in Hertz above which a + * low-pass filter is applied. + */ + public abstract void setOcclusionFilter(int sampleId, float frequencyCutoff); +} diff --git a/src/classes/share/javax/media/j3d/AudioDeviceEnumerator.java b/src/classes/share/javax/media/j3d/AudioDeviceEnumerator.java new file mode 100644 index 0000000..79b2b57 --- /dev/null +++ b/src/classes/share/javax/media/j3d/AudioDeviceEnumerator.java @@ -0,0 +1,67 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * The class that enumerates all AudioDevices defined in the environment + * + * An AudioDeviceEnumerator generates the audio devices defined with the + * execution environment of the currently running Java 3D application. + */ + +class AudioDeviceEnumerator implements Enumeration { + + boolean endOfList; // NOTE: list length always equals one or zero + AudioDevice device; + + AudioDeviceEnumerator(PhysicalEnvironment physicalEnvironment) { + device = physicalEnvironment.getAudioDevice(); + if(device == null) + endOfList = true; + else + endOfList = false; + } + + void reset() { + if(device != null) + endOfList = false; + } + + + /** + * Query that tells whether the enumerator has more elements + * @return true if the enumerator has more elements, false otherwise + */ + public boolean hasMoreElements() { + if(endOfList == false) + return true; + else + return false; + } + + /** + * Return the next element in the enumerators + * @return the next element in this enumerator + */ + public Object nextElement() { + if (this.hasMoreElements()) { + endOfList = true; + return ((Object) device); + } else { + throw new NoSuchElementException(J3dI18N.getString("AudioDeviceEnumerator0")); + } + } +} diff --git a/src/classes/share/javax/media/j3d/AuralAttributes.java b/src/classes/share/javax/media/j3d/AuralAttributes.java new file mode 100644 index 0000000..dde9881 --- /dev/null +++ b/src/classes/share/javax/media/j3d/AuralAttributes.java @@ -0,0 +1,1261 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Point2f; + +/** + * The AuralAttributes object is a component object of a Soundscape node that + * defines environmental audio parameters that affect sound rendering. These + * attributes include gain scale factor, atmospheric rolloff, and parameters + * controlling reverberation, distance frequency filtering, and velocity-based + * Doppler effect. + *<P> + * Attribute Gain + * <P><UL> + * Scale factor applied to all sound's amplitude active within this region. + * This factor attenuates both direct and reflected/reverbered amplitudes. + * Valid values are >= 0.0 + * </UL> + *<P> + * Attribute Gain Rolloff + * <P><UL> + * Rolloff scale factor is used to model atmospheric changes from normal + * speed of sound. The base value, 0.344 meters/millisecond is used + * to approximate the speed of sound through air at room temperature, + * is multipled by this scale factor whenever the speed of sound is + * applied during spatialization calculations. + * Valid values are >= 0.0. Values > 1.0 increase the speed of sound, + * while values < 1.0 decrease its speed. A value of zero makes sound + * silent (but it continues to play). + * </UL> + *<P> + * Auralization <P> + *<UL> + * Auralization is the environmental modeling of sound iteratively + * reflecting off the surfaces of the bounded region the listener is in. + * Auralization components include + * early, distinct, low-order reflections and later, dense, + * higher-order reflections referred to as reverberation. + * These reflections are attenuated relative to the direct, unreflected + * sound. The difference in gain between direct and reflected sound + * gives the listener a sense of the surface material and + * the relative distance of the sound. + * The delay between the start of the direct sound and start of + * reverberation (as detected by the listener), + * as well as the length of time reverberation is audible as it + * exponentially decays, give the listener a sense of the size of the + * listening space. + * <P> + * In Java3D's model for auralization there are several parameters + * that approximate sound reflection and reverberation for a particular + * listening space: + * <UL>Reflection Coefficient <UL>Gain attenuation of the initial + * reflections across all frequencies.</UL></UL> + * <UL>(Early) Reflection Delay <UL>The time it takes for the first + * low-order reflected sound to reach the listener.</UL></UL> + * <UL>Reverb Coefficient <UL>Gain attenuation of the late reflections + * (referred to as 'reverb') across all frequencies.</UL></UL> + * <UL>Reverb Delay <UL>The time it takes for reverbered sound + * to reach the listener.</UL></UL> + * <UL>Decay Time <UL>Describes the reverb decay curve by defining the + * length of time reverb takes to decay to effective zero. + * </UL></UL> + * <UL>Decay Filter <UL>High-frequencies of the late reverberation + * can be attenuated at a different rate. </UL></UL> + * <UL>Density <UL>Modal density (spectral coloration) of + * reverberation.</UL></UL> + * <UL>Diffusion <UL>Echo dispersement of reverberation.</UL></UL> + * <UL>Reverb Bounds <UL>Approximates the volume of the listening space. + * If specified, it defines the reverberation delay.</UL></UL> + * <UL>Reverb Order <UL>Optionally limits the amount of times during + * reverb calculation that a sound is recursively reflected off the + * bounding region.</UL></UL> + * <P> + * Reflection Coefficient + * <P><UL> + * The reflection coefficient is an amplitude scale factor used to + * approximate the average reflective or absorptive characteristics + * for early reflections + * of the composite surfaces in the region the listener is in. + * This scale factor is applied to the sound's amplitude regardless of the + * sound's position. + * The range of valid values is 0.0 to 1.0. + * A value of 1.0 denotes that reflections are unattenuated - + * the amplitude of reflected sound waves are not decreased. + * A value of 0.0 represents full absorption of reflections + * by the surfaces in the listening space (no reflections occur + * thus reverberation is disabled). + * </UL> + * <P> + * Reflection Delay + * <P><UL> + * The early reflection delay time (in milliseconds) can be explicitly + * set. Well-defined values are floats > 0.0. + * A value of 0.0 results in reverberation being added as soon as + * possible after the sound begins. + * </UL> + * <P> + * Reverberation Coefficient + * <P><UL> + * The reverb coefficient is an amplitude scale factor used to + * approximate the average reflective or absorptive characteristics + * of late reflections. + * A value of 0.0 represents full absorption of reflections + * by the surfaces in the listening space (no reflections occur + * thus reverberation is disabled). + * </UL> + * <P> + * Reverberation Delay + * <P><UL> + * The reverb delay time (in milliseconds) is set either explicitly, + * or implicitly by supplying a reverb bounds volume (from which the + * delay time can be calculated). Well-defined values are floats > 0.0. + * A value of 0.0 results in reverberation being added as soon as + * possible after the sound begins. Reverb delay, as calculated from non- + * null reverb bounds, takes precedence over explicitly set delay time. + * </UL> + * <P> + * Reverberation Bounds + * <P><UL> + * The reverb bounding region defines the overall size of space + * that reverberation is calculated for. + * This optional bounds does not have to be the same as the application + * region of the Soundscape node referencing this AuralAttributes object. + * If this bounding region is specified then reverb decay and delay are + * internally calculated from this bounds. + * </UL> + * <P> + * Reverberation Order + * <P><UL> + * The reverb order is a hint that can be used during reverberation + * to limit the number of late reflections required in calculation of + * reverb decay. + * All positive values can be interpreted during reverb rendering + * as the maximum order of reflections to be calculated. + * A non-positive value signifies that no limit is placed on the order of + * reflections calculated during reverberation rendering. + * In the case where reverb order is not limited, reverb decay is defined + * strictly by the Reverberation Decay Time parameter. + * </UL> + * <P> + * Decay Time + * <P><UL> + * The reverberation decay time explicitly defines the length of time in + * milliseconds it takes for the amplitude of late reflections to + * exponentally decrease to effective zero. + * In the case where reverb delay is set non-positive + * the renderer will perform the shortest reverberation decay + * possible. + * If ReverbOrder is set, this parameter is clamped by the reverb + * time calculated as time = reverb Delay * reverb Order. + * If ReverbOrder is 0, the decay time parameter is not clamped. + * </UL> + * <P> + * Decay Filter + * <P><UL> + * The reverberation decay filter defines how frequencies above a given + * value are attenuated by the listening space. This allows for modelling + * materials on surfaces that absorb high frequencies at a faster rate + * than low frequencies. + * </UL> + * <P> + * Reverberation Diffusion + * <P><UL> + * The reverberation diffusion explicitly defines echo dispersement + * (sometimes refered to as echo density). The value for diffusion + * is proportional to the number of echos per second heard in late + * reverberation, especially noticable at the tail of the reverberation + * decay. The greater the diffusion the more 'natural' the reverberation + * decay sounds. Reducing diffusion makes the decay sound hollow as + * produced in a small highly reflecive space (such as a bathroom). + * </UL> + * <P> + * Reverberation Density + * <P><UL> + * The reverberation density explicitly defines modal reverb density + * The value for this modal density is proportional to the number of + * resonances heard in late reverberation perceived as spectral + * coloration. The greater the density, the smoother, less grainy the + * later reverberation decay. + * </UL> + *</UL> + *<P> + * Distance Filter + * <P><UL> + * This parameter specifies a (distance, filter) attenuation pairs array. + * If this is not set, no distance filtering is performed (equivalent to + * using a distance filter of Sound.NO_FILTER for all distances). Currently, + * this filter is a low-pass cutoff frequency. This array of pairs defines + * a piece-wise linear slope for range of values. This attenuation array is + * similar to the PointSound node's distanceAttenuation pair array, except + * paired with distances in this list are frequency values. Using these + * pairs, distance-based low-pass frequency filtering can be applied during + * sound rendering. Distances, specified in the local coordinate system in + * meters, must be > 0. Frequencies (in Hz) must be > 0. + *<P> + * If the distance from the listener to the sound source is less than the + * first distance in the array, the first filter is applied to the sound + * source. This creates a spherical region around the listener within + * which a sound is uniformly attenuated by the first filter in the array. + * If the distance from the listener to the sound source is greater than + * the last distance in the array, the last filter is applied to the sound + * source. + * <P> + * Distance elements in these array of pairs is a monotonically-increasing + * set of floating point numbers measured from the location of the sound + * source. FrequencyCutoff elements in this list of pairs can be any + * positive float. While for most applications this list of values will + * usually be monotonically-decreasing, they do not have to be. + * <P> + * The getDistanceFilterLength method returns the length of the distance filter + * arrays. Arrays passed into getDistanceFilter methods should all be at + * least this size.</UL> + * </UL><P> + * Doppler Effect Model + * <P><UL> + * Doppler effect can be used to create a greater sense of movement of + * sound sources, and can help reduce front-back localization errors. + * The frequency of sound waves emanating from the source are raised or + * lowered based on the speed of the source in relation to the listener, + * and several AuralAttribute parameters. + * <P> + * The FrequencyScaleFactor can be used to increase or reduce the change + * of frequency associated with normal Doppler calculation, or to shift + * the pitch of the sound directly if Doppler effect is disabled. + * Values must be > zero for sounds to be heard. If the value is zero, + * sounds affected by this AuralAttribute object are paused. + * <P> + * To simulate Doppler effect, the relative velocity (change in + * distance in the local coordinate system between the sound source and + * the listener over time, in meters per second) is calculated. This + * calculated velocity is multipled by the given VelocityScaleFactor. + * Values must be >= zero. If is a scale factor value of zero is given, + * Doppler effect is not calculated or applied to sound.</UL></UL> + */ +public class AuralAttributes extends NodeComponent { + + /** + * + * Constants + * + * These flags, when enabled using the setCapability method, allow an + * application to invoke methods that read or write its parameters. + * + */ + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's attribute gain scale factor information. + */ + public static final int + ALLOW_ATTRIBUTE_GAIN_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_ATTRIBUTE_GAIN_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's attribute gain scale factor information. + */ + public static final int + ALLOW_ATTRIBUTE_GAIN_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_ATTRIBUTE_GAIN_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's atmospheric rolloff. + */ + public static final int + ALLOW_ROLLOFF_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_ROLLOFF_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's atmospheric rolloff. + */ + public static final int + ALLOW_ROLLOFF_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_ROLLOFF_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reflection coefficient. + */ + public static final int + ALLOW_REFLECTION_COEFFICIENT_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REFLECTION_COEFFICIENT_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reflection coefficient. + */ + public static final int + ALLOW_REFLECTION_COEFFICIENT_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REFLECTION_COEFFICIENT_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reflection delay information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_REFLECTION_DELAY_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REFLECTION_DELAY_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reflection delay information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_REFLECTION_DELAY_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REFLECTION_DELAY_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reverb coefficient. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_REVERB_COEFFICIENT_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REVERB_COEFFICIENT_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reverb coefficient. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_REVERB_COEFFICIENT_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REVERB_COEFFICIENT_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reverberation delay information. + */ + public static final int + ALLOW_REVERB_DELAY_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REVERB_DELAY_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reverberation delay information. + */ + public static final int + ALLOW_REVERB_DELAY_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REVERB_DELAY_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reverb order (feedback loop) information. + */ + public static final int + ALLOW_REVERB_ORDER_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REVERB_ORDER_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reverb order (feedback loop) information. + */ + public static final int + ALLOW_REVERB_ORDER_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_REVERB_ORDER_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reverb decay time information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DECAY_TIME_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DECAY_TIME_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reverb decay time information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DECAY_TIME_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DECAY_TIME_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reverb decay filter information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DECAY_FILTER_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DECAY_FILTER_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reverb decay filter information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DECAY_FILTER_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DECAY_FILTER_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reverb diffusion information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DIFFUSION_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DIFFUSION_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reverb diffusion information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DIFFUSION_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DIFFUSION_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's reverb density information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DENSITY_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DENSITY_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's reverb density information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DENSITY_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DENSITY_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's frequency cutoff information. + */ + public static final int + ALLOW_DISTANCE_FILTER_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DISTANCE_FILTER_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's frequency cutoff information. + */ + public static final int + ALLOW_DISTANCE_FILTER_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_DISTANCE_FILTER_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's frequency scale factor information. + */ + public static final int + ALLOW_FREQUENCY_SCALE_FACTOR_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_FREQUENCY_SCALE_FACTOR_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's frequency scale factor information. + */ + public static final int + ALLOW_FREQUENCY_SCALE_FACTOR_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_FREQUENCY_SCALE_FACTOR_WRITE; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the reading of it's velocity scale factor information. + */ + public static final int + ALLOW_VELOCITY_SCALE_FACTOR_READ = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_VELOCITY_SCALE_FACTOR_READ; + + /** + * For AuralAttributes component objects, specifies that this object + * allows the writing of it's velocity scale factor information. + */ + public static final int + ALLOW_VELOCITY_SCALE_FACTOR_WRITE = CapabilityBits.AURAL_ATTRIBUTES_ALLOW_VELOCITY_SCALE_FACTOR_WRITE; + + /** ***************** + * + * Constructors + * + * ******************/ + /** + * Constructs and initializes a new AuralAttributes object using default + * parameters. The following default values are used: + * <ul> + * attribute gain: 1.0<br> + * rolloff: 1.0<br> + * reflection coeff: 0.0<br> + * reflection delay: 20.0<br> + * reverb coeff: 1.0<br> + * reverb delay: 40.0<br> + * decay time: 1000.0<br> + * decay filter: 5000.0<> + * diffusion: 1.0<br> + * density: 1.0<br> + * reverb bounds: null<br> + * reverb order: 0<br> + * distance filtering: null (no filtering performed)<br> + * frequency scale factor: 1.0<br> + * velocity scale factor: 0.0<br> + * </ul> + */ + public AuralAttributes() { + // Just use default values + } + + /** + * Constructs and initializes a new AuralAttributes object using specified + * parameters including an array of Point2f for the distanceFilter. + * @param gain amplitude scale factor + * @param rolloff atmospheric (changing speed of sound) scale factor + * @param reflectionCoefficient reflective/absorptive factor applied to reflections + * @param reverbDelay delay time before start of reverberation + * @param reverbOrder limit to number of reflections added to reverb signal + * @param distanceFilter frequency cutoff + * @param frequencyScaleFactor applied to change of pitch + * @param velocityScaleFactor applied to velocity of sound in relation to listener + */ + public AuralAttributes(float gain, + float rolloff, + float reflectionCoefficient, + float reverbDelay, + int reverbOrder, + Point2f[] distanceFilter, + float frequencyScaleFactor, + float velocityScaleFactor) { + ((AuralAttributesRetained)this.retained).setAttributeGain(gain); + ((AuralAttributesRetained)this.retained).setRolloff(rolloff); + ((AuralAttributesRetained)this.retained).setReflectionCoefficient( + reflectionCoefficient); + ((AuralAttributesRetained)this.retained).setReverbDelay(reverbDelay); + ((AuralAttributesRetained)this.retained).setReverbOrder(reverbOrder); + ((AuralAttributesRetained)this.retained).setDistanceFilter( + distanceFilter); + ((AuralAttributesRetained)this.retained).setFrequencyScaleFactor( + frequencyScaleFactor); + ((AuralAttributesRetained)this.retained).setVelocityScaleFactor( + velocityScaleFactor); + } + /** + * Constructs and initializes a new AuralAttributes object using specified + * parameters with separate float arrays for components of distanceFilter. + * @param gain amplitude scale factor + * @param rolloff atmospheric (changing speed of sound) scale factor + * @param reflectionCoefficient reflection/absorption factor applied to reflections + * @param reverbDelay delay time before start of reverberation + * @param reverbOrder limit to number of reflections added to reverb signal + * @param distance filter frequency cutoff distances + * @param frequencyCutoff distance filter frequency cutoff + * @param frequencyScaleFactor applied to velocity/wave-length + * @param velocityScaleFactor applied to velocity of sound in relation to listener + */ + public AuralAttributes(float gain, + float rolloff, + float reflectionCoefficient, + float reverbDelay, + int reverbOrder, + float[] distance, + float[] frequencyCutoff, + float frequencyScaleFactor, + float velocityScaleFactor) { + ((AuralAttributesRetained)this.retained).setAttributeGain(gain); + ((AuralAttributesRetained)this.retained).setRolloff(rolloff); + ((AuralAttributesRetained)this.retained).setReflectionCoefficient( + reflectionCoefficient); + ((AuralAttributesRetained)this.retained).setReverbDelay(reverbDelay); + ((AuralAttributesRetained)this.retained).setReverbOrder(reverbOrder); + ((AuralAttributesRetained)this.retained).setDistanceFilter(distance, + frequencyCutoff); + ((AuralAttributesRetained)this.retained).setFrequencyScaleFactor( + frequencyScaleFactor); + ((AuralAttributesRetained)this.retained).setVelocityScaleFactor( + velocityScaleFactor); + } + + /** + * Constructs and initializes a new AuralAttributes object using specified + * parameters with separate float arrays for components of distanceFilter + * and full reverb parameters. + * @param gain amplitude scale factor + * @param rolloff atmospheric (changing speed of sound) scale factor + * @param reflectionCoefficient factor applied to early reflections + * @param reflectionDelay delay time before start of early reflections + * @param reverbCoefficient factor applied to late reflections + * @param reverbDelay delay time before start of late reverberation + * @param decayTime time (in milliseconds) reverb takes to decay to -60bD + * @param decayFilter reverb decay filter frequency cutoff + * @param diffusion percentage of echo dispersement between min and max + * @param density percentage of modal density between min and max + * @param distance filter frequency cutoff distances + * @param frequencyCutoff distance filter frequency cutoff + * @param frequencyScaleFactor applied to velocity/wave-length + * @param velocityScaleFactor applied to velocity of sound in relation to listener + * @since Java 3D 1.3 + */ + public AuralAttributes(float gain, + float rolloff, + float reflectionCoefficient, + float reflectionDelay, + float reverbCoefficient, + float reverbDelay, + float decayTime, + float decayFilter, + float diffusion, + float density, + float[] distance, + float[] frequencyCutoff, + float frequencyScaleFactor, + float velocityScaleFactor) { + ((AuralAttributesRetained)this.retained).setAttributeGain(gain); + ((AuralAttributesRetained)this.retained).setRolloff(rolloff); + ((AuralAttributesRetained)this.retained).setReflectionCoefficient( + reflectionCoefficient); + ((AuralAttributesRetained)this.retained).setReflectionDelay( + reflectionDelay); + ((AuralAttributesRetained)this.retained).setReverbCoefficient( + reverbCoefficient); + ((AuralAttributesRetained)this.retained).setReverbDelay( + reverbDelay); + ((AuralAttributesRetained)this.retained).setDecayTime(decayTime); + ((AuralAttributesRetained)this.retained).setDecayFilter(decayFilter); + ((AuralAttributesRetained)this.retained).setDiffusion(diffusion); + ((AuralAttributesRetained)this.retained).setDensity(density); + ((AuralAttributesRetained)this.retained).setDistanceFilter(distance, + frequencyCutoff); + ((AuralAttributesRetained)this.retained).setFrequencyScaleFactor( + frequencyScaleFactor); + ((AuralAttributesRetained)this.retained).setVelocityScaleFactor( + velocityScaleFactor); + } + + /** + * Creates the retained mode AuralAttributesRetained object that this + * component object will point to. + */ + void createRetained() { + this.retained = new AuralAttributesRetained(); + this.retained.setSource(this); + } + + /** **************************************** + * + * Attribute Gain + * + * ****************************************/ + /** + * Set Attribute Gain (amplitude) scale factor. + * @param gain scale factor applied to amplitude of direct and reflected sound + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAttributeGain(float gain) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ATTRIBUTE_GAIN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes0")); + ((AuralAttributesRetained)this.retained).setAttributeGain(gain); + } + + /** + * Retrieve Attribute Gain (amplitude). + * @return gain amplitude scale factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getAttributeGain() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ATTRIBUTE_GAIN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes1")); + return ((AuralAttributesRetained)this.retained).getAttributeGain(); + } + + /** + * Set Attribute Gain Rolloff. + * @param rolloff atmospheric gain scale factor (changing speed of sound) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setRolloff(float rolloff) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ROLLOFF_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes2")); + ((AuralAttributesRetained)this.retained).setRolloff(rolloff); + } + + /** + * Retrieve Attribute Gain Rolloff. + * @return rolloff atmospheric gain scale factor (changing speed of sound) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getRolloff() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ROLLOFF_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes3")); + return ((AuralAttributesRetained)this.retained).getRolloff(); + } + + /** + * Set Reflective Coefficient. + * Scales the amplitude of the early reflections of reverberated sounds + * @param coefficient reflection/absorption factor applied to reflections + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setReflectionCoefficient(float coefficient) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REFLECTION_COEFFICIENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes4")); + ((AuralAttributesRetained)this.retained).setReflectionCoefficient(coefficient); + } + + /** + * Retrieve Reflective Coefficient. + * @return reflection coeff reflection/absorption factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getReflectionCoefficient() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REFLECTION_COEFFICIENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes21")); + return ((AuralAttributesRetained)this.retained).getReflectionCoefficient(); + } + + /********************* + * + * Early Reflection Delay + * + ********************/ + /** + * Set early Refection Delay Time. + * In this form, the parameter specifies the time between the start of the + * direct, unreflected sound and the start of first order early reflections. + * In this method, this time is explicitly given in milliseconds. + * @param reflectionDelay delay time before start of reverberation + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setReflectionDelay(float reflectionDelay) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REFLECTION_DELAY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes22")); + ((AuralAttributesRetained)this.retained).setReflectionDelay(reflectionDelay); + } + + /** + * Retrieve Reflection Delay Time. + * @return reflection delay time + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public float getReflectionDelay() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REFLECTION_DELAY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes23")); + return ((AuralAttributesRetained)this.retained).getReflectionDelay(); + } + + /** ****************** + * + * Reverb Coefficient + * + ********************/ + /** + * Set Reverb Coefficient. + * Scale the amplitude of the late reflections including the decaying tail + * of reverberated sound. + * @param coefficient reflective/absorptive factor applied to late reflections + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setReverbCoefficient(float coefficient) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REVERB_COEFFICIENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes24")); + ((AuralAttributesRetained)this.retained).setReverbCoefficient(coefficient); + } + + /** + * Retrieve Reverb Coefficient. + * @return late reflection coeff. reflection/absorption factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public float getReverbCoefficient() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REVERB_COEFFICIENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes25")); + return ((AuralAttributesRetained)this.retained).getReverbCoefficient(); + } + + /********************* + * + * Reverberation Delay + * + ********************/ + /** + * Set Reverberation Delay Time. + * In this form, the parameter specifies the time between the start of the + * direct, unreflected sound and the start of reverberation. In this + * method, this time is explicitly given in milliseconds. + * @param reverbDelay delay time before start of reverberation + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setReverbDelay(float reverbDelay) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REVERB_DELAY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes5")); + ((AuralAttributesRetained)this.retained).setReverbDelay(reverbDelay); + } + + /** + * Retrieve Reverberation Delay Time. + * @return reverb delay time + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getReverbDelay() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REVERB_DELAY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes7")); + return ((AuralAttributesRetained)this.retained).getReverbDelay(); + } + + /** ****************** + * + * Decay Time + * + ********************/ + /** + * Set Decay Time + * Length of time from the start of late reflections reverberation volume + * takes to decay to effective zero (-60 dB of initial signal amplitude). + * @param decayTime of late reflections (reverb) in milliseconds + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setDecayTime(float decayTime) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DECAY_TIME_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes28")); + ((AuralAttributesRetained)this.retained).setDecayTime(decayTime); + } + + /** + * Retrieve Decay Time. + * @return reverb decay time + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public float getDecayTime() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DECAY_TIME_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes29")); + return ((AuralAttributesRetained)this.retained).getDecayTime(); + } + + /** ****************** + * + * Decay Filter + * + ********************/ + /** + * Set Decay Filter + * In this form, reverberation decay filtering is defined as a low-pass + * filter, starting at the given reference frequency. This allows for + * higher frequencies to be attenuated at a different (typically faster) + * rate than lower frequencies. + * @param frequencyCutoff of reverberation decay low-pass filter + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setDecayFilter(float frequencyCutoff) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DECAY_FILTER_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes30")); + ((AuralAttributesRetained)this.retained).setDecayFilter(frequencyCutoff); + } + + /** + * Retrieve Decay Filter. + * @return reverb decay filter cutoff frequency + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public float getDecayFilter() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DECAY_FILTER_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes31")); + return ((AuralAttributesRetained)this.retained).getDecayFilter(); + } + + /** ****************** + * + * Diffusion + * + ********************/ + /** + * Set Diffusion. + * Sets the echo dispersement of reverberation to an amount between + * the minimum (0.0) to the maximum (1.0) available. Changing this + * increases/decreases the 'smoothness' of reverb decay. + * @param ratio reverberation echo dispersement factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setDiffusion(float ratio) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DIFFUSION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes32")); + ((AuralAttributesRetained)this.retained).setDiffusion(ratio); + } + + /** + * Retrieve Diffusion. + * @return reverb diffusion ratio + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public float getDiffusion() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIFFUSION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes33")); + return ((AuralAttributesRetained)this.retained).getDiffusion(); + } + + /** ****************** + * + * Density + * + ********************/ + /** + * Set Density. + * Sets the density of reverberation to an amount between + * the minimum (0.0) to the maximum (1.0) available. Changing this + * effects the spectral coloration (timbre) of late reflections. + * @param ratio reverberation modal density factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setDensity(float ratio) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DENSITY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes34")); + ((AuralAttributesRetained)this.retained).setDensity(ratio); + } + + /** + * Retrieve Density. + * @return reverb density + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public float getDensity() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DENSITY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes35")); + return ((AuralAttributesRetained)this.retained).getDensity(); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setReverbBounds(Bounds)</code> + */ + public void setReverbDelay(Bounds reverbVolume) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REVERB_DELAY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes5")); + ((AuralAttributesRetained)this.retained).setReverbBounds(reverbVolume); + } + + /** + * Set Reverberation Bounds volume. + * In this form, the reverberation bounds volume parameter is used to + * calculate the reverberation Delay and Decay times. Specification + * of a non-null bounding volume causes the explicit values given for + * Reverb Delay and Decay to be overridden by the implicit values + * calculated from these bounds. + * ALLOW_REVERB_DELAY_WRITE flag used setting capability of this method. + * @param reverbVolume the bounding region + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.2 + */ + public void setReverbBounds(Bounds reverbVolume) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REVERB_DELAY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes26")); + ((AuralAttributesRetained)this.retained).setReverbBounds(reverbVolume); + } + + /** + * Retrieve Reverberation Delay Bounds volume. + * @return reverb bounds volume that defines the Reverberation space and + * indirectly the delay/decay + * ALLOW_REVERB_DELAY_READ flag used setting capability of this method. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.2 + */ + public Bounds getReverbBounds() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REVERB_DELAY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes27")); + return ((AuralAttributesRetained)this.retained).getReverbBounds(); + } + + /** ******************* + * + * Reverberation Order + * + ********************/ + /** + * Set Reverberation Order + * This parameter limits the number of times reflections are added + * to the reverberation being rendered. + * A non-positive value specifies an unbounded number of reflections. + * @param reverbOrder limit to the number of times reflections added to reverb signal + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setReverbOrder(int reverbOrder) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REVERB_ORDER_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes8")); + ((AuralAttributesRetained)this.retained).setReverbOrder(reverbOrder); + } + + /** + * Retrieve Reverberation Order + * @return reverb order + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getReverbOrder() { + if (!this.getCapability(ALLOW_REVERB_ORDER_READ)) + if (isLiveOrCompiled()) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes9")); + return ((AuralAttributesRetained)this.retained).getReverbOrder(); + } + + /** + * Set Distance Filter using a single array containing distances and + * frequency cutoff as pairs of values as a single array of Point2f. + * @param attenuation array of pairs of distance and frequency cutoff + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDistanceFilter(Point2f[] attenuation) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DISTANCE_FILTER_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes10")); + ((AuralAttributesRetained)this.retained).setDistanceFilter(attenuation); + } + + /** + * Set Distance Filter using separate arrays for distances and frequency + * cutoff. The distance and frequencyCutoff arrays should be of the same + * length. If the frequencyCutoff array length is greater than the distance + * array length, the frequencyCutoff array elements beyond the length of + * the distance array are ignored. If the frequencyCutoff array is shorter + * than the distance array, the last frequencyCutoff array value is repeated + * to fill an array of length equal to distance array. + * @param distance array of float distance with corresponding cutoff values + * @param frequencyCutoff array of frequency cutoff values in Hertz + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDistanceFilter(float[] distance, + float[] frequencyCutoff) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DISTANCE_FILTER_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes10")); + ((AuralAttributesRetained)this.retained).setDistanceFilter( + distance, frequencyCutoff ); + } + + /** + * Retrieve Distance Filter array length. + * @return attenuation array length + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getDistanceFilterLength() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DISTANCE_FILTER_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes12")); + return (((AuralAttributesRetained)this.retained).getDistanceFilterLength()); + } + /** + * Retrieve Distance Filter as a single array containing distances + * and frequency cutoff. The distance filter is copied into + * the specified array. + * The array must be large enough to hold all of the points. + * The individual array elements must be allocated by the caller. + * @return attenuation array of pairs of distance and frequency cutoff values + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDistanceFilter(Point2f[] attenuation) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DISTANCE_FILTER_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes12")); + ((AuralAttributesRetained)this.retained).getDistanceFilter(attenuation); + } + + /** + * Retrieve Distance Filter in separate distance and frequency cutoff arrays. + * The arrays must be large enough to hold all of the distance + * and frequency cutoff values. + * @param distance array + * @param frequencyCutoff cutoff array + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDistanceFilter(float[] distance, + float[] frequencyCutoff) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DISTANCE_FILTER_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes12")); + ((AuralAttributesRetained)this.retained).getDistanceFilter( + distance, frequencyCutoff); + } + + /** + * This parameter specifies a scale factor applied to the frequency + * of sound during rendering playback. If the Doppler effect is + * disabled, this scale factor can be used to increase or + * decrease the original pitch of the sound. During rendering, + * this scale factor expands or contracts the usual frequency shift + * applied to the sound source due to Doppler calculations. + * Valid values are >= 0.0. + * A value of zero causes playing sounds to pause. + * @param frequencyScaleFactor factor applied to change of frequency + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setFrequencyScaleFactor(float frequencyScaleFactor) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_FREQUENCY_SCALE_FACTOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes15")); + ((AuralAttributesRetained)this.retained).setFrequencyScaleFactor( + frequencyScaleFactor); + } + + /** + * Retrieve Frequency Scale Factor. + * @return scaleFactor factor applied to change of frequency + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getFrequencyScaleFactor() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_FREQUENCY_SCALE_FACTOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes17")); + return ((AuralAttributesRetained)this.retained).getFrequencyScaleFactor(); + } + + /** ****************************** + * + * Velocity Scale Factor + * + *********************************/ + /** + * Set Velocity scale factor applied during Doppler Effect calculation. + * This parameter specifies a scale factor applied to the velocity of + * the sound relative to the listener's position and movement in relation + * to the sound's position and movement. This scale factor is multipled + * by the calculated velocity portion of the Doppler effect equation used + * during sound rendering. + * A value of zero disables Doppler calculations. + * @param velocityScaleFactor applied to velocity of sound in relation + * to listener + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setVelocityScaleFactor(float velocityScaleFactor) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_VELOCITY_SCALE_FACTOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes19")); + ((AuralAttributesRetained)this.retained).setVelocityScaleFactor( + velocityScaleFactor); + } + + /** + * Retrieve Velocity Scale Factor used to calculate Doppler Effect. + * @return scale factor applied to Doppler velocity of sound + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getVelocityScaleFactor() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_VELOCITY_SCALE_FACTOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("AuralAttributes20")); + return ((AuralAttributesRetained)this.retained).getVelocityScaleFactor(); + } + + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>cloneNodeComponent(boolean forceDuplicate)</code> + */ + public NodeComponent cloneNodeComponent() { + AuralAttributes a = new AuralAttributes(); + a.duplicateNodeComponent(this, this.forceDuplicate); + return a; + } + + + /** + * Copies all AuralAttributes information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, + forceDuplicate); + + AuralAttributesRetained aural = (AuralAttributesRetained) originalNodeComponent.retained; + AuralAttributesRetained rt = (AuralAttributesRetained) retained; + + rt.setAttributeGain(aural.getAttributeGain()); + rt.setRolloff(aural.getRolloff()); + rt.setReflectionCoefficient(aural.getReflectionCoefficient()); + rt.setReverbDelay(aural.getReverbDelay()); + rt.setReverbOrder(aural.getReverbOrder()); + rt.setReverbBounds(aural.getReverbBounds()); + rt.setFrequencyScaleFactor(aural.getFrequencyScaleFactor()); + rt.setVelocityScaleFactor(aural.getVelocityScaleFactor()); + int len = aural.getDistanceFilterLength(); + float distance[] = new float[len]; + float frequencyCutoff[] = new float[len]; + aural.getDistanceFilter(distance, frequencyCutoff); + rt.setDistanceFilter(distance, frequencyCutoff); + } +} diff --git a/src/classes/share/javax/media/j3d/AuralAttributesRetained.java b/src/classes/share/javax/media/j3d/AuralAttributesRetained.java new file mode 100644 index 0000000..7f118b5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/AuralAttributesRetained.java @@ -0,0 +1,654 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Hashtable; +import javax.vecmath.Point2f; + +/** + * The AuralAttributesRetained object defines all rendering state that can + * be set as a component object of a retained Soundscape node. + */ +class AuralAttributesRetained extends NodeComponentRetained { + + /** + * Gain Scale Factor applied to source with this attribute + */ + float attributeGain = 1.0f; // Valid values are >= 0.0. + + /** + * Atmospheric Rolloff - speed of sound - coeff + * Normal gain attenuation based on distance of sound from + * listener is scaled by a rolloff factor, which can increase + * or decrease the usual inverse-distance-square value. + */ + float rolloff = 1.0f; // Valid values are >= 0.0 + static final float SPEED_OF_SOUND = 0.344f; // in meters/milliseconds + + /* + * Reverberation + * + * Within Java 3D's model for auralization, the components to + * reverberation for a particular space are: + * Reflection and Reverb Coefficients - + * attenuation of sound (uniform for all frequencies) due to + * absorption of reflected sound off materials within the + * listening space. + * Reflection and Reverb Delay - + * approximating time from the start of the direct sound that + * initial early and late reflection waves take to reach listener. + * Reverb Decay - + * approximating time from the start of the direct sound that + * reverberation is audible. + */ + + /** + * Coefficients for reverberation + * The (early) Reflection and Reverberation coefficient scale factors + * are used to approximate the reflective/absorptive characteristics + * of the surfaces in this bounded Auralizaton environment. + * Theses scale factors is applied to sound's amplitude regardless + * of sound's position. + * Value of 1.0 represents complete (unattenuated) sound reflection. + * Value of 0.0 represents full absorption; reverberation is disabled. + */ + float reflectionCoefficient = 0.0f; // Range of values 0.0 to 1.0 + float reverbCoefficient = 1.0f; // Range of values 0.0 to 1.0 + + /** + * Time Delays in milliseconds + * Set with either explicitly with time, or impliciticly by supplying + * bounds volume and having the delay time calculated. + * Bounds of reverberation space does not have to be the same as + * Attribute bounds. + */ + float reflectionDelay = 20.0f; // in milliseconds + float reverbDelay = 40.0f; // in milliseconds + Bounds reverbBounds = null; + + /** + * Decay parameters + * Length and timbre of reverb decay tail + */ + float decayTime = 1000.0f; // in milliseconds + float decayFilter = 5000.0f; // low-pass cutoff frequency + + /** + * Reverb Diffusion and Density ratios (0=min, 1=max) + */ + float diffusion = 1.0f; + float density = 1.0f; + + /** + * Reverberation order + * This limits the number of Reverberation iterations executed while + * sound is being reverberated. As long as reflection coefficient is + * small enough, the reverberated sound decreases (as it would naturally) + * each successive iteration. + * Value of > zero defines the greatest reflection order to be used by + * the reverberator. + * All positive values are used as the number of loop iteration. + * Value of <= zero signifies that reverberation is to loop until reverb + * gain reaches zero (-60dB or 1/1000 of sound amplitude). + */ + int reverbOrder = 0; + + /** + * Distance Filter + * Each sound source is attenuated by a filter based on it's distance + * from the listener. + * For now the only supported filterType will be LOW_PASS frequency cutoff. + * At some time full FIR filtering will be supported. + */ + static final int NO_FILTERING = -1; + static final int LOW_PASS = 1; + + int filterType = NO_FILTERING; + float[] distance = null; + float[] frequencyCutoff = null; + + /** + * Doppler Effect parameters + * Between two snapshots of the head and sound source positions some + * delta time apart, the distance between head and source is compared. + * If there has been no change in the distance between head and sound + * source over this delta time: + * f' = f + * + * If there has been a change in the distance between head and sound: + * f' = f * Af * v + * + * When head and sound are moving towards each other then + * | (S * Ar) + (deltaV(h,t) * Av) | + * v = | -------------------------------- | + * | (S * Ar) - (deltaV(s,t) * Av) | + * + * When head and sound are moving away from each other then + * | (S * Ar) - (deltaV(h,t) * Av) | + * v = | -------------------------------- | + * | (S * Ar) + (deltaV(s,t) * Av) | + * + * + * Af = AuralAttribute frequency scalefactor + * Ar = AuralAttribute rolloff scalefactor + * Av = AuralAttribute velocity scalefactor + * deltaV = delta velocity + * f = frequency of sound + * h = Listeners head position + * v = Ratio of delta velocities + * Vh = Vector from center ear to sound source + * S = Speed of sound + * s = Sound source position + * t = time + * + * If adjusted velocity of head or adjusted velocity of sound is + * greater than adjusted speed of sound, f' is undefined. + */ + /** + * Frequency Scale Factor + * used to increase or reduce the change of frequency associated + * with normal rate of playback. + * Value of zero causes sounds to be paused. + */ + float frequencyScaleFactor = 1.0f; + /** + * Velocity Scale Factor + * Float value applied to the Change of distance between Sound Source + * and Listener over some delta time. Non-zero if listener moving + * even if sound is not. Value of zero implies no Doppler applied. + */ + float velocityScaleFactor = 0.0f; + + /** + * This boolean is set when something changes in the attributes + */ + boolean aaDirty = true; + + /** + * The mirror copy of this AuralAttributes. + */ + AuralAttributesRetained mirrorAa = null; + + /** + ** Debug print mechanism for Sound nodes + **/ + static final // 'static final' so compiler doesn't include debugPrint calls + boolean debugFlag = false; + + static final // 'static final' so internal error message are not compiled + boolean internalErrors = false; + + void debugPrint(String message) { + if (debugFlag) // leave test in in case debugFlag made non-static final + System.out.println(message); + } + + + // **************************************** + // + // Set and Get individual attribute values + // + // **************************************** + + /** + * Set Attribute Gain (amplitude) + * @param gain scale factor applied to amplitude + */ + void setAttributeGain(float gain) { + this.attributeGain = gain; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Attribute Gain (amplitude) + * @return gain amplitude scale factor + */ + float getAttributeGain() { + return this.attributeGain; + } + + /** + * Set Attribute Gain Rolloff + * @param rolloff atmospheric gain scale factor (changing speed of sound) + */ + void setRolloff(float rolloff) { + this.rolloff = rolloff; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Attribute Gain Rolloff + * @return rolloff atmospheric gain scale factor (changing speed of sound) + */ + float getRolloff() { + return this.rolloff; + } + + /** + * Set Reflective Coefficient + * @param reflectionCoefficient reflection/absorption factor applied to + * early reflections. + */ + void setReflectionCoefficient(float reflectionCoefficient) { + this.reflectionCoefficient = reflectionCoefficient; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Reflective Coefficient + * @return reflection coeff reflection/absorption factor applied to + * early reflections. + */ + float getReflectionCoefficient() { + return this.reflectionCoefficient; + } + + /** + * Set Reflection Delay Time + * @param reflectionDelay time before the start of early (first order) + * reflections. + */ + void setReflectionDelay(float reflectionDelay) { + this.reflectionDelay = reflectionDelay; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Reflection Delay Time + * @return reflection delay time + */ + float getReflectionDelay() { + return this.reflectionDelay; + } + + /** + * Set Reverb Coefficient + * @param reverbCoefficient reflection/absorption factor applied to + * late reflections. + */ + void setReverbCoefficient(float reverbCoefficient) { + this.reverbCoefficient = reverbCoefficient; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Reverb Coefficient + * @return reverb coeff reflection/absorption factor applied to late + * reflections. + */ + float getReverbCoefficient() { + return this.reverbCoefficient; + } + + /** + * Set Revereration Delay Time + * @param reverbDelay time between each order of reflection + */ + void setReverbDelay(float reverbDelay) { + this.reverbDelay = reverbDelay; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Revereration Delay Time + * @return reverb delay time between each order of reflection + */ + float getReverbDelay() { + return this.reverbDelay; + } + /** + * Set Decay Time + * @param decayTime length of time reverb takes to decay + */ + void setDecayTime(float decayTime) { + this.decayTime = decayTime; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Revereration Decay Time + * @return reverb delay time + */ + float getDecayTime() { + return this.decayTime; + } + + /** + * Set Decay Filter + * @param decayFilter frequency referenced used in low-pass filtering + */ + void setDecayFilter(float decayFilter) { + this.decayFilter = decayFilter; + this.aaDirty = true; + notifyUsers(); + } + + /** + * Retrieve Revereration Decay Filter + * @return reverb delay Filter + */ + float getDecayFilter() { + return this.decayFilter; + } + + /** + * Set Reverb Diffusion + * @param diffusion ratio between min and max device diffusion settings + */ + void setDiffusion(float diffusion) { + this.diffusion = diffusion; + this.aaDirty = true; + notifyUsers(); + } + + /** + * Retrieve Revereration Decay Diffusion + * @return reverb diffusion + */ + float getDiffusion() { + return this.diffusion; + } + + /** + * Set Reverb Density + * @param density ratio between min and max device density settings + */ + void setDensity(float density) { + this.density = density; + this.aaDirty = true; + notifyUsers(); + } + + /** + * Retrieve Revereration Density + * @return reverb density + */ + float getDensity() { + return this.density; + } + + + /** + * Set Revereration Bounds + * @param reverbVolume bounds used to approximate reverb time. + */ + synchronized void setReverbBounds(Bounds reverbVolume) { + this.reverbBounds = reverbVolume; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Revereration Delay Bounds volume + * @return reverb bounds volume that defines the Reverberation space and + * indirectly the delay + */ + Bounds getReverbBounds() { + return this.reverbBounds; + } + + /** + * Set Reverberation Order of Reflections + * @param reverbOrder number of times reflections added to reverb signal + */ + void setReverbOrder(int reverbOrder) { + this.reverbOrder = reverbOrder; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Reverberation Order of Reflections + * @return reverb order number of times reflections added to reverb signal + */ + int getReverbOrder() { + return this.reverbOrder; + } + + /** + * Set Distance Filter (based on distances and frequency cutoff) + * @param attenuation array of pairs defining distance frequency cutoff + */ + synchronized void setDistanceFilter(Point2f[] attenuation) { + if (attenuation == null) { + this.filterType = NO_FILTERING; + return; + } + int attenuationLength = attenuation.length; + if (attenuationLength == 0) { + this.filterType = NO_FILTERING; + return; + } + this.filterType = LOW_PASS; + // Reallocate every time unless size of new array equal old array + if ( distance == null || + (distance != null && (distance.length != attenuationLength) ) ) { + this.distance = new float[attenuationLength]; + this.frequencyCutoff = new float[attenuationLength]; + } + for (int i = 0; i< attenuationLength; i++) { + this.distance[i] = attenuation[i].x; + this.frequencyCutoff[i] = attenuation[i].y; + } + this.aaDirty = true; + notifyUsers(); + } + /** + * Set Distance Filter (based on distances and frequency cutoff) using + * separate arrays + * @param distance array containing distance values + * @param filter array containing low-pass frequency cutoff values + */ + synchronized void setDistanceFilter(float[] distance, float[] filter) { + if (distance == null || filter == null) { + this.filterType = NO_FILTERING; + return; + } + int distanceLength = distance.length; + int filterLength = filter.length; + if (distanceLength == 0 || filterLength == 0) { + this.filterType = NO_FILTERING; + return; + } + // Reallocate every time unless size of new array equal old array + if ( this.distance == null || + ( this.distance != null && + (this.distance.length != filterLength) ) ) { + this.distance = new float[distanceLength]; + this.frequencyCutoff = new float[distanceLength]; + } + this.filterType = LOW_PASS; + // Copy the distance array into nodes field + System.arraycopy(distance, 0, this.distance, 0, distanceLength); + // Copy the filter array an array of same length as the distance array + if (distanceLength <= filterLength) { + System.arraycopy(filter, 0, this.frequencyCutoff,0, distanceLength); + } + else { + System.arraycopy(filter, 0, this.frequencyCutoff, 0, filterLength); + // Extend filter array to length of distance array by + // replicate last filter values. + for (int i=filterLength; i< distanceLength; i++) { + this.frequencyCutoff[i] = filter[filterLength - 1]; + } + } + if (debugFlag) { + debugPrint("AAR setDistanceFilter(D,F)"); + for (int jj=0;jj<distanceLength;jj++) { + debugPrint(" from distance, freq = " + distance[jj] + ", " + + filter[jj]); + debugPrint(" into distance, freq = " + this.distance[jj] + ", " + + this.frequencyCutoff[jj]); + } + } + this.aaDirty = true; + notifyUsers(); + } + + /** + * Retrieve Distance Filter array length + * @return attenuation array length + */ + int getDistanceFilterLength() { + if (distance == null) + return 0; + else + return this.distance.length; + } + + + /** + * Retrieve Distance Filter (distances and frequency cutoff) + * @return attenaution pairs of distance and frequency cutoff filter + */ + void getDistanceFilter(Point2f[] attenuation) { + // Write into existing param array already allocated + if (attenuation == null) + return; + if (this.distance == null || this.frequencyCutoff == null) + return; + // The two filter attenuation arrays length should be the same + // We can assume that distance and filter lengths are the same + // and are non-zero. + int distanceLength = this.distance.length; + // check that attenuation array large enough to contain + // auralAttribute arrays + if (distanceLength > attenuation.length) + distanceLength = attenuation.length; + for (int i=0; i< distanceLength; i++) { + attenuation[i].x = this.distance[i]; + if (filterType == NO_FILTERING) + attenuation[i].y = Sound.NO_FILTER; + else if (filterType == LOW_PASS) + attenuation[i].y = this.frequencyCutoff[i]; + if (debugFlag) + debugPrint("AAR: getDistF: " + attenuation[i].x + ", " + + attenuation[i].y); + } + } + /** + * Retrieve Distance Filter as arrays distances and frequency cutoff array + * @param distance array of float values + * @param frequencyCutoff array of float cutoff filter values in Hertz + */ + void getDistanceFilter(float[] distance, float[] filter) { + // Write into existing param arrays already allocated + if (distance == null || filter == null) + return; + if (this.distance == null || this.frequencyCutoff == null) + return; + int distanceLength = this.distance.length; + // check that distance parameter large enough to contain auralAttribute + // distance array + // We can assume that distance and filter lengths are the same + // and are non-zero. + if (distance.length < distanceLength) + // parameter array not large enough to hold all this.distance data + distanceLength = distance.length; + System.arraycopy(this.distance, 0, distance, 0, distanceLength); + if (debugFlag) + debugPrint("AAR getDistanceFilter(D,F) " + this.distance[0]); + int filterLength = this.frequencyCutoff.length; + if (filter.length < filterLength) + // parameter array not large enough to hold all this.filter data + filterLength = filter.length; + if (filterType == NO_FILTERING) { + for (int i=0; i< filterLength; i++) + filter[i] = Sound.NO_FILTER; + } + if (filterType == LOW_PASS) { + System.arraycopy(this.frequencyCutoff, 0, filter, 0, filterLength); + } + if (debugFlag) + debugPrint(", " + this.frequencyCutoff[0]); + } + + /** + * Set Frequency Scale Factor + * @param frequencyScaleFactor factor applied to sound's base frequency + */ + void setFrequencyScaleFactor(float frequencyScaleFactor) { + this.frequencyScaleFactor = frequencyScaleFactor; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Frequency Scale Factor + * @return frequency scale factor applied to sound's base frequency + */ + float getFrequencyScaleFactor() { + return this.frequencyScaleFactor; + } + + /** + * Set Velocity ScaleFactor used in calculating Doppler Effect + * @param velocityScaleFactor applied to velocity of sound in relation to listener + */ + void setVelocityScaleFactor(float velocityScaleFactor) { + this.velocityScaleFactor = velocityScaleFactor; + this.aaDirty = true; + notifyUsers(); + } + /** + * Retrieve Velocity ScaleFactor used in calculating Doppler Effect + * @return velocity scale factor + */ + float getVelocityScaleFactor() { + return this.velocityScaleFactor; + } + + synchronized void reset(AuralAttributesRetained aa) { + int i; + + this.attributeGain = aa.attributeGain; + this.rolloff = aa.rolloff; + this.reflectionCoefficient = aa.reflectionCoefficient; + this.reverbCoefficient = aa.reverbCoefficient; + this.reflectionDelay = aa.reflectionDelay; + this.reverbDelay = aa.reverbDelay; + this.reverbBounds = aa.reverbBounds; + this.reverbOrder = aa.reverbOrder; + this.decayTime = aa.decayTime; + this.decayFilter = aa.decayFilter; + this.diffusion = aa.diffusion; + this.density = aa.density; + this.frequencyScaleFactor = aa.frequencyScaleFactor; + this.velocityScaleFactor = aa.velocityScaleFactor; + + if (aa.distance != null) { + this.distance = new float[aa.distance.length]; + if (debugFlag) + debugPrint("reset aa; aa.distance.length = " + this.distance.length); + System.arraycopy(aa.distance, 0, this.distance, 0, this.distance.length); + } + else + if (debugFlag) + debugPrint("reset aa; aa.distance = null"); + if (aa.frequencyCutoff != null) { + this.frequencyCutoff = new float[aa.frequencyCutoff.length]; + if (debugFlag) + debugPrint("reset aa; aa.frequencyCutoff.length = " + this.frequencyCutoff.length); + System.arraycopy(aa.frequencyCutoff, 0, this.frequencyCutoff, 0, + this.frequencyCutoff.length); + } + else + if (debugFlag) + debugPrint("reset aa; aa.frequencyCutoff = null"); + // TODO: (Enhancement) Why are these dirtyFlag cleared rather than aa->this + this.aaDirty = false; + aa.aaDirty = false; + } + + void update(AuralAttributesRetained aa) { + this.reset(aa); + } + +} diff --git a/src/classes/share/javax/media/j3d/BHInsertStructure.java b/src/classes/share/javax/media/j3d/BHInsertStructure.java new file mode 100644 index 0000000..81eb7be --- /dev/null +++ b/src/classes/share/javax/media/j3d/BHInsertStructure.java @@ -0,0 +1,139 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +class BHInsertStructure { + + static boolean debug = false; + static boolean debug2 = false; + + Random randomNumber; + ArrayList[] bhListArr = null; + ArrayList[] oldBhListArr = null; + BHNode[] bhListArrRef = null; + BHNode[] oldBhListArrRef = null; + int bhListArrCnt = 0; + int bhListArrMaxCnt = 0; + int blockSize = 0; + + BHInsertStructure(int length) { + randomNumber = new Random(0); + + if(length > 50) { + length = 50; + } + + blockSize = 50; + bhListArr = new ArrayList[length]; + bhListArrRef = new BHNode[length]; + bhListArrCnt = 0; + bhListArrMaxCnt = length; + + } + + void clear() { + + for(int i=0; i< bhListArrCnt; i++) { + bhListArr[i].clear(); + bhListArrRef[i] = null; + } + bhListArrCnt = 0; + } + + void lookupAndInsert(BHNode parent, BHNode child) { + boolean found = false; + + for ( int i=0; i<bhListArrCnt; i++ ) { + // check for current parent + if ( bhListArrRef[i] == parent ) { + // place child element in currents array of children + bhListArr[i].add(child); + found = true; + break; + } + } + + if ( !found ) { + + if(bhListArrCnt >= bhListArrMaxCnt) { + // allocate a bigger array here.... + if(debug) + System.out.println("(1) Expanding bhListArr array ..."); + bhListArrMaxCnt += blockSize; + oldBhListArr = bhListArr; + oldBhListArrRef = bhListArrRef; + + bhListArr = new ArrayList[bhListArrMaxCnt]; + bhListArrRef = new BHNode[bhListArrMaxCnt]; + System.arraycopy(oldBhListArr, 0, bhListArr, 0, oldBhListArr.length); + System.arraycopy(oldBhListArrRef, 0, bhListArrRef, 0, + oldBhListArrRef.length); + } + + bhListArrRef[bhListArrCnt] = parent; + bhListArr[bhListArrCnt] = new ArrayList(); + bhListArr[bhListArrCnt].add(child); + bhListArrCnt++; + } + + } + + void updateBoundingTree(BHTree bhTree) { + + // based on the data in this stucture, update the tree such that + // all things work out now .. i.e for each element of the array list + // of bhListArr ... create a new reclustered tree. + int size, cnt; + BHNode child1, child2; + + for ( int i=0; i < bhListArrCnt; i++ ) { + // extract and form an array of all children : l, r, and n1 ... nk + cnt = 0; + child1 = ((BHInternalNode)(bhListArrRef[i])).getLeftChild(); + child2 = ((BHInternalNode)(bhListArrRef[i])).getRightChild(); + if(child1 != null) cnt++; + if(child2 != null) cnt++; + + size = bhListArr[i].size(); + + BHNode bhArr[] = new BHNode[cnt + size]; + + bhListArr[i].toArray(bhArr); + + //reset cnt, so that we can reuse it. + cnt = 0; + if(child1 != null) { + bhArr[size] = child1; + cnt++; + bhArr[size + cnt] = child2; + } + + if(debug2) + if((child1 == null) || (child2 == null)) { + System.out.println("child1 or child2 is null ..."); + System.out.println("This is bad, it shouldn't happen"); + + } + + ((BHInternalNode)(bhListArrRef[i])).setRightChild(null); + ((BHInternalNode)(bhListArrRef[i])).setLeftChild(null); + + bhTree.cluster((BHInternalNode)bhListArrRef[i], bhArr); + } + } + +} + + diff --git a/src/classes/share/javax/media/j3d/BHInternalNode.java b/src/classes/share/javax/media/j3d/BHInternalNode.java new file mode 100644 index 0000000..7c7a0b2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BHInternalNode.java @@ -0,0 +1,196 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +class BHInternalNode extends BHNode { + + static boolean debug2 = true; + + BHNode rChild; + BHNode lChild; + + BHInternalNode() { + super(); + nodeType = BH_TYPE_INTERNAL; + this.rChild = null; + this.lChild = null; + } + + BHInternalNode(BHNode parent) { + super(parent); + nodeType = BH_TYPE_INTERNAL; + this.rChild = null; + this.lChild = null; + } + + BHInternalNode(BHNode parent, BHNode rChild, BHNode lChild) { + super(parent); + nodeType = BH_TYPE_INTERNAL; + this.rChild = rChild; + this.lChild = lChild; + } + + BHInternalNode(BHNode parent, BoundingBox bHull) { + super(parent, bHull); + nodeType = BH_TYPE_INTERNAL; + this.rChild = null; + this.lChild = null; + } + + BHInternalNode(BHNode parent, BHNode rChild, BHNode lChild, BoundingBox bHull) { + super(parent, bHull); + nodeType = BH_TYPE_INTERNAL; + this.rChild = rChild; + this.lChild = lChild; + } + + BHNode getLeftChild() { + return (BHNode) lChild; + } + + BHNode getRightChild() { + return (BHNode) rChild; + } + + void setLeftChild(BHNode child) { + lChild = child; + } + + void setRightChild(BHNode child) { + rChild = child; + } + + void computeBoundingHull(BoundingBox bHull) { + computeBoundingHull(); + bHull.set(this.bHull); + } + + void computeBoundingHull() { + BoundingBox rChildBound = null; + BoundingBox lChildBound = null; + int i; + + if((lChild==null) && (rChild==null)) { + bHull = null; + return; + } + + if(lChild != null) + lChildBound = lChild.getBoundingHull(); + + if(rChild != null) + rChildBound = rChild.getBoundingHull(); + + if(bHull == null) + bHull = new BoundingBox(); + + // Since left child is null. bHull is equal to right child's Hull. + if(lChild == null) { + bHull.set(rChildBound); + return; + } + + // Since right child is null. bHull is equal to left child's Hull. + if(rChild == null) { + bHull.set(lChildBound); + return; + } + + // Compute the combined bounds of the children. + bHull.set(rChildBound); + bHull.combine(lChildBound); + + } + + void updateMarkedBoundingHull() { + + if(mark == false) + return; + + rChild.updateMarkedBoundingHull(); + lChild.updateMarkedBoundingHull(); + computeBoundingHull(); + mark = false; + + } + + // this method inserts a single element into the tree given the stipulation + // that the current tree node already contains the child ... 3 cases + // one --node is inside the left child, and not inside the right + // so recurse placing it inside the left child + // two -- node is not inside the left but is inside the right + // recurse placing it inside the right child + // three -- node is not inside either one, added it to the current + // element + + void insert( BHNode node, BHInsertStructure insertStructure ) { + // NOTE: the node must already be inside this node if its not then fail. + if(debug2) + if ( !this.isInside(node.bHull) ) { + System.out.println("Incorrect use of insertion, current node"); + System.out.println("must contain the input element ..."); + } + + boolean insideRightChild = false; + boolean insideLeftChild = false; + + // leaf children are considered inpenetrable for insert so returns false + if(this.rChild.nodeType == BHNode.BH_TYPE_LEAF) { + insideRightChild = false; + } else { + insideRightChild = this.rChild.isInside(node.bHull); + } + if(this.lChild.nodeType == BHNode.BH_TYPE_LEAF) { + insideLeftChild = false; + } else { + insideLeftChild = this.lChild.isInside(node.bHull); + } + + if ( insideLeftChild && !insideRightChild ) { + ((BHInternalNode)this.lChild).insert(node, insertStructure); + } else if ( !insideLeftChild && insideRightChild ) { + ((BHInternalNode)this.rChild).insert(node, insertStructure); + } else if ( insideLeftChild && insideRightChild ) { + // choose randomly to put it in the left or right + if ( insertStructure.randomNumber.nextBoolean() ) { + ((BHInternalNode)this.lChild).insert(node, insertStructure); + } else { + ((BHInternalNode)this.rChild).insert(node, insertStructure); + } + } else { + // doesn't fit in either one .... + // lookup the current node this in the auxilaryInsertStructure + // if it appears then add element to the array of sub elements + // if not then allocate a new element to the array + insertStructure.lookupAndInsert(this, node); + } + } + + void destroyTree(BHNode[] bhArr, int[] index) { + + if(rChild != null) + rChild.destroyTree(bhArr, index); + + if(lChild != null) + lChild.destroyTree(bhArr, index); + + rChild = null; + lChild = null; + + // add to free list ... + FreeListManager.freeObject(FreeListManager.BHINTERNAL, this); + } +} + diff --git a/src/classes/share/javax/media/j3d/BHLeafInterface.java b/src/classes/share/javax/media/j3d/BHLeafInterface.java new file mode 100644 index 0000000..bf121ac --- /dev/null +++ b/src/classes/share/javax/media/j3d/BHLeafInterface.java @@ -0,0 +1,26 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +interface BHLeafInterface { + + abstract BoundingBox computeBoundingHull(); + + abstract boolean isEnable(); + + abstract boolean isEnable(int visibilityPolicy); + + // Can't use getLocale, it is used by BranchGroupRetained + abstract Locale getLocale2(); + +} diff --git a/src/classes/share/javax/media/j3d/BHLeafNode.java b/src/classes/share/javax/media/j3d/BHLeafNode.java new file mode 100644 index 0000000..21215e0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BHLeafNode.java @@ -0,0 +1,91 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +class BHLeafNode extends BHNode { + + BHLeafInterface leafIF; + + BHLeafNode() { + super(); + nodeType = BH_TYPE_LEAF; + leafIF = null; + } + + BHLeafNode(BHNode parent) { + super(parent); + nodeType = BH_TYPE_LEAF; + } + + BHLeafNode(BHLeafInterface lIF) { + super(); + nodeType = BH_TYPE_LEAF; + leafIF = lIF; + } + + BHLeafNode(BHNode parent, BHLeafInterface lIF) { + super(parent); + leafIF = lIF; + nodeType = BH_TYPE_LEAF; + } + + BHLeafNode(BHNode parent, BoundingBox bHull) { + super(parent, bHull); + nodeType = BH_TYPE_LEAF; + } + + BHLeafNode(BHNode parent, BHLeafInterface lIF, BoundingBox bHull) { + super(parent, bHull); + leafIF = lIF; + nodeType = BH_TYPE_LEAF; + } + + void computeBoundingHull() { + bHull = leafIF.computeBoundingHull(); + } + + void updateMarkedBoundingHull() { + + if(mark == false) + return; + + computeBoundingHull(); + mark = false; + } + + boolean isEnable() { + return leafIF.isEnable(); + } + + boolean isEnable(int vis) { + return leafIF.isEnable(vis); + } + + Locale getLocale() { + return leafIF.getLocale2(); + } + + void destroyTree(BHNode[] bhArr, int[] index) { + if(bhArr.length <= index[0]) { + // System.out.println("BHLeafNode : Problem bhArr overflow!!!"); + return; + } + + parent = null; + bhArr[index[0]] = this; + index[0]++; + } + +} diff --git a/src/classes/share/javax/media/j3d/BHNode.java b/src/classes/share/javax/media/j3d/BHNode.java new file mode 100644 index 0000000..7e11af3 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BHNode.java @@ -0,0 +1,271 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +abstract class BHNode { + + static final byte BH_TYPE_INTERNAL = 1; + static final byte BH_TYPE_LEAF = 2; + + static final int NUMBER_OF_PLANES = 6; + + static final boolean debug = false; + static final boolean debug2 = false; + + BHNode parent; + byte nodeType; + BoundingBox bHull = null; + boolean mark; + + BHNode () { + this.parent = null; + mark = false; + } + + BHNode (BHNode parent) { + this.parent = parent; + mark = false; + } + + BHNode (BHNode parent, BoundingBox bHull) { + this.parent = parent; + mark = false; + + this.bHull = bHull; + } + + BHNode getParent () { + return (this.parent) ; + } + + abstract void computeBoundingHull(); + abstract void updateMarkedBoundingHull(); + abstract void destroyTree(BHNode[] bhArr, int[] index); + + void setParent (BHNode node) { + this.parent = node; + } + + BoundingBox getBoundingHull() { + return (this.bHull); + } + + void setBoundingHull(BoundingBox bHull) { + this.bHull = bHull; + } + + // given two nodes determine the bHull surrounding them, ie. the parent hull + void combineBHull(BHNode node1, BHNode node2 ) { + BoundingBox bHull1 = null; + BoundingBox bHull2 = null; + + bHull1 = node1.getBoundingHull(); + bHull2 = node2.getBoundingHull(); + + if(this.bHull==null) + this.bHull = new BoundingBox(bHull1); + else + this.bHull.set(bHull1); + + this.bHull.combine(bHull2); + + } + + // returns true iff the bHull is completely inside this + // bounding hull i.e. bHull values are strictly less + // than or equal to all this.bHull values + boolean isInside(BoundingBox bHull) { + if(bHull == null) + return false; + + if( this.bHull.isEmpty() || bHull.isEmpty() ) { + return false; + } + + if( this.bHull.upper.x < bHull.upper.x || + this.bHull.upper.y < bHull.upper.y || + this.bHull.upper.z < bHull.upper.z || + this.bHull.lower.x > bHull.lower.x || + this.bHull.lower.y > bHull.lower.y || + this.bHull.lower.z > bHull.lower.z ) + return false; + else + return true; + } + + // finds the node matching the search element in the tree and returns + // the node if found, else it returns null if the node couldn't be found + BHNode findNode(BHNode node) { + BHNode fNode = null; + + if ( this.nodeType == BHNode.BH_TYPE_LEAF) { + if ( this == node ) { + return this; + } + } + else { + if (((BHInternalNode) this).rChild.isInside(node.bHull)) { + fNode = ((BHInternalNode)this).rChild.findNode(node); + if(fNode != null) { + return fNode; + } + } + if (((BHInternalNode)this).lChild.isInside(node.bHull)) { + return ((BHInternalNode)this).lChild.findNode(node); + } + } + return null; + } + + void deleteFromParent() { + BHInternalNode parent; + + // System.out.println("deleteFromParent - this " + this ); + parent = (BHInternalNode) (this.parent); + if(parent != null) { + if(parent.rChild == this) + parent.rChild = null; + else if(parent.lChild == this) + parent.lChild = null; + else { + if(debug2) { + System.out.println("BHNode.java: Trouble! No match found. This can't happen."); + System.out.println("this " + this ); + if ( this.nodeType == BHNode.BH_TYPE_INTERNAL) { + System.out.println("rChild " + ((BHInternalNode)this).rChild + + " lChild " + ((BHInternalNode)this).lChild); + } + System.out.println("parent " + parent + + " parent.rChild " + parent.rChild + + " parent.lChild " + parent.lChild); + } + } + } + + // add to free list ... + VirtualUniverse.mc.addBHNodeToFreelists(this); + } + + // delete all leaf nodes marked with DELETE_UPDATE and update the + // bounds of the parents node + BHNode deleteAndUpdateMarkedNodes() { + + if (this.mark == true) { + if (this.nodeType == BH_TYPE_LEAF) { + this.deleteFromParent(); + return null; + + } else { + if(debug) + if(((BHInternalNode)(this)).rChild == ((BHInternalNode)(this)).lChild) + System.out.println("rChild " + ((BHInternalNode)(this)).rChild + + " lChild " + ((BHInternalNode)(this)).lChild); + + + if(((BHInternalNode)(this)).rChild != null) + ((BHInternalNode)(this)).rChild = + ((BHInternalNode)(this)).rChild.deleteAndUpdateMarkedNodes(); + if(((BHInternalNode)(this)).lChild != null) + ((BHInternalNode)(this)).lChild = + ((BHInternalNode)(this)).lChild.deleteAndUpdateMarkedNodes(); + + if ((((BHInternalNode)(this)).rChild == null) && + (((BHInternalNode)(this)).lChild == null)) { + this.deleteFromParent(); + return null; + } else { + if ( ((BHInternalNode)this).rChild == null ) { + BHNode leftChild = ((BHInternalNode)this).lChild; + leftChild.parent = this.parent; + // delete self, return lChild + this.deleteFromParent(); + return leftChild; + } else if ( ((BHInternalNode)this).lChild == null ) { + BHNode rightChild = ((BHInternalNode)this).rChild; + rightChild.parent = this.parent; + // delete self, return rChild + this.deleteFromParent(); + return rightChild; + } else { + // recompute your bounds and return yourself + this.combineBHull(((BHInternalNode)this).rChild, + ((BHInternalNode)this).lChild); + // update the parent's pointers + ((BHInternalNode)this).rChild.parent = this; + ((BHInternalNode)this).lChild.parent = this; + this.mark = false; + return this; + } + } + } + } else { + // mark is NOT set, simply return self + return this; + } + } + + + // generic tree gathering statistics operations + + int countNumberOfInternals() { + if ( this.nodeType == BHNode.BH_TYPE_LEAF ) { + return 0; + } else { + return (((BHInternalNode)this).rChild.countNumberOfInternals() + + ((BHInternalNode)this).lChild.countNumberOfInternals() + 1 ); + } + } + + // recursively traverse the tree and compute the total number of leaves + int countNumberOfLeaves() { + if ( this.nodeType == BHNode.BH_TYPE_LEAF ) { + return 1; + } else { + return ( ((BHInternalNode)this).rChild.countNumberOfLeaves() + + ((BHInternalNode)this).lChild.countNumberOfLeaves() ); + } + } + + + // traverse tree and compute the maximum depth to a leaf + int computeMaxDepth (int currentDepth) { + if ( this.nodeType == BHNode.BH_TYPE_LEAF ) { + return (currentDepth); + } else { + int rightDepth = ((BHInternalNode)this).rChild.computeMaxDepth(currentDepth + 1); + int leftDepth = ((BHInternalNode)this).lChild.computeMaxDepth(currentDepth + 1); + if( rightDepth > leftDepth ) + return rightDepth; + return leftDepth; + } + } + + // compute the average depth of the leaves ... + float computeAverageLeafDepth ( int numberOfLeaves, int currentDepth ) { + int sumOfDepths = this.computeSumOfDepths(0); + return ( (float)sumOfDepths / (float)numberOfLeaves ); + } + + int computeSumOfDepths ( int currentDepth ) { + if ( this.nodeType == BHNode.BH_TYPE_LEAF ) { + return ( currentDepth ); + } else { + return (((BHInternalNode)this).rChild.computeSumOfDepths(currentDepth + 1) + + ((BHInternalNode)this).lChild.computeSumOfDepths(currentDepth + 1) ) ; + } + } + + +} diff --git a/src/classes/share/javax/media/j3d/BHTree.java b/src/classes/share/javax/media/j3d/BHTree.java new file mode 100644 index 0000000..ef622c2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BHTree.java @@ -0,0 +1,1136 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; +import java.util.Vector; +import javax.vecmath.Point4d; + +class BHTree { + + Locale locale; + BHNode root; + BHInsertStructure insertStructure = null; + + + // Temporary point, so we dont generate garbage + Point4d tPoint4d = new Point4d(); + + // A flag to signal that number of renderAtoms sent to RenderBin is stable. + private boolean stable = false; + + // An estimate of the maxmium depth of this tree (upper bound). + int estMaxDepth; + + static final double LOG_OF_2 = Math.log(2); + + // Assume that the size avg. leaf node is 256 bytes. For a 64bit system, we'll + // down with max depth of 56 for an ideal balance tree. + static final int DEPTH_UPPER_BOUND = 56; + static final int INCR_DEPTH_BOUND = 5; + int depthUpperBound = DEPTH_UPPER_BOUND; + + BHTree() { + locale = null; + root = null; + } + + BHTree(Locale loc) { + locale = loc; + root = null; + } + + BHTree(BHNode bhArr[]) { + locale = null; + root = null; + create(bhArr); + } + + void setLocale(Locale loc) { + locale = loc; + } + + Locale getLocale() { + return locale; + } + + void cluster(BHInternalNode root, BHNode[] bhArr) { + + if(J3dDebug.devPhase) { + if(J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_4, + "BHTree.java :In cluster length is " + bhArr.length + + "\n")) { + + for(int i=0; i<bhArr.length; i++) { + System.out.println(bhArr[i]); + } + } + } + + if((bhArr == null) || (bhArr.length < 2) || (root == null)){ + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_1, + "BHTree.java : cluster : trouble! \n"); + return; + } + + int centerValuesIndex[] = new int[bhArr.length]; + float centerValues[][] = computeCenterValues(bhArr, centerValuesIndex); + + constructTree(root, bhArr, centerValues, centerValuesIndex); + + } + + // bhArr can only contains BHLeafNode. + + void boundsChanged(BHNode bhArr[], int size) { + // Mark phase. + markParentChain(bhArr, size); + + // Compute phase. + root.updateMarkedBoundingHull(); + } + + + // Return true if bhTree's root in encompass by frustumBBox and nothing changed. + boolean getVisibleBHTrees(RenderBin rBin, ArrayList bhTrees, + BoundingBox frustumBBox, long referenceTime, + boolean stateChanged, int visibilityPolicy, + boolean singleLocale) { + + int i, j, size; + + if ((frustumBBox != null) && (root != null)) { + + boolean inSide = aEncompassB(frustumBBox, root.bHull); + /* + System.out.println("stateChanged is " + stateChanged); + System.out.println("frustumBBox is " + frustumBBox); + System.out.println("root.bHull is " + root.bHull); + System.out.println("inSide is " + inSide); + */ + + if(singleLocale && !stateChanged && inSide && stable) { + // just return the whole tree, no change in render mol.. + // System.out.println("Optimize case 1 ..." + this); + bhTrees.add(root); + return true; + } + else if(!stateChanged && inSide) { + // the whole tree is in, but we've to be sure that RenderBin is + // stable ... + // System.out.println("Optimize case 2 ..." + this); + select(rBin, bhTrees, frustumBBox, root, referenceTime, + visibilityPolicy, true); + + bhTrees.add(root); + stable = true; + } else { + // System.out.println("Not in Optimize case ..." + this); + select(rBin, bhTrees, frustumBBox, root, referenceTime, + visibilityPolicy, false); + + stable = false; + } + + } + + return false; + } + + private void select(RenderBin rBin, ArrayList bhTrees, BoundingBox frustumBBox, + BHNode bh, long referenceTime, int visibilityPolicy, + boolean inSide) { + + if ((bh == null) || (bh.bHull.isEmpty())) { + return; + } + + switch(bh.nodeType) { + case BHNode.BH_TYPE_LEAF: + if((((BHLeafNode) bh).leafIF instanceof GeometryAtom) && + (((BHLeafNode) bh).isEnable(visibilityPolicy)) && + ((inSide) || (frustumBBox.intersect(bh.bHull)))) { + + // do render atom setup. + rBin.processGeometryAtom((GeometryAtom) + (((BHLeafNode)bh).leafIF), + referenceTime); + if(!inSide) { + bhTrees.add(bh); + } + } + break; + case BHNode.BH_TYPE_INTERNAL: + if(inSide) { + select(rBin, bhTrees, frustumBBox, + ((BHInternalNode)bh).getRightChild(), + referenceTime, visibilityPolicy, true); + select(rBin, bhTrees, frustumBBox, + ((BHInternalNode)bh).getLeftChild(), + referenceTime, visibilityPolicy, true); + } + else if(aEncompassB(frustumBBox, bh.bHull)) { + bhTrees.add(bh); + select(rBin, bhTrees, frustumBBox, + ((BHInternalNode)bh).getRightChild(), + referenceTime, visibilityPolicy, true); + select(rBin, bhTrees, frustumBBox, + ((BHInternalNode)bh).getLeftChild(), + referenceTime, visibilityPolicy, true); + } + else if(frustumBBox.intersect(bh.bHull)) { + select(rBin, bhTrees, frustumBBox, + ((BHInternalNode)bh).getRightChild(), + referenceTime, visibilityPolicy, false); + select(rBin, bhTrees, frustumBBox, + ((BHInternalNode)bh).getLeftChild(), + referenceTime, visibilityPolicy, false); + } + break; + } + } + + // returns true iff the bBox is completely inside aBox + // i.e. bBoxl values are strictly less than or equal to all aBox values. + static boolean aEncompassB(BoundingBox aBox, BoundingBox bBox) { + return ((aBox.upper.x >= bBox.upper.x) && + (aBox.upper.y >= bBox.upper.y) && + (aBox.upper.z >= bBox.upper.z) && + (aBox.lower.x <= bBox.lower.x) && + (aBox.lower.y <= bBox.lower.y) && + (aBox.lower.z <= bBox.lower.z)); + } + + + BHLeafInterface selectAny(GeometryAtom atom, int accurancyMode) { + if (atom.source.geometryList == null) + return null; + BHNode bhNode = doSelectAny(atom, root, accurancyMode); + if (bhNode == null) { + return null; + } + + return ((BHLeafNode) bhNode).leafIF; + } + + + BHLeafInterface selectAny(GeometryAtom atoms[], int size, int accurancyMode) { + BHNode bhNode = doSelectAny(atoms, size, root, accurancyMode); + if (bhNode == null) { + return null; + } + + return ((BHLeafNode) bhNode).leafIF; + } + + + private BHNode doSelectAny(GeometryAtom atoms[],int atomSize, + BHNode bh, int accurancyMode) { + if ((bh == null) || (bh.bHull.isEmpty())) { + return null; + } + switch (bh.nodeType) { + case BHNode.BH_TYPE_LEAF: + BHLeafInterface leaf = ((BHLeafNode) bh).leafIF; + GeometryAtom atom; + int i; + + if (leaf instanceof GeometryAtom) { + GeometryAtom leafAtom = (GeometryAtom) leaf; + + if (((BHLeafNode) bh).isEnable() && + leafAtom.source.isCollidable) { + + // atom self intersection between atoms[] + for (i=atomSize-1; i >=0; i--) { + if (atoms[i] == leafAtom) { + return null; + } + } + for (i=atomSize-1; i >=0; i--) { + atom = atoms[i]; + if ((atom.source.sourceNode != leafAtom.source.sourceNode) && + (atom.source.collisionVwcBound.intersect(leafAtom.source.collisionVwcBound)) && + ((accurancyMode == WakeupOnCollisionEntry.USE_BOUNDS) || + ((leafAtom.source.geometryList != null) && + (atom.source.intersectGeometryList(leafAtom.source))))) { + return bh; + } + } + } + } else if (leaf instanceof GroupRetained) { + if (((BHLeafNode) bh).isEnable() && + ((GroupRetained) leaf).sourceNode.collidable) { + for (i=atomSize-1; i >=0; i--) { + atom = atoms[i]; + if (atom.source.collisionVwcBound.intersect(bh.bHull) && + ((accurancyMode == WakeupOnCollisionEntry.USE_BOUNDS) || + (atom.source.intersectGeometryList( + atom.source.getCurrentLocalToVworld(0), bh.bHull)))) { + return bh; + } + } + } + } + return null; + case BHNode.BH_TYPE_INTERNAL: + for (i=atomSize-1; i >=0; i--) { + atom = atoms[i]; + if (atom.source.collisionVwcBound.intersect(bh.bHull)) + { + BHNode hitNode = doSelectAny(atoms, + atomSize, + ((BHInternalNode) bh).getRightChild(), + accurancyMode); + if (hitNode != null) + return hitNode; + + return doSelectAny(atoms, atomSize, + ((BHInternalNode) bh).getLeftChild(), + accurancyMode); + } + } + return null; + } + return null; + } + + + private BHNode doSelectAny(GeometryAtom atom, BHNode bh, int accurancyMode) { + if ((bh == null) || (bh.bHull.isEmpty())) { + return null; + } + switch (bh.nodeType) { + case BHNode.BH_TYPE_LEAF: + BHLeafInterface leaf = ((BHLeafNode) bh).leafIF; + if (leaf instanceof GeometryAtom) { + GeometryAtom leafAtom = (GeometryAtom) leaf; + if ((atom.source.sourceNode != leafAtom.source.sourceNode) && + (((BHLeafNode) bh).isEnable()) && + (leafAtom.source.isCollidable) && + (atom.source.collisionVwcBound.intersect(leafAtom.source.collisionVwcBound)) && + ((accurancyMode == WakeupOnCollisionEntry.USE_BOUNDS) || + ((leafAtom.source.geometryList != null) && + (atom.source.intersectGeometryList(leafAtom.source))))) { + return bh; + } + } else if (leaf instanceof GroupRetained) { + if (((BHLeafNode) bh).isEnable() && + ((GroupRetained) leaf).sourceNode.collidable && + atom.source.collisionVwcBound.intersect(bh.bHull) && + ((accurancyMode == WakeupOnCollisionEntry.USE_BOUNDS) || + (atom.source.intersectGeometryList( + atom.source.getCurrentLocalToVworld(0), bh.bHull)))) { + return bh; + } + } + return null; + case BHNode.BH_TYPE_INTERNAL: + if (atom.source.collisionVwcBound.intersect(bh.bHull)) { + BHNode hitNode = doSelectAny(atom, + ((BHInternalNode) bh).getRightChild(), + accurancyMode); + if (hitNode != null) + return hitNode; + + return doSelectAny(atom, + ((BHInternalNode) bh).getLeftChild(), + accurancyMode); + } + return null; + } + return null; + } + + BHLeafInterface selectAny(Bounds bound, int accurancyMode, + NodeRetained armingNode) { + if (bound == null) { + return null; + } + BHNode bhNode = doSelectAny(bound, root, accurancyMode, armingNode); + if (bhNode == null) { + return null; + } + return ((BHLeafNode) bhNode).leafIF; + } + + private BHNode doSelectAny(Bounds bound, BHNode bh, int accurancyMode, + NodeRetained armingNode) { + if ((bh == null) || (bh.bHull.isEmpty())) { + return null; + } + + switch (bh.nodeType) { + case BHNode.BH_TYPE_LEAF: + BHLeafInterface leaf = ((BHLeafNode) bh).leafIF; + if (leaf instanceof GeometryAtom) { + GeometryAtom leafAtom = (GeometryAtom) leaf; + if ((((BHLeafNode) bh).isEnable()) && + (leafAtom.source.isCollidable) && + (bound.intersect(leafAtom.source.collisionVwcBound)) && + ((accurancyMode == WakeupOnCollisionEntry.USE_BOUNDS) || + ((leafAtom.source.geometryList != null) && + (leafAtom.source.intersectGeometryList( + leafAtom.source.getCurrentLocalToVworld(0), bound))))) { + return bh; + } + } else if (leaf instanceof GroupRetained) { + if ((leaf != armingNode) && + ((BHLeafNode) bh).isEnable() && + ((GroupRetained) leaf).sourceNode.collidable && + bound.intersect(bh.bHull)) { + return bh; + } + } + return null; + case BHNode.BH_TYPE_INTERNAL: + if (bound.intersect(bh.bHull)) { + BHNode hitNode = doSelectAny(bound, + ((BHInternalNode) bh).getRightChild(), + accurancyMode, + armingNode); + if (hitNode != null) + return hitNode; + + return doSelectAny(bound, + ((BHInternalNode) bh).getLeftChild(), + accurancyMode, + armingNode); + } + return null; + } + return null; + } + + + BHLeafInterface selectAny(Bounds bound, int accurancyMode, + GroupRetained armingGroup) { + if (bound == null) { + return null; + } + BHNode bhNode = doSelectAny(bound, root, accurancyMode, armingGroup); + if (bhNode == null) { + return null; + } + return ((BHLeafNode) bhNode).leafIF; + } + + private BHNode doSelectAny(Bounds bound, BHNode bh, int accurancyMode, + GroupRetained armingGroup) { + if ((bh == null) || (bh.bHull.isEmpty())) { + return null; + } + switch (bh.nodeType) { + case BHNode.BH_TYPE_LEAF: + BHLeafInterface leaf = ((BHLeafNode) bh).leafIF; + + if (leaf instanceof GeometryAtom) { + GeometryAtom leafAtom = (GeometryAtom) leaf; + if ((((BHLeafNode) bh).isEnable()) && + (leafAtom.source.isCollidable) && + (bound.intersect(leafAtom.source.collisionVwcBound)) && + (!isDescendent(leafAtom.source.sourceNode, + armingGroup, leafAtom.source.key)) && + ((accurancyMode == WakeupOnCollisionEntry.USE_BOUNDS) || + ((leafAtom.source.geometryList != null) && + (leafAtom.source.intersectGeometryList( + leafAtom.source.getCurrentLocalToVworld(0), bound))))) { + return bh; + } + } else if (leaf instanceof GroupRetained) { + GroupRetained group = (GroupRetained) leaf; + if (((BHLeafNode) bh).isEnable() && + group.sourceNode.collidable && + bound.intersect(bh.bHull) && + !isDescendent(group.sourceNode, armingGroup, group.key)) { + return bh; + } + } + return null; + case BHNode.BH_TYPE_INTERNAL: + if (bound.intersect(bh.bHull)) { + BHNode hitNode = doSelectAny(bound, + ((BHInternalNode) bh).getRightChild(), + accurancyMode, + armingGroup); + if (hitNode != null) + return hitNode; + + return doSelectAny(bound, + ((BHInternalNode) bh).getLeftChild(), + accurancyMode, + armingGroup); + } + return null; + } + return null; + } + + // Return true if node is a descendent of group + private boolean isDescendent(NodeRetained node, + GroupRetained group, + HashKey key) { + + synchronized (group.universe.sceneGraphLock) { + if (node.inSharedGroup) { + // getlastNodeId() will destroy this key + if (key != null) { + key = new HashKey(key); + } + } + + do { + if (node == group) { + return true; + } + if (node instanceof SharedGroupRetained) { + // retrieve the last node ID + String nodeId = key.getLastNodeId(); + NodeRetained prevNode = node; + Vector parents = ((SharedGroupRetained) node).parents; + for(int i=parents.size()-1; i >=0; i--) { + NodeRetained link = (NodeRetained) parents.elementAt(i); + if (link.nodeId.equals(nodeId)) { + node = link; + break; + } + } + if (prevNode == node) { + // branch is already detach + return true; + } + } + node = node.parent; + } while (node != null); // reach Locale + } + return false; + } + + + void select(PickShape pickShape, UnorderList hitArrList) { + + if((pickShape == null)||(root == null)) + return; + + doSelect(pickShape, hitArrList, root, tPoint4d); + + } + + + private void doSelect(PickShape pickShape, UnorderList hitArrList, + BHNode bh, Point4d pickPos) { + + if ((bh == null) || (bh.bHull.isEmpty())) { + return; + } + + switch(bh.nodeType) { + case BHNode.BH_TYPE_LEAF: + if (((BHLeafNode)(bh)).isEnable() && + (((BHLeafNode) bh).leafIF instanceof GeometryAtom) && + ((GeometryAtom) (((BHLeafNode) + bh).leafIF)).source.isPickable && + pickShape.intersect(bh.bHull, pickPos)) { + hitArrList.add(bh); + } + break; + case BHNode.BH_TYPE_INTERNAL: + if (pickShape.intersect(bh.bHull, pickPos)) { + doSelect(pickShape, + hitArrList, + ((BHInternalNode)bh).getRightChild(), + pickPos); + doSelect(pickShape, + hitArrList, + ((BHInternalNode)bh).getLeftChild(), + pickPos); + } + break; + } + } + + BHNode selectAny(PickShape pickShape) { + + if((pickShape == null)||(root == null)) + return null; + + return doSelectAny(pickShape, root, tPoint4d); + + } + + + private BHNode doSelectAny(PickShape pickShape, BHNode bh, Point4d pickPos) { + + BHNode hitNode = null; + + if((bh == null) || (bh.bHull.isEmpty())) + return null; + + switch(bh.nodeType) { + case BHNode.BH_TYPE_LEAF: + if (((BHLeafNode)(bh)).isEnable() && + (((BHLeafNode) bh).leafIF instanceof GeometryAtom) && + ((GeometryAtom) (((BHLeafNode) + bh).leafIF)).source.isPickable && + pickShape.intersect(bh.bHull, pickPos)) { + return bh; + } + break; + case BHNode.BH_TYPE_INTERNAL: + if (pickShape.intersect(bh.bHull, pickPos)) { + hitNode = doSelectAny(pickShape, + ((BHInternalNode)bh).getRightChild(), + pickPos); + + if (hitNode != null) { + return hitNode; + } + + return doSelectAny(pickShape, + ((BHInternalNode)bh).getLeftChild(), + pickPos); + } + break; + } + return null; + } + + + private void create(BHNode bhArr[]) { + int i; + + if(bhArr == null) { + root = null; + return; + } + + if(bhArr.length == 1) { + bhArr[0].computeBoundingHull(); + root = (BHNode)bhArr[0]; + return; + } + + int centerValuesIndex[] = new int[bhArr.length]; + float centerValues[][] = computeCenterValues(bhArr, centerValuesIndex); + + /* + System.out.println("Length of array is " + bhArr.length); + for(int kk=0; kk<bhArr.length;kk++) { + System.out.println("( " + centerValues[kk][0] + ", " + + centerValues[kk][1] + ", " + centerValues[kk][2] + " )"); + } + */ + + root = VirtualUniverse.mc.getBHNode(BHNode.BH_TYPE_INTERNAL); + constructTree((BHInternalNode) root, bhArr, centerValues, + centerValuesIndex); + + + if(J3dDebug.devPhase && J3dDebug.debug) + gatherTreeStatistics(); + + } + + void insert(BHNode bhArr[], int size) { + // first pass: add all elements to the tree creating k array internal + // nodes using the auxiliaryInsertStucture + // second pass: go through all elements of the auxiliaryInsertStructure + // and then update these nodes reclustering the trees with the new + // k element siblings ... + + if(J3dDebug.devPhase && J3dDebug.debug) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_4, + "BHTree.java : Insert - bhArr.length is " + + bhArr.length + "\n"); + + if((bhArr == null) || (size < 1) || (bhArr.length < 1)) + return; + + if(root == null) { + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_4, + "BHTree.java : Tree has not be created yet.\n"); + + // This extra "new" is needed, because create() require its input + // array's length be equal to the number of inserted nodes. + BHNode[] newbhArr = new BHNode[size]; + System.arraycopy(bhArr, 0, newbhArr, 0, size); + create(newbhArr); + return; + } + + if(root.nodeType == BHNode.BH_TYPE_LEAF) { + BHNode[] oldBhArr = bhArr; + bhArr = new BHNode[size + 1]; + System.arraycopy(oldBhArr, 0, bhArr, 0, size); + bhArr[size] = root; + create(bhArr); + return; + } + + if(insertStructure == null) { + insertStructure = new BHInsertStructure(size); + } + else { + insertStructure.clear(); + } + + for (int i=0; i<size; i++) { + // test if its inside the 'root' element + if ( root.isInside(bhArr[i].bHull) ) { + ((BHInternalNode)root).insert(bhArr[i], insertStructure); + } + else { + // extend the bounds of root by joining with current element + root.bHull.combine(bhArr[i].bHull); + insertStructure.lookupAndInsert(root, bhArr[i]); + } + } + + insertStructure.updateBoundingTree(this); + // System.out.println("BHTree - Inserting ..."); + + // Guard against size<1 is done at the start of this method. + estMaxDepth += (int) (Math.log(size)/LOG_OF_2) + 1; + + if(estMaxDepth > depthUpperBound) { + int maxDepth = root.computeMaxDepth(0); + int leafCount = root.countNumberOfLeaves(); + double compDepth = Math.log(leafCount)/LOG_OF_2; + /* + System.out.println("BHTree - evaluate for reConstructTree ..."); + System.out.println("compDepth " + compDepth); + System.out.println("maxDepth " + maxDepth); + System.out.println("leafCount " + leafCount); + */ + + // Upper bound guard. + if(maxDepth > depthUpperBound) { + reConstructTree(leafCount); + maxDepth = root.computeMaxDepth(0); + /* + System.out.println("BHTree - Did reConstructTree ..."); + System.out.println("compDepth " + compDepth); + System.out.println("maxDepth " + maxDepth); + */ + } + + // Adjust depthUpperBound according to app. need. + // If we encounter lots of overlapping bounds, the re-balanced + // tree may not be an ideal balance tree. So there might be a + // likehood of maxDepth exceeding the preset depthUpperBound. + if(maxDepth > depthUpperBound) { + depthUpperBound = depthUpperBound + INCR_DEPTH_BOUND; + }else if((depthUpperBound != DEPTH_UPPER_BOUND) && + (maxDepth * 1.5 < depthUpperBound)) { + depthUpperBound = depthUpperBound - INCR_DEPTH_BOUND; + + if(depthUpperBound < DEPTH_UPPER_BOUND) { + // Be sure that DEPTH_UPPER_BOUND is the min. + depthUpperBound = DEPTH_UPPER_BOUND; + } + } + + // This is the only place for resetting estMaxDepth to the tree real + // maxDepth. Hence in cases where tree may get deteriorate fast, such + // as multiple inserts and deletes frequently. estMaxDepth is accuminated, + // and will lead to tree re-evaluation and possibly re-balancing. + estMaxDepth = maxDepth; + } + + } + + + // mark all elements of the node and its parent as needing updating + private void markParentChain(BHNode[] nArr, int size) { + BHNode node; + + for(int i=0; i<size; i++) { + node = nArr[i]; + node.mark = true; + while((node.parent != null) && (node.parent.mark == false)) { + node = node.parent; + node.mark = true; + } + } + } + + // mark all elements of the node and its parent as needing updating + private void markParentChain(BHNode node) { + node.mark = true; + while((node.parent != null) && (node.parent.mark == false)) { + node = node.parent; + node.mark = true; + } + } + + // Delete a series of n node elements from the input binary tree. + // These elements are removed from the tree in a 2 phase process. + // First, all elements to be removed are marked and all parent + // chains are marked ... then a second phase of the algorithm goes + // through and deletes them and updates all of the bounds ... + + // delete the n elements in the array from the tree + void delete(BHNode bhArr[], int size) { + BHNode node; + + /* + if((bhArr == null) || (bhArr.length < 1)) + return; + System.out.println("BHTree.java : delete - bhArr.length is " + + bhArr.length); + for(int i=0; i<bhArr.length; i++) + System.out.println("bhArr[" + i +"] " + bhArr[i]); + + */ + + for(int i=0; i<size; i++) { + if((bhArr[i] != null) && (bhArr[i].nodeType == BHNode.BH_TYPE_LEAF)) { + markParentChain(bhArr[i]); + } else { + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_1, + "Warning, element " + i + " is null/not leaf node.\n" + + "Error in deletion routine, element " + + bhArr[i] + "\n" + + "In tree = " + this + + " can not delete it ...\n"); + } + + } + + root = root.deleteAndUpdateMarkedNodes(); + + if(J3dDebug.devPhase) + if (root == null) { + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_4, + "Tree has been completely deleted ...\n"); + } + } + + // compute the center values along each of the three dimensions given + // the array of leaf objects to be split and joined + float[][] computeCenterValues(BHNode[] bhArr, int[] cIndex) { + float centers[][] = new float[bhArr.length][3]; + + // compute the center values of the input array of nodes + for ( int i=0; i < bhArr.length; i++ ) { + cIndex[i] = i; + + bhArr[i].computeBoundingHull(); + + centers[i][0] = + (float)((bhArr[i].bHull.upper.x + bhArr[i].bHull.lower.x))/ 2.0f; + centers[i][1] = + (float)((bhArr[i].bHull.upper.y + bhArr[i].bHull.lower.y)) / 2.0f; + centers[i][2] = + (float)((bhArr[i].bHull.upper.z + bhArr[i].bHull.lower.z)) / 2.0f; + + } + return centers; + } + + + void computeMeansAndSumSquares(float[][] centerValues, int[] centerValuesIndex, + float[] means, float[] ss) { + + int i, arrLen; + float sumCenters[] = new float[3]; + float temp = 0.0f; + + arrLen = centerValuesIndex.length; + // Initialization. + for(i=2; i>=0; i--) { + sumCenters[i] = 0.0f; + ss[i] = 0.0f; + } + + for(i=arrLen-1; i>=0 ; i--) { + sumCenters[0] += centerValues[centerValuesIndex[i]][0]; + sumCenters[1] += centerValues[centerValuesIndex[i]][1]; + sumCenters[2] += centerValues[centerValuesIndex[i]][2]; + } + + means[0] = sumCenters[0]/(float)arrLen; + means[1] = sumCenters[1]/(float)arrLen; + means[2] = sumCenters[2]/(float)arrLen; + + for(i=arrLen-1; i>=0 ; i--) { + temp = (centerValues[centerValuesIndex[i]][0] - means[0]); + ss[0] += (temp*temp); + temp = (centerValues[centerValuesIndex[i]][1] - means[1]); + ss[1] += (temp*temp); + temp = (centerValues[centerValuesIndex[i]][2] - means[2]); + ss[2] += (temp*temp); + + } + + } + + // find the split axis (the highest ss and return its index) for + // a given set of ss values + int findSplitAxis ( float ss[] ) { + int splitAxis = -1; + float maxSS = 0.0f; + + // the largest ss index value + for (int i=0; i < 3; i++) { + if ( ss[i] > maxSS ) { + maxSS = ss[i]; + splitAxis = i; + } + } + return splitAxis; + } + + // Recursive method for constructing a binary tree. + void constructTree( BHInternalNode parent, BHNode bhArr[], + float[][] centerValues, + int[] centerValuesIndex ){ + + int i, splitAxis; + int rightSetCount = 0; + int leftSetCount = 0; + float means[] = new float[3]; + float ss[] = new float[3]; + + if(J3dDebug.devPhase) + if ( bhArr.length <= 1 ) { + // this is only here for debugging can be removed after testing + // to ensure that it never gets called + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_1, + "constructTree - bhArr.length <= 1. Bad !!!\n"); + } + + computeMeansAndSumSquares(centerValues, centerValuesIndex, means, ss); + + splitAxis = findSplitAxis(ss); + + // an array of decision variables for storing the values of inside + // the right or left set for a particular element of bhArr. + // true if its in the left set, false if its in the right set + boolean leftOrRightSet[] = new boolean[bhArr.length]; + + if ( splitAxis == -1 ) { + // This is bad. Since we can't find a split axis, the best thing + // to do is to split the set in two sets; each with about the + // same number of elements. By doing this we can avoid constructing + // a skew tree. + + // split elements into half. + for ( i=0; i < bhArr.length; i++) { + if(leftSetCount > rightSetCount) { + rightSetCount++; + leftOrRightSet[i] = false; + } else { + leftSetCount++; + leftOrRightSet[i] = true; + } + } + } + else { + for ( i=0; i < bhArr.length; i++) { + // the split criterion, special multiple equals cases added + if ( centerValues[centerValuesIndex[i]][splitAxis] < + means[splitAxis]) { + + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_4, + "Found a left element\n"); + leftSetCount++; + leftOrRightSet[i] = true; + } else if ( centerValues[centerValuesIndex[i]][splitAxis] > + means[splitAxis]) { + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_4, + "Found a right element\n"); + rightSetCount++; + leftOrRightSet[i] = false; + } else { + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_4, + "Found an equal element\n"); + if(leftSetCount > rightSetCount) { + rightSetCount++; + leftOrRightSet[i] = false; + } else { + leftSetCount++; + leftOrRightSet[i] = true; + } + } + } + } + + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_2, + "LeftSetCount " + leftSetCount + " RightSetCount "+ + rightSetCount + "\n"); + + + // Don't think that this guard is needed, but still like to have it. + // Just in case, bad means and the sum of squares might lead us into the guard. + if (leftSetCount == bhArr.length) { + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_1, + "Split Axis of = " + splitAxis + " didn't yield "+ + "any split among the objects ?\n"); + // split elements into half + rightSetCount = 0; + leftSetCount = 0; + for ( i=0; i < bhArr.length; i++) { + if(leftSetCount > rightSetCount) { + rightSetCount++; + leftOrRightSet[i] = false; + } else { + leftSetCount++; + leftOrRightSet[i] = true; + } + } + } else if (rightSetCount == bhArr.length) { + if(J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_1, + "Split Axis of = " + splitAxis + " didn't yield "+ + "any split among the objects ?\n"); + // split elements into half + rightSetCount = 0; + leftSetCount = 0; + for ( i=0; i < bhArr.length; i++) { + if(leftSetCount > rightSetCount) { + rightSetCount++; + leftOrRightSet[i] = false; + } else { + leftSetCount++; + leftOrRightSet[i] = true; + } + } + } + + if(J3dDebug.devPhase) + if(J3dDebug.doDebug(J3dDebug.bHTree, J3dDebug.LEVEL_4)) + // check to make sure that rightSet and leftSet sum to the + // number of elements in the original array. + if ( bhArr.length != (rightSetCount + leftSetCount) ) { + System.out.println("An error has occurred in spliting"); + } + + BHNode rightSet[] = new BHNode[rightSetCount]; + BHNode leftSet[] = new BHNode[leftSetCount]; + int centerValuesIndexR[] = new int[rightSetCount]; + int centerValuesIndexL[] = new int[leftSetCount]; + + rightSetCount = 0; + leftSetCount = 0; + + for (i=0; i < bhArr.length; i++) { + if ( leftOrRightSet[i] ) { // element in left set + leftSet[leftSetCount] = bhArr[i]; + centerValuesIndexL[leftSetCount] = centerValuesIndex[i]; + leftSetCount++; + } else { + rightSet[rightSetCount] = bhArr[i]; + centerValuesIndexR[rightSetCount] = centerValuesIndex[i]; + rightSetCount++; + } + } + + if (rightSet.length != 1) { + parent.rChild = VirtualUniverse.mc.getBHNode(BHNode.BH_TYPE_INTERNAL); + parent.rChild.setParent(parent); + constructTree((BHInternalNode)(parent.rChild), rightSet, centerValues, + centerValuesIndexR); + } else { + parent.rChild = rightSet[0]; + parent.rChild.setParent(parent); + } + + if (leftSet.length != 1) { + parent.lChild = VirtualUniverse.mc.getBHNode(BHNode.BH_TYPE_INTERNAL); + parent.lChild.setParent(parent); + constructTree((BHInternalNode)(parent.lChild), leftSet, centerValues, + centerValuesIndexL); + } else { + parent.lChild = leftSet[0]; + parent.lChild.setParent(parent); + } + + parent.combineBHull(parent.rChild, parent.lChild); + } + + + void reConstructTree(int numOfLeaf) { + if(root == null) + return; + + BHNode bhArr[] = new BHNode[numOfLeaf]; + int index[] = new int[1]; + index[0] = 0; + root.destroyTree(bhArr, index); + + /* + if(bhArr.length != index[0]) + System.out.println("BHTree - This isn't right!!! - bhArr.length " + + bhArr.length + " index " + index[0]); + */ + + create(bhArr); + + } + + void gatherTreeStatistics() { + + int leafCount = root.countNumberOfLeaves(); + int internalCount = root.countNumberOfInternals(); + int maxDepth = root.computeMaxDepth(0); + float averageDepth = root.computeAverageLeafDepth ( leafCount, 0); + + + System.out.println("Statistics for tree = " + this); + System.out.println("Total Number of nodes in tree = " + + (leafCount + internalCount) ); + System.out.println("Number of Leaf Nodes = " + leafCount ); + System.out.println("Number of Internal Nodes = " + internalCount ); + System.out.println("Maximum Leaf depth = " + maxDepth ); + System.out.println("Average Leaf depth = " + averageDepth ); + System.out.println("root.bHull = " + root.bHull); + // printTree(root); + + } + + + void printTree(BHNode bh) { + if(bh!= null) { + if(bh.nodeType == BHNode.BH_TYPE_INTERNAL) { + System.out.println("BH_TYPE_INTERNAL - bHull : " + bh); + System.out.println(bh.bHull); + System.out.println("rChild : " + ((BHInternalNode)bh).rChild + + " lChild : " + ((BHInternalNode)bh).lChild); + printTree(((BHInternalNode)bh).rChild); + printTree(((BHInternalNode)bh).lChild); + } + else if(bh.nodeType == BHNode.BH_TYPE_LEAF) { + System.out.println("BH_TYPE_LEAF - bHull : " + bh); + System.out.println(bh.bHull); + } + + } + + + } +} + + + + + + diff --git a/src/classes/share/javax/media/j3d/Background.java b/src/classes/share/javax/media/j3d/Background.java new file mode 100644 index 0000000..f22f100 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Background.java @@ -0,0 +1,704 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * The Background leaf node defines a solid background color + * and a background image that are used to fill the window at the + * beginning of each new frame. The background image may be null. + * It optionally allows background + * geometry---which is pre-tessellated onto a unit sphere and is drawn + * at infinity---to be referenced. It also specifies an application + * region in which this background is active. A Background node is + * active when its application region intersects the ViewPlatform's + * activation volume. If multiple Background nodes are active, the + * Background node that is "closest" to the eye will be used. If no + * Background nodes are active, then the window is cleared to black. + * + * <p> + * The set of nodes that can be added to a BranchGroup associated with + * a Background node is limited. All Group nodes except + * ViewSpecificGroup are legal in a background geometry branch + * graph. The only Leaf nodes that are legal are Shape3D (except + * OrientedShape3D), Morph, Light, and Fog. The presence of any other + * Leaf node, including OrientedShape3D, or of a ViewSpecificGroup + * node will cause an IllegalSceneGraphException to be thrown. Note + * that Link nodes are not allowed; a background geometry branch graph + * must not reference shared subgraphs. NodeComponent objects can be + * shared between background branches and ordinary (non-background) + * branches or among different background branches, however. + * + * <p> + * Light and Fog nodes in a background geometry branch graph do not + * affect nodes outside of the background geometry branch graph, and + * vice versa. Light and Fog nodes that appear in a background + * geometry branch graph must not be hierarchically scoped to any + * group node outside of that background geometry branch graph. + * Conversely, Light and Fog nodes that appear outside of a particular + * background geometry branch graph must not be hierarchically scoped + * to any group node in that background geometry branch graph. Any + * attempt to do so will be ignored. + * + * <p> + * The influencing bounds of any Light or Fog node in a background + * geometry branch graph is effectively infinite (meaning that all + * lights can affect all geometry objects nodes within the background + * geometry graph, and that an arbitrary fog is selected). An + * application wishing to limit the scope of a Light or Fog node must + * use hierarchical scoping. + * + * <p> + * Picking and collision is ignored for nodes inside a background + * geometry branch graph. + */ +public class Background extends Leaf { + /** + * Specifies that the Background allows read access to its application + * bounds and bounding leaf at runtime. + */ + public static final int + ALLOW_APPLICATION_BOUNDS_READ = CapabilityBits.BACKGROUND_ALLOW_APPLICATION_BOUNDS_READ; + + /** + * Specifies that the Background allows write access to its application + * bounds and bounding leaf at runtime. + */ + public static final int + ALLOW_APPLICATION_BOUNDS_WRITE = CapabilityBits.BACKGROUND_ALLOW_APPLICATION_BOUNDS_WRITE; + + /** + * Specifies that the Background allows read access to its image + * at runtime. + */ + public static final int + ALLOW_IMAGE_READ = CapabilityBits.BACKGROUND_ALLOW_IMAGE_READ; + + /** + * Specifies that the Background allows write access to its image + * at runtime. + */ + public static final int + ALLOW_IMAGE_WRITE = CapabilityBits.BACKGROUND_ALLOW_IMAGE_WRITE; + + /** + * Specifies that the Background allows read access to its color + * at runtime. + */ + public static final int + ALLOW_COLOR_READ = CapabilityBits.BACKGROUND_ALLOW_COLOR_READ; + + /** + * Specifies that the Background allows write access to its color + * at runtime. + */ + public static final int + ALLOW_COLOR_WRITE = CapabilityBits.BACKGROUND_ALLOW_COLOR_WRITE; + + /** + * Specifies that the Background allows read access to its + * background geometry at runtime. + */ + public static final int + ALLOW_GEOMETRY_READ = CapabilityBits.BACKGROUND_ALLOW_GEOMETRY_READ; + + /** + * Specifies that the Background allows write access to its + * background geometry at runtime. + */ + public static final int + ALLOW_GEOMETRY_WRITE = CapabilityBits.BACKGROUND_ALLOW_GEOMETRY_WRITE; + + /** + * Specifies that the Background allows read access to its image + * scale mode at runtime. + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_IMAGE_SCALE_MODE_READ = + CapabilityBits.BACKGROUND_ALLOW_IMAGE_SCALE_MODE_READ; + + /** + * Specifies that the Background allows write access to its image + * scale mode at runtime. + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_IMAGE_SCALE_MODE_WRITE = + CapabilityBits.BACKGROUND_ALLOW_IMAGE_SCALE_MODE_WRITE; + + + /** + * Indicates that no scaling of the background image is done. The + * image will be drawn in its actual size. If the window is + * smaller than the image, the image will be clipped. If the + * window is larger than the image, the portion of the window not + * filled by the image will be filled with the background color. + * In all cases, the upper left corner of the image is anchored at + * the upper-left corner of the window. + * This is the default mode. + * + * @see #setImageScaleMode + * + * @since Java 3D 1.3 + */ + public static final int SCALE_NONE = 0; + + /** + * Indicates that the background image is uniformly scaled to fit + * the window such that the entire image is visible. The image is + * scaled by the smaller of <code>window.width/image.width</code> + * and <code>window.height/image.height</code>. The image will + * exactly fill either the width or height of the window, but not + * necessarily both. The portion of the window not filled by the + * image will be filled with the background color. + * The upper left corner of the image is anchored at the + * upper-left corner of the window. + * + * @see #setImageScaleMode + * + * @since Java 3D 1.3 + */ + public static final int SCALE_FIT_MIN = 1; + + /** + * Indicates that the background image is uniformly scaled to fit + * the window such that the entire window is filled. The image is + * scaled by the larger of <code>window.width/image.width</code> + * and <code>window.height/image.height</code>. The image will + * entirely fill the window, but may by clipped either in <i>X</i> + * or <i>Y</i>. + * The upper left corner of the image is anchored at the + * upper-left corner of the window. + * + * @see #setImageScaleMode + * + * @since Java 3D 1.3 + */ + public static final int SCALE_FIT_MAX = 2; + + + /** + * Indicates that the background image is scaled to fit the + * window. The image is scaled non-uniformly in <i>x</i> and + * <i>y</i> by <code>window.width/image.width</code> and and + * <code>window.height/image.height</code>, respectively. The + * image will entirely fill the window. + * + * @see #setImageScaleMode + * + * @since Java 3D 1.3 + */ + public static final int SCALE_FIT_ALL = 3; + + /** + * Indicates that the background image is tiled to fill the entire + * window. The image is not scaled. + * The upper left corner of the image is anchored at the + * upper-left corner of the window. + * + * @see #setImageScaleMode + * + * @since Java 3D 1.3 + */ + public static final int SCALE_REPEAT = 4; + + /** + * Indicates that the background image is centered in the window + * and that no scaling of the image is done. The image will be + * drawn in its actual size. If the window is smaller than the + * image, the image will be clipped. If the window is larger than + * the image, the portion of the window not filled by the image + * will be filled with the background color. + * + * @see #setImageScaleMode + * + * @since Java 3D 1.3 + */ + public static final int SCALE_NONE_CENTER = 5; + + /** + * Constructs a Background node with default parameters. The default + * values are as follows: + * <ul> + * color : black (0,0,0)<br> + * image : null<br> + * geometry : null<br> + * image scale mode : SCALE_NONE<br> + * application bounds : null<br> + * application bounding leaf : null<br> + * </ul> + */ + public Background () { + // Just use the defaults + } + + /** + * Constructs a Background node with the specified color. + * This color is used to fill the window prior to drawing any + * objects in the scene. + */ + public Background(Color3f color) { + ((BackgroundRetained)this.retained).setColor(color); + } + + /** + * Constructs a Background node with the specified color. + * This color is used to fill the window prior to drawing any + * objects in the scene. + */ + public Background(float r, float g, float b) { + ((BackgroundRetained)this.retained).setColor(r, g, b); + } + + /** + * Constructs a Background node with the specified image. If this + * image is non-null, it is rendered to the window prior to + * drawing any objects in the scene. If the image is smaller + * than the window, + * then that portion of the window not covered by the image is + * filled with the background color. + * + * @param image pixel array object used as the background image + */ + public Background(ImageComponent2D image) { + ((BackgroundRetained)this.retained).setImage(image); + } + + /** + * Constructs a Background node with the specified geometry. + * If non-null, this background geometry is drawn on top of + * the background color and image using a projection + * matrix that essentially puts the geometry at infinity. The geometry + * should be pre-tessellated onto a unit sphere. + * @param branch the root of the background geometry + * @exception IllegalSharingException if the BranchGroup node + * is a child of any Group node, or is already attached to a Locale, + * or is already referenced by another Background node. + * @exception IllegalSceneGraphException if specified branch graph + * contains an illegal node. + */ + public Background(BranchGroup branch) { + ((BackgroundRetained)this.retained).setGeometry(branch); + } + + /** + * Sets the background color to the specified color. + * This color is used to fill the window prior to drawing any + * objects in the scene. + * @param color the new background color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setColor(Color3f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Background0")); + + if (isLive()) + ((BackgroundRetained)this.retained).setColor(color); + else + ((BackgroundRetained)this.retained).initColor(color); + + } + + /** + * Sets the background color to the specified color. + * This color is used to fill the window prior to drawing any + * objects in the scene. + * @param r the red component of the background color + * @param g the green component of the background color + * @param b the blue component of the background color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setColor(float r, float g, float b) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Background0")); + + if (isLive()) + ((BackgroundRetained)this.retained).setColor(r, g, b); + else + ((BackgroundRetained)this.retained).initColor(r, g, b); + } + + /** + * Retrieves the background color. + * @param color the vector that will receive the current background color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getColor(Color3f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Background2")); + + ((BackgroundRetained)this.retained).getColor(color); + } + + /** + * Sets the background image to the specified image. If this + * image is non-null, it is rendered to the window prior to + * drawing any objects in the scene. If the image is smaller + * than the window, + * then that portion of the window not covered by the image is + * filled with the background color. + * @param image new pixel array object used as the background image + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setImage(ImageComponent2D image) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_IMAGE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Background3")); + + if (isLive()) + ((BackgroundRetained)this.retained).setImage(image); + else + ((BackgroundRetained)this.retained).initImage(image); + } + + /** + * Retrieves the background image. + * @return the current background image + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public ImageComponent2D getImage() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Background4")); + + return ((BackgroundRetained)this.retained).getImage(); + } + + /** + * Sets the image scale mode for this Background node. + * + * @param imageScaleMode the new image scale mode, one of: + * SCALE_NONE, SCALE_FIT_MIN, SCALE_FIT_MAX, SCALE_FIT_ALL, + * SCALE_REPEAT, or SCALE_NONE_CENTER. + * + * @exception IllegalArgumentException if <code>imageScaleMode</code> + * is a value other than SCALE_NONE, SCALE_FIT_MIN, SCALE_FIT_MAX, + * SCALE_FIT_ALL, SCALE_REPEAT, or SCALE_NONE_CENTER. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setImageScaleMode(int imageScaleMode) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_IMAGE_SCALE_MODE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Background9")); + + switch (imageScaleMode) { + case SCALE_NONE: + case SCALE_FIT_MIN: + case SCALE_FIT_MAX: + case SCALE_FIT_ALL: + case SCALE_REPEAT: + case SCALE_NONE_CENTER: + break; + default: + throw new IllegalArgumentException(J3dI18N.getString("Background11")); + } + + if (isLive()) + ((BackgroundRetained)this.retained).setImageScaleMode(imageScaleMode); + else + ((BackgroundRetained)this.retained).initImageScaleMode(imageScaleMode); + + } + + /** + * Retrieves the current image scale mode. + * @return the current image scale mode, one of: + * SCALE_NONE, SCALE_FIT_MIN, SCALE_FIT_MAX, SCALE_FIT_ALL, + * SCALE_REPEAT, or SCALE_NONE_CENTER. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getImageScaleMode() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_IMAGE_SCALE_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Background10")); + return ((BackgroundRetained)this.retained).getImageScaleMode(); + } + + /** + * Sets the background geometry to the specified BranchGroup node. + * If non-null, this background geometry is drawn on top of + * the background color and image using a projection + * matrix that essentially puts the geometry at infinity. The geometry + * should be pre-tessellated onto a unit sphere. + * @param branch the root of the background geometry + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalSharingException if the BranchGroup node + * is a child of any Group node, or is already attached to a Locale, + * or is already referenced by another Background node. + * @exception IllegalSceneGraphException if specified branch graph + * contains an illegal node. + */ + public void setGeometry(BranchGroup branch) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_GEOMETRY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Background5")); + + if (isLive()) + ((BackgroundRetained)this.retained).setGeometry(branch); + else + ((BackgroundRetained)this.retained).initGeometry(branch); + } + + /** + * Retrieves the background geometry. + * @return the BranchGroup node that is the root of the background + * geometry + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BranchGroup getGeometry() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_GEOMETRY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Background6")); + + return ((BackgroundRetained)this.retained).getGeometry(); + } + + /** + * Set the Background's application region to the specified bounds. + * This is used when the application bounding leaf is set to null. + * @param region the bounds that contains the Background's new application + * region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setApplicationBounds(Bounds region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Background7")); + + if (isLive()) + ((BackgroundRetained)this.retained).setApplicationBounds(region); + else + ((BackgroundRetained)this.retained).initApplicationBounds(region); + } + + /** + * Retrieves the Background node's application bounds. + * @return this Background's application bounds information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getApplicationBounds() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Background8")); + + return ((BackgroundRetained)this.retained).getApplicationBounds(); + } + + /** + * Set the Background's application region to the specified bounding leaf. + * When set to a value other than null, this overrides the application + * bounds object. + * @param region the bounding leaf node used to specify the Background + * node's new application region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setApplicationBoundingLeaf(BoundingLeaf region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Background7")); + + if (isLive()) + ((BackgroundRetained)this.retained).setApplicationBoundingLeaf(region); + else + ((BackgroundRetained)this.retained).initApplicationBoundingLeaf(region); + } + + /** + * Retrieves the Background node's application bounding leaf. + * @return this Background's application bounding leaf information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BoundingLeaf getApplicationBoundingLeaf() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Background8")); + + return ((BackgroundRetained)this.retained).getApplicationBoundingLeaf(); + } + + /** + * Creates the retained mode BackgroundRetained object that this + * Background component object will point to. + */ + void createRetained() { + this.retained = new BackgroundRetained(); + this.retained.setSource(this); + } + + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied.<br> + * Background geometry will not clone in this operation. + * It is the user's responsibility + * to call <code>cloneTree</code> on that branchGroup. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Background b = new Background(); + b.duplicateNode(this, forceDuplicate); + return b; + } + + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @exception ClassCastException if originalNode is not an instance of + * <code>Background</code> + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean + forceDuplicate) { + checkDuplicateNode(originalNode, forceDuplicate); + } + + + /** + * Copies all Background information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + BackgroundRetained attr = (BackgroundRetained) originalNode.retained; + BackgroundRetained rt = (BackgroundRetained) retained; + + Color3f c = new Color3f(); + attr.getColor(c); + rt.initColor(c); + rt.initApplicationBounds(attr.getApplicationBounds()); + rt.initGeometry(attr.getGeometry()); + rt.initImage((ImageComponent2D) getNodeComponent( + attr.getImage(), + forceDuplicate, + originalNode.nodeHashtable)); + + // this will be updated in updateNodeReferences + rt.initApplicationBoundingLeaf(attr.getApplicationBoundingLeaf()); + } + + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances + * + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + super.updateNodeReferences(referenceTable); + + BackgroundRetained rt = (BackgroundRetained) retained; + BoundingLeaf bl= rt.getApplicationBoundingLeaf(); + + if (bl != null) { + Object o = referenceTable.getNewObjectReference(bl); + rt.initApplicationBoundingLeaf((BoundingLeaf) o); + } + } +} diff --git a/src/classes/share/javax/media/j3d/BackgroundRetained.java b/src/classes/share/javax/media/j3d/BackgroundRetained.java new file mode 100644 index 0000000..38edb3f --- /dev/null +++ b/src/classes/share/javax/media/j3d/BackgroundRetained.java @@ -0,0 +1,817 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; +import java.awt.image.BufferedImage; + + +/** + * The Background leaf node defines either a solid background color + * or a background image that is used to fill the window at the + * beginning of each new frame. It also specifies an application + * region in which this background is active. + */ +class BackgroundRetained extends LeafRetained { + + static final int COLOR_CHANGED = 0x00001; + static final int IMAGE_CHANGED = 0x00002; + static final int GEOMETRY_CHANGED = 0x00004; + static final int BOUNDS_CHANGED = 0x00008; + static final int BOUNDINGLEAF_CHANGED = 0x00010; + static final int IMAGE_SCALE_CHANGED = 0x00020; + // Background color or image. If non-null, the image overrides the + // color. + Color3f color = new Color3f(0.0f, 0.0f, 0.0f); + ImageComponent2DRetained image = null; + + // the image scale mode if image is used. + int imageScaleMode = Background.SCALE_NONE; + + /** + * The Boundary object defining the lights's application region. + */ + Bounds applicationRegion = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * Background geometry branch group + */ + BranchGroup geometryBranch = null; + + /** + * The transformed value of the applicationRegion. + */ + Bounds transformedRegion = null; + + /** + * The state structure used for Background Geometry + */ + SetLiveState setLiveState = null; + + /** + * The locale of this Background node since we don't have mirror object + * when clearLive is called + * locale is set to null, we still want locale to have a + * non-null value, since renderingEnv structure may be using the + * locale + */ + Locale cachedLocale = null; + + // This is true when this background is referenced in an immediate mode context + boolean inImmCtx = false; + + // list of light nodes for background geometry + ArrayList lights = new ArrayList(); + + // list of fog nodes for background geometry + ArrayList fogs = new ArrayList(); + + // a list of background geometry atoms + ArrayList bgGeometryAtomList = new ArrayList(); + + // false is background geometry atoms list has changed + boolean bgGeometryAtomListDirty = true; + + // an array of background geometry atoms + GeometryAtom[] bgGeometryAtoms = null; + + // Target threads to be notified when light changes + // Note, the rendering env structure only get notified + // when there is a bounds related change + final static int targetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + + // Is true, if the background is viewScoped + boolean isViewScoped = false; + + // for texture mapping the background + ImageComponent2DRetained texImage = null; + int xmax = 0; + int ymax = 0; + + BackgroundRetained () { + this.nodeType = NodeRetained.BACKGROUND; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + + /** + * Initializes the background color to the specified color. + * This color is used + * if the image is null. + * @param color the new background color + */ + final void initColor(Color3f color) { + this.color.set(color); + } + + + /** + * Sets the background color to the specified color. This color is used + * if the image is null. + * @param color the new background color + */ + final void setColor(Color3f color) { + initColor(color); + if (source.isLive()) { + sendMessage(COLOR_CHANGED, new Color3f(color)); + } + } + + /** + * Initializes the background color to the specified color. + * This color is used + * if the image is null. + * @param r the red component of the background color + * @param g the green component of the background color + * @param b the blue component of the background color + */ + final void initColor(float r, float g, float b) { + this.color.x = r; + this.color.y = g; + this.color.z = b; + } + + + + /** + * Sets the background color to the specified color. This color is used + * if the image is null. + * @param r the red component of the background color + * @param g the green component of the background color + * @param b the blue component of the background color + */ + final void setColor(float r, float g, float b) { + setColor(new Color3f(r, g, b)); + } + + + /** + * Retrieves the background color. + * @param color the vector that will receive the current background color + */ + final void getColor(Color3f color) { + color.set(this.color); + } + + /** + * Initialize the image scale mode to the specified mode + * @imageScaleMode the image scale mode to the used + */ + final void initImageScaleMode(int imageScaleMode){ + this.imageScaleMode = imageScaleMode; + } + + /** + * Sets the image scale mode for this Background node. + * @param imageScaleMode the image scale mode + */ + final void setImageScaleMode(int imageScaleMode){ + initImageScaleMode(imageScaleMode); + if(source.isLive()){ + sendMessage(IMAGE_SCALE_CHANGED, new Integer(imageScaleMode)); + } + } + + /** + * gets the image scale mode for this Background node. + */ + final int getImageScaleMode(){ + return imageScaleMode; + } + + /** + * Initializes the background image to the specified image. + * @param image new ImageCompoent3D object used as the background image + */ + final void initImage(ImageComponent2D img) { + if (img != null) { + // scale to power of 2 for texture mapping + ImageComponent2DRetained rimage = (ImageComponent2DRetained) img.retained; + + if (!VirtualUniverse.mc.isBackgroundTexture) { + rimage.setRasterRef(); + } + else { +// rimage.setTextureRef(); + + xmax = rimage.width; + ymax = rimage.height; + int width = getClosestPowerOf2(xmax); + int height = getClosestPowerOf2(ymax); + float xScale = (float)width/(float)xmax; + float yScale = (float)height/(float)ymax; + + // scale if scales aren't 1.0 + if (!(xScale == 1.0f && yScale == 1.0f)) { + BufferedImage origImg = (BufferedImage) rimage.getImage(); + AffineTransform at = AffineTransform.getScaleInstance(xScale, + yScale); + AffineTransformOp atop = new AffineTransformOp(at, + AffineTransformOp.TYPE_BILINEAR); + BufferedImage scaledImg = atop.filter(origImg, null); + int format = rimage.getFormat(); + boolean yUp = rimage.isYUp(); + boolean byRef = rimage.isByReference(); + ImageComponent2D ic = new ImageComponent2D(format, + scaledImg, + byRef, yUp); + texImage = (ImageComponent2DRetained)ic.retained; + texImage.setTextureRef(); + //rimage.setTextureRef(); + //texImage.setRasterRef(); + } + else { + texImage = rimage; + texImage.setTextureRef(); + //rimage.setTextureRef(); + //texImage.setRasterRef(); + } + } + + this.image = rimage; + } else { + this.image = null; + this.texImage = null; + } + } + + private int getClosestPowerOf2(int value) { + + if (value < 1) + return value; + + int powerValue = 1; + for (;;) { + powerValue *= 2; + if (value < powerValue) { + // Found max bound of power, determine which is closest + int minBound = powerValue/2; + if ((powerValue - value) > + (value - minBound)) + return minBound; + else + return powerValue; + } + } + } + + + + /** + * Sets the background image to the specified image. + * @param image new ImageCompoent3D object used as the background image + */ + final void setImage(ImageComponent2D img) { + if (source.isLive()) { + if (this.image != null) { + this.image.clearLive(refCount); + } + } + initImage(img); + if (source.isLive()) { + if (img != null) { + ((ImageComponent2DRetained) + img.retained).setLive(inBackgroundGroup, refCount); + } + sendMessage(IMAGE_CHANGED, + (image != null ? image.clone() : null)); + } + } + + /** + * Retrieves the background image. + * @return the current background image + */ + final ImageComponent2D getImage() { + return (image == null ? null : + (ImageComponent2D)image.source); + } + + /** + * Initializes the background geometry branch group to the specified branch. + * @param branch new branch group object used for background geometry + */ + final void initGeometry(BranchGroup branch) { + geometryBranch = branch; + } + + + /** + * Sets the background geometry branch group to the specified branch. + * @param branch new branch group object used for background geometry + */ + final void setGeometry(BranchGroup branch) { + int numMessages = 0; + int i; + + if (source.isLive()) { + J3dMessage m[]; + if (geometryBranch != null) + numMessages+=2; // REMOVE_NODES, ORDERED_GROUP_REMOVED + if (branch != null) + numMessages+=2; // INSERT_NODES, ORDERED_GROUP_INSERTED + m = new J3dMessage[numMessages]; + for (i=0; i<numMessages; i++) { + m[i] = VirtualUniverse.mc.getMessage(); + } + i = 0; + if (geometryBranch != null) { + clearGeometryBranch((BranchGroupRetained)geometryBranch.retained); + m[i].threads = (J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_RENDERING_ENVIRONMENT); + m[i].type = J3dMessage.ORDERED_GROUP_REMOVED; + m[i].universe = universe; + m[i].args[0] = setLiveState.ogList.toArray(); + m[i].args[1] = setLiveState.ogChildIdList.toArray(); + m[i].args[3] = setLiveState.ogCIOList.toArray(); + m[i].args[4] = setLiveState.ogCIOTableList.toArray(); + i++; + + m[i].threads = setLiveState.notifyThreads; + m[i].type = J3dMessage.REMOVE_NODES; + m[i].universe = universe; + m[i].args[0] = setLiveState.nodeList.toArray(); + i++; + + } + if (branch != null) { + setGeometryBranch((BranchGroupRetained)branch.retained); + m[i].threads = (J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_RENDERING_ENVIRONMENT); + m[i].type = J3dMessage.ORDERED_GROUP_INSERTED; + m[i].universe = universe; + m[i].args[0] = setLiveState.ogList.toArray(); + m[i].args[1] = setLiveState.ogChildIdList.toArray(); + m[i].args[2] = setLiveState.ogOrderedIdList.toArray(); + m[i].args[3] = setLiveState.ogCIOList.toArray(); + m[i].args[4] = setLiveState.ogCIOTableList.toArray(); + i++; + + m[i].threads = setLiveState.notifyThreads; + m[i].type = J3dMessage.INSERT_NODES; + m[i].universe = universe; + m[i].args[0] = setLiveState.nodeList.toArray(); + } + VirtualUniverse.mc.processMessage(m); + // Free up memory + setLiveState.reset(null); + } + initGeometry(branch); + } + + /** + * Retrieves the background geometry branch group. + * @return the current background geometry branch group + */ + final BranchGroup getGeometry() { + return geometryBranch; + } + + /** + * Initializes the Background's application region. + * @param region a region that contains the Backgound's new application bounds + */ + final void initApplicationBounds(Bounds region) { + if (region != null) { + applicationRegion = (Bounds) region.clone(); + } else { + applicationRegion = null; + } + } + + /** + * Set the Background's application region. + * @param region a region that contains the Backgound's new application bounds + */ + final void setApplicationBounds(Bounds region) { + initApplicationBounds(region); + // Don't send the message if there is a valid boundingleaf + if (boundingLeaf == null) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads | + J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.type = J3dMessage.BACKGROUND_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(BOUNDS_CHANGED); + if (region != null) + createMessage.args[2] = region.clone(); + else + createMessage.args[2] = null; + VirtualUniverse.mc.processMessage(createMessage); + } + } + + /** + * Get the Backgound's application region. + * @return this Backgound's application region information + */ + final Bounds getApplicationBounds() { + return (applicationRegion != null ? (Bounds) applicationRegion.clone() : null); + } + + /** + * Initializes the Background's application region + * to the specified Leaf node. + */ + void initApplicationBoundingLeaf(BoundingLeaf region) { + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + } else { + boundingLeaf = null; + } + } + + /** + * Set the Background's application region to the specified Leaf node. + */ + void setApplicationBoundingLeaf(BoundingLeaf region) { + if (boundingLeaf != null) + boundingLeaf.mirrorBoundingLeaf.removeUser(this); + + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + boundingLeaf.mirrorBoundingLeaf.addUser(this); + } else { + boundingLeaf = null; + } + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads | + J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.type = J3dMessage.BACKGROUND_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(BOUNDINGLEAF_CHANGED); + if (boundingLeaf != null) { + createMessage.args[2] = boundingLeaf.mirrorBoundingLeaf; + createMessage.args[3] = null; + } else { + createMessage.args[2] = null; + if (applicationRegion != null) + createMessage.args[3] = applicationRegion.clone(); + else + createMessage.args[3] = null; + } + VirtualUniverse.mc.processMessage(createMessage); + } + + + /** + * Get the Background's application region + */ + BoundingLeaf getApplicationBoundingLeaf() { + return (boundingLeaf != null ? + (BoundingLeaf)boundingLeaf.source : null); + } + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return (inImmCtx); + } + + void setGeometryBranch(BranchGroupRetained branch) { + setLiveState.reset(locale); + setLiveState.inBackgroundGroup = true; + + setLiveState.geometryBackground = this; + setLiveState.currentTransforms[0] = new Transform3D[2]; + setLiveState.currentTransforms[0][0] = new Transform3D(); + setLiveState.currentTransforms[0][1] = new Transform3D(); + setLiveState.currentTransformsIndex[0] = new int[2]; + setLiveState.currentTransformsIndex[0][0] = 0; + setLiveState.currentTransformsIndex[0][1] = 0; + + setLiveState.localToVworld = setLiveState.currentTransforms; + setLiveState.localToVworldIndex = setLiveState.currentTransformsIndex; + + setLiveState.branchGroupPaths = new ArrayList(); + setLiveState.branchGroupPaths.add(new BranchGroupRetained[0]); + + setLiveState.orderedPaths = new ArrayList(1); + setLiveState.orderedPaths.add(new OrderedPath()); + + setLiveState.switchStates = new ArrayList(1); + setLiveState.switchStates.add(new SwitchState(false)); + + + branch.setLive(setLiveState); + + + } + + void clearGeometryBranch(BranchGroupRetained branch) { + setLiveState.reset(locale); + setLiveState.inBackgroundGroup = true; + setLiveState.geometryBackground = this; + branch.clearLive(setLiveState); + branch.setParent(null); + branch.setLocale(null); + + } + + + /** + * This setLive routine first calls the superclass's method, then + * it adds itself to the list of lights + */ + void setLive(SetLiveState s) { + TransformGroupRetained[] tlist; + int i; + + super.doSetLive(s); + + if (inImmCtx) { + throw new IllegalSharingException( + J3dI18N.getString("BackgroundRetained1")); + } + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("BackgroundRetained5")); + } + + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("BackgroundRetained6")); + } + + + if (geometryBranch != null) { + BranchGroupRetained branch = + (BranchGroupRetained)geometryBranch.retained; + if (branch.inBackgroundGroup == true) + throw new IllegalSharingException( + J3dI18N.getString("BackgroundRetained0")); + + if (branch.parent != null) + throw new IllegalSharingException( + J3dI18N.getString("BackgroundRetained3")); + + if (branch.locale != null) + throw new IllegalSharingException( + J3dI18N.getString("BackgroundRetained4")); + + if (setLiveState == null) { + setLiveState = new SetLiveState(universe); + setLiveState.universe = universe; + } + setGeometryBranch((BranchGroupRetained)geometryBranch.retained); + // add background geometry nodes to setLiveState's nodeList + s.nodeList.addAll(setLiveState.nodeList); + s.notifyThreads |= setLiveState.notifyThreads; + s.ogList.addAll(setLiveState.ogList); + s.ogChildIdList.addAll(setLiveState.ogChildIdList); + s.ogOrderedIdList.addAll(setLiveState.ogOrderedIdList); + // Free up memory. + setLiveState.reset(null); + } + + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(this); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(this); + } + // System.out.println("bkg.setlive nodeList " + s.nodeList); + + // process switch leaf + if (s.switchTargets != null && s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.ENV_TARGETS); + } + switchState = (SwitchState)s.switchStates.get(0); + + // Initialize some mirror values + if (boundingLeaf != null) { + transformedRegion = + (Bounds)boundingLeaf.mirrorBoundingLeaf.transformedRegion; + } + else { // Evaluate applicationRegion if not null + if (applicationRegion != null) { + transformedRegion = (Bounds)applicationRegion.clone(); + transformedRegion.transform( + applicationRegion, + getLastLocalToVworld()); + } + else { + transformedRegion = null; + } + + } + cachedLocale = s.locale; + + // add this node to the transform target + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + + if (image != null) { + image.setLive(inBackgroundGroup, refCount); + } + super.markAsLive(); + + } + + /** + * This clearLive routine first calls the superclass's method, then + * it removes itself to the list of lights + */ + void clearLive(SetLiveState s) { + super.clearLive(s); + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(this); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(this); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + if (s.switchTargets != null && s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.ENV_TARGETS); + } + + if (geometryBranch != null) { + BranchGroupRetained branch = + (BranchGroupRetained)geometryBranch.retained; + clearGeometryBranch((BranchGroupRetained)geometryBranch.retained); + // add background geometry nodes to setLiveState's nodeList + s.nodeList.addAll(setLiveState.nodeList); + s.ogList.addAll(setLiveState.ogList); + s.ogChildIdList.addAll(setLiveState.ogChildIdList); + s.notifyThreads |= setLiveState.notifyThreads; + // Free up memory. + setLiveState.reset(null); + lights.clear(); + fogs.clear(); + } + + if (image != null) { + image.clearLive(refCount); + } + + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + } + + + + + // The update Object function. + synchronized void updateImmediateMirrorObject(Object[] objs) { + int component = ((Integer)objs[1]).intValue(); + Transform3D trans; + // If initialization + + // Bounds message only sent when boundingleaf is null + if ((component & BOUNDS_CHANGED) != 0) { + if (objs[2] != null) { + transformedRegion = ((Bounds)((Bounds) objs[2])).copy(transformedRegion); + transformedRegion.transform( + (Bounds) objs[2], getCurrentLocalToVworld()); + } + else { + transformedRegion = null; + } + } + else if ((component & BOUNDINGLEAF_CHANGED) != 0) { + if (objs[2] != null) { + transformedRegion = ((BoundingLeafRetained)objs[2]).transformedRegion; + } + else { // Evaluate applicationRegion if not null + Bounds appRegion = (Bounds)objs[3]; + if (appRegion != null) { + transformedRegion = appRegion.copy(transformedRegion); + transformedRegion.transform( + appRegion, getCurrentLocalToVworld()); + } + else { + transformedRegion = null; + } + + } + } + + } + + /** Note: This routine will only be called + * to update the object's + * transformed region + */ + void updateBoundingLeaf() { + if (boundingLeaf != null && + boundingLeaf.mirrorBoundingLeaf.switchState.currentSwitchOn) { + transformedRegion = + boundingLeaf.mirrorBoundingLeaf.transformedRegion; + } else { // Evaluate applicationRegion if not null + if (applicationRegion != null) { + transformedRegion = applicationRegion.copy(transformedRegion); + transformedRegion.transform( + applicationRegion, getCurrentLocalToVworld()); + } else { + transformedRegion = null; + } + } + } + + void updateImmediateTransformChange() { + // If bounding leaf is null, tranform the bounds object + if (boundingLeaf == null) { + if (applicationRegion != null) { + transformedRegion = applicationRegion.copy(transformedRegion); + transformedRegion.transform( + applicationRegion, getCurrentLocalToVworld()); + } + } + } + + + final void sendMessage(int attrMask, Object attr) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.universe = universe; + createMessage.type = J3dMessage.BACKGROUND_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + VirtualUniverse.mc.processMessage(createMessage); + } + + void addBgGeometryAtomList(GeometryAtom geomAtom) { + bgGeometryAtomList.add(geomAtom); + bgGeometryAtomListDirty = true; + } + + void removeBgGeometryAtomList(GeometryAtom geomAtom) { + bgGeometryAtomList.remove(bgGeometryAtomList.indexOf(geomAtom)); + bgGeometryAtomListDirty = true; + } + + GeometryAtom[] getBackgroundGeometryAtoms() { + if (bgGeometryAtomListDirty) { + int nAtoms = bgGeometryAtomList.size(); + if (nAtoms == 0) { + bgGeometryAtoms = null; + } else { + bgGeometryAtoms = new GeometryAtom[nAtoms]; + for (int i=0; i<bgGeometryAtoms.length; i++) { + bgGeometryAtoms[i] = (GeometryAtom)bgGeometryAtomList.get(i) ; + } + bgGeometryAtomListDirty = false; + } + } + return(bgGeometryAtoms); + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + if (applicationRegion != null) { + applicationRegion.transform(xform.transform); + } + } + + // notifies the Background object that the image data in a referenced + // ImageComponent object is changed. + // Currently we are not making use of this information. + + void notifyImageComponentImageChanged(ImageComponentRetained image, + ImageComponentUpdateInfo value) { + } + void getMirrorObjects(ArrayList leafList, HashKey key) { + leafList.add(this); // No Mirror in this case + } +} diff --git a/src/classes/share/javax/media/j3d/BackgroundSound.java b/src/classes/share/javax/media/j3d/BackgroundSound.java new file mode 100644 index 0000000..0eff076 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BackgroundSound.java @@ -0,0 +1,136 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A BackgroundSound node defines an unattenuated, nonspatialized sound + * source that has no position or direction. It has the same attributes as a + * Sound node. This type of sound is simply added to the sound mix without + * modification and is useful for playing a mono or stereo music track, or an + * ambient sound effect. Unlike a Background (visual) node, more than one + * BackgroundSound node can be simultaneously enabled and active. + */ +public class BackgroundSound extends Sound { + /** + * Constructs a new BackgroundSound node using the default parameters + * for Sound nodes. + */ + public BackgroundSound() { + /** + * Uses default values defined in SoundRetained.java + */ + } + + /** + * Constructs a BackgroundSound node object using only the provided + * parameter values for sound data and sample gain. The remaining fields + * are set to the default values for a Sound node. + * @param soundData sound data associated with this sound source node + * @param initialGain amplitude scale factor applied to sound source + */ + public BackgroundSound(MediaContainer soundData, float initialGain ) { + super(soundData, initialGain); + } + + /** + * Constructs a BackgroundSound object accepting all the parameters + * associated with a Sound node. + * @param soundData sound data associated with this sound source node + * @param initialGain amplitude scale factor applied to sound source + * @param loopCount number of times loop is looped + * @param release flag denoting playing sound data to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + */ + public BackgroundSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority) { + + super(soundData, initialGain, loopCount, release, continuous, + enable, region, priority ); + } + + + /** + * Creates the retained mode BackgroundSoundRetained object that this + * BackgroundSound component object will point to. + */ + void createRetained() { + this.retained = new BackgroundSoundRetained(); + this.retained.setSource(this); + } + + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + BackgroundSound b = new BackgroundSound(); + b.duplicateNode(this, forceDuplicate); + return b; + } + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @exception ClassCastException if originalNode is not an instance of + * <code>Sound</code> + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + checkDuplicateNode(originalNode, forceDuplicate); + } +} diff --git a/src/classes/share/javax/media/j3d/BackgroundSoundRetained.java b/src/classes/share/javax/media/j3d/BackgroundSoundRetained.java new file mode 100644 index 0000000..389d9c0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BackgroundSoundRetained.java @@ -0,0 +1,28 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * BackgroundSound is a class for sounds that are not spatially rendered. + * These sounds are simply added to the stereo sound mix without modification. + * These could be used to play mono or stereo music, or ambient sound effects. + */ +class BackgroundSoundRetained extends SoundRetained { + + BackgroundSoundRetained() { + this.nodeType = NodeRetained.BACKGROUNDSOUND; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } +} diff --git a/src/classes/share/javax/media/j3d/BadTransformException.java b/src/classes/share/javax/media/j3d/BadTransformException.java new file mode 100644 index 0000000..bf53ae5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BadTransformException.java @@ -0,0 +1,55 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates an attempt to use a Tranform3D object that is + * inappropriate for the object in which it is being used. + * For example: + * <ul> + * <li> + * Transforms that are used in the scene graph, within a TransformGroup + * node, must be affine. They may optionally contain a non-uniform + * scale and/or a shear, subject to other listed restrictions. + * <li> + * All transforms in the TransformGroup nodes above a ViewPlatform + * object must be congruent. This ensures that the Vworld coordinates to + * ViewPlatform coordinates transform is angle and length-preserving with + * no shear and only uniform scale. + * <li> + * Most viewing transforms other than those in the scene graph can + * only contain translation and rotation. + * <li> + * The projection transform is allowed to be non-affine, but it + * must either be a single point perspective projection or a parallel + * projection. + * </ul> + */ +public class BadTransformException extends RuntimeException{ + +/** + * Create the exception object with default values. + */ + public BadTransformException(){ + } + +/** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public BadTransformException(String str){ + + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/Behavior.java b/src/classes/share/javax/media/j3d/Behavior.java new file mode 100644 index 0000000..22e2dfa --- /dev/null +++ b/src/classes/share/javax/media/j3d/Behavior.java @@ -0,0 +1,500 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +import java.util.Enumeration; + +/** + * The Behavior leaf node provides a framework for adding user-defined + * actions into the scene graph. Behavior is an abstract class that + * defines two methods that must be overridden by a subclass: An + * <code>initialization</code> method, called once when the behavior + * becomes "live," and a <code>processStimulus</code> method called + * whenever appropriate by the Java 3D behavior scheduler. The + * Behavior node also contains an enable flag, a scheduling region, + * a scheduling interval, and a wakeup condition. + * + * <P> + * The <i>scheduling region</i> defines a spatial volume that serves + * to enable the scheduling of Behavior nodes. A Behavior node is + * <i>active</i> (can receive stimuli) whenever an active ViewPlatform's + * activation volume intersects a Behavior object's scheduling + * region. Only active behaviors can receive stimuli. + * + * <P> + * The <i>scheduling interval</i> defines a partial order of execution + * for behaviors that wake up in response to the same wakeup condition + * (that is, those behaviors that are processed at the same "time"). + * Given a set of behaviors whose wakeup conditions are satisfied at + * the same time, the behavior scheduler will execute all behaviors in + * a lower scheduling interval before executing any behavior in a + * higher scheduling interval. Within a scheduling interval, + * behaviors can be executed in any order, or in parallel. Note that + * this partial ordering is only guaranteed for those behaviors that + * wake up at the same time in response to the same wakeup condition, + * for example, the set of behaviors that wake up every frame in + * response to a WakeupOnElapsedFrames(0) wakeup condition. + * + * <P> + * The <code>initialize</code> method allows a Behavior object to + * initialize its internal state and specify its initial wakeup + * condition(s). Java 3D invokes a behavior's initialize code when the + * behavior's containing BranchGroup node is added to the virtual + * universe. Java 3D does not invoke the initialize method in a new + * thread. Thus, for Java 3D to regain control, the initialize method + * must not execute an infinite loop; it must return. Furthermore, a + * wakeup condition must be set or else the behavior's processStimulus + * method is never executed. + * + * <P> + * The <code>processStimulus</code> method receives and processes a + * behavior's ongoing messages. The Java 3D behavior scheduler invokes + * a Behavior node's processStimulus method when an active ViewPlatform's + * activation volume intersects a Behavior object's scheduling region + * and all of that behavior's wakeup criteria are satisfied. The + * processStimulus method performs its computations and actions + * (possibly including the registration of state change information + * that could cause Java 3D to wake other Behavior objects), + * establishes its next wakeup condition, and finally exits. + * A typical behavior will modify one or more nodes or node components + * in the scene graph. These modifications can happen in parallel + * with rendering. In general, applications cannot count on behavior + * execution being synchronized with rendering. There are two + * exceptions to this general rule: + * <ol> + * <li>All modifications to scene graph objects (not including geometry + * by-reference or texture by-reference) made from the + * <code>processStimulus</code> method of a single behavior instance + * are guaranteed to take effect in the same rendering frame.</li> + * <li>All modifications to scene graph objects (not including geometry + * by-reference or texture by-reference) made from the + * <code>processStimulus</code> methods of the set of behaviors that + * wake up in response to a WakeupOnElapsedFrames(0) wakeup condition + * are guaranteed to take effect in the same rendering frame.</li> + * </ol> + * + * Note that modifications to geometry by-reference or texture + * by-reference are not guaranteed to show up in the same frame as + * other scene graph changes. + * + * <P> + * <b>Code Structure</b> + * <P> + * When the Java 3D behavior scheduler invokes a Behavior object's + * processStimulus method, that method may perform any computation it + * wishes. Usually, it will change its internal state and specify its + * new wakeup conditions. Most probably, it will manipulate scene + * graph elements. However, the behavior code can only change those + * aspects of a scene graph element permitted by the capabilities + * associated with that scene graph element. A scene graph's + * capabilities restrict behavioral manipulation to those + * manipulations explicitly allowed. + * + * <P> + * The application must provide the Behavior object with references to + * those scene graph elements that the Behavior object will + * manipulate. The application provides those references as arguments + * to the behavior's constructor when it creates the Behavior + * object. Alternatively, the Behavior object itself can obtain access + * to the relevant scene graph elements either when Java 3D invokes + * its initialize method or each time Java 3D invokes its + * processStimulus method. + * + * <P> + * Behavior methods have a very rigid structure. Java 3D assumes that + * they always run to completion (if needed, they can spawn + * threads). Each method's basic structure consists of the following: + * + * <P> + * <UL> + * <LI>Code to decode and extract references from the WakeupCondition + * enumeration that caused the object's awakening.</LI> + * <LI>Code to perform the manipulations associated with the + * WakeupCondition</LI> + * <LI>Code to establish this behavior's new WakeupCondition</LI> + * <LI>A path to Exit (so that execution returns to the Java 3D + * behavior scheduler)</LI> + * </UL> + * + * <P> + * <b>WakeupCondition Object</b> + * <P> + * A WakeupCondition object is an abstract class specialized to + * fourteen different WakeupCriterion objects and to four combining + * objects containing multiple WakeupCriterion objects. A Behavior + * node provides the Java 3D behavior scheduler with a WakeupCondition + * object. When that object's WakeupCondition has been satisfied, the + * behavior scheduler hands that same WakeupCondition back to the + * Behavior via an enumeration. + * + * <P> + * <b>WakeupCriterion Object</b> + * <P> + * Java 3D provides a rich set of wakeup criteria that Behavior + * objects can use in specifying a complex WakeupCondition. These + * wakeup criteria can cause Java 3D's behavior scheduler to invoke a + * behavior's processStimulus method whenever + * + * <UL> + * <LI>The center of a ViewPlatform enters a specified region</LI> + * <LI>The center of a ViewPlatform exits a specified region</LI> + * <LI>A behavior is activated</LI> + * <LI>A behavior is deactivated</LI> + * <LI>A specified TransformGroup node's transform changes</LI> + * <LI>Collision is detected between a specified Shape3D node's + * Geometry object and any other object</LI> + * <LI>Movement occurs between a specified Shape3D node's Geometry + * object and any other object with which it collides</LI> + * <LI>A specified Shape3D node's Geometry object no longer collides + * with any other object</LI> + * <LI>A specified Behavior object posts a specific event</LI> + * <LI>A specified AWT event occurs</LI> + * <LI>A specified time interval elapses</LI> + * <LI>A specified number of frames have been drawn</LI> + * <LI>The center of a specified Sensor enters a specified region</LI> + * <LI>The center of a specified Sensor exits a specified region</LI> + * </UL> + * + * <p> + * A Behavior object constructs a WakeupCriterion by constructing the + * appropriate criterion object. The Behavior object must provide the + * appropriate arguments (usually a reference to some scene graph + * object and possibly a region of interest). Thus, to specify a + * WakeupOnViewPlatformEntry, a behavior would specify the region that + * will cause the behavior to execute if an active ViewPlatform enters it. + * + * <p> + * Note that a unique WakeupCriterion object must be used with each + * instance of a Behavior. Sharing wakeup criteria among different + * instances of a Behavior is illegal. + * + * @see WakeupCondition + */ + +public abstract class Behavior extends Leaf { + + /** + * Constructs a Behavior node with default parameters. The default + * values are as follows: + * <ul> + * enable flag : true<br> + * scheduling bounds : null<br> + * scheduling bounding leaf : null<br> + * scheduling interval : numSchedulingIntervals / 2<br> + * </ul> + */ + public Behavior() { + } + + /** + * Initialize this behavior. Classes that extend Behavior must + * provide their own initialize method. + * <br> + * NOTE: Applications should <i>not</i> call this method. It is called + * by the Java 3D behavior scheduler. + */ + public abstract void initialize(); + + /** + * Process a stimulus meant for this behavior. This method is invoked + * if the Behavior's wakeup criteria are satisfied and an active + * ViewPlatform's + * activation volume intersects with the Behavior's scheduling region. + * Classes that extend Behavior must provide their own processStimulus + * method. + * <br> + * NOTE: Applications should <i>not</i> call this method. It is called + * by the Java 3D behavior scheduler. + * @param criteria an enumeration of triggered wakeup criteria for this + * behavior + */ + public abstract void processStimulus(Enumeration criteria); + + /** + * Set the Behavior's scheduling region to the specified bounds. + * This is used when the scheduling bounding leaf is set to null. + * @param region the bounds that contains the Behavior's new scheduling + * region + */ + public void setSchedulingBounds(Bounds region) { + ((BehaviorRetained)this.retained).setSchedulingBounds(region); + } + + /** + * Retrieves the Behavior node's scheduling bounds. + * @return this Behavior's scheduling bounds information + */ + public Bounds getSchedulingBounds() { + return ((BehaviorRetained)this.retained).getSchedulingBounds(); + } + + /** + * Set the Behavior's scheduling region to the specified bounding leaf. + * When set to a value other than null, this overrides the scheduling + * bounds object. + * @param region the bounding leaf node used to specify the Behavior + * node's new scheduling region + */ + public void setSchedulingBoundingLeaf(BoundingLeaf region) { + ((BehaviorRetained)this.retained).setSchedulingBoundingLeaf(region); + } + + /** + * Retrieves the Behavior node's scheduling bounding leaf. + * @return this Behavior's scheduling bounding leaf information + */ + public BoundingLeaf getSchedulingBoundingLeaf() { + return ((BehaviorRetained)this.retained).getSchedulingBoundingLeaf(); + } + + /** + * Creates the retained mode BehaviorRetained object that this + * Behavior object will point to. + */ + void createRetained() { + this.retained = new BehaviorRetained(); + this.retained.setSource(this); + } + + /** + * Defines this behavior's wakeup criteria. This method + * may only be called from a Behavior object's initialize + * or processStimulus methods to (re)arm the next wakeup. + * It should be the last thing done by those methods. + * @param criteria the wakeup criteria for this behavior + * @exception IllegalStateException if this method is called by + * a method <i>other than</i> initialize or processStimulus + */ + protected void wakeupOn(WakeupCondition criteria) { + BehaviorRetained behavret = (BehaviorRetained) this.retained; + synchronized (behavret) { + if (!behavret.inCallback) { + throw new IllegalStateException(J3dI18N.getString("Behavior0")); + } + } + behavret.wakeupOn(criteria); + } + + /** + * Retrieves this behavior's current wakeup condition as set by + * the wakeupOn method. If no wakeup condition is currently + * active, null will be returned. In particular, this means that + * null will be returned if Java 3D is executing this behavior's + * processStimulus routine and wakeupOn has not yet been called to + * re-arm the wakeup condition for next time. + * + * @return the current wakeup condition for this behavior + * + * @since Java 3D 1.3 + */ + protected WakeupCondition getWakeupCondition() { + return ((BehaviorRetained)this.retained).getWakeupCondition(); + } + + /** + * Posts the specified postId to the Behavior Scheduler. All behaviors + * that have registered WakeupOnBehaviorPost with this postId, or a postId + * of 0, and with this behavior, or a null behavior, will have that wakeup + * condition met. + * <p> + * This feature allows applications to send arbitrary events into the + * behavior scheduler stream. It can be used as a notification scheme + * for communicating events to behaviors in the system. + * </p> + * @param postId the Id being posted + * + * @see WakeupOnBehaviorPost + */ + public void postId(int postId){ + ((BehaviorRetained)this.retained).postId(postId); + } + + /** + * Enables or disables this Behavior. The default state is enabled. + * @param state true or false to enable or disable this Behavior + */ + public void setEnable(boolean state) { + ((BehaviorRetained)this.retained).setEnable(state); + } + + /** + * Retrieves the state of the Behavior enable flag. + * @return the Behavior enable state + */ + public boolean getEnable() { + return ((BehaviorRetained)this.retained).getEnable(); + } + + /** + * Returns the number of scheduling intervals supported by this + * implementation of Java 3D. The minimum number of supported + * intervals must be at least 10. The default scheduling interval + * for each behavior instance is set to + * <code>numSchedulingIntervals / 2</code>. + * + * @return the number of supported scheduling intervals + * + * @since Java 3D 1.3 + */ + public static int getNumSchedulingIntervals() { + return BehaviorRetained.NUM_SCHEDULING_INTERVALS; + } + + + /** + * Sets the scheduling interval of this Behavior node to the + * specified value. + * + * The scheduling interval defines a partial order of execution + * for behaviors that wake up in response to the same wakeup + * condition (that is, those behaviors that are processed at the + * same "time"). Given a set of behaviors whose wakeup conditions + * are satisfied at the same time, the behavior scheduler will + * execute all behaviors in a lower scheduling interval before + * executing any behavior in a higher scheduling interval. Within + * a scheduling interval, behaviors can be executed in any order, + * or in parallel. Note that this partial ordering is only + * guaranteed for those behaviors that wake up at the same time in + * response to the same wakeup condition, for example, the set of + * behaviors that wake up every frame in response to a + * WakeupOnElapsedFrames(0) wakeup condition. + * + * The default value is <code>numSchedulingIntervals / 2</code>. + * + * @param schedulingInterval the new scheduling interval + * + * @exception IllegalArgumentException if + * <code>schedulingInterval</code> < 0 or + * <code>schedulingInterval</code> >= + * <code>numSchedulingIntervals</code> + * + * @since Java 3D 1.3 + */ + public void setSchedulingInterval(int schedulingInterval) { + if (schedulingInterval < 0 || + schedulingInterval >= getNumSchedulingIntervals()) { + + throw new IllegalStateException(J3dI18N.getString("Behavior1")); + } + + ((BehaviorRetained)this.retained). + setSchedulingInterval(schedulingInterval); + } + + /** + * Retrieves the current scheduling interval of this Behavior + * node. + * + * @return the current scheduling interval + * + * @since Java 3D 1.3 + */ + public int getSchedulingInterval() { + return ((BehaviorRetained)this.retained).getSchedulingInterval(); + } + + /** + * Returns the primary view associated with this behavior. This method + * is useful with certain types of behaviors (e.g., Billboard, LOD) that + * rely on per-View information and with behaviors in general in regards + * to scheduling (the distance from the view platform determines the + * active behaviors). The "primary" view is defined to be the first + * View attached to a live ViewPlatform, if there is more than one active + * View. So, for instance, Billboard behaviors would be oriented toward + * this primary view, in the case of multiple active views into the same + * scene graph. + */ + protected View getView() { + return ((BehaviorRetained)this.retained).getView(); + } + + + /** + * Copies all Behavior information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + BehaviorRetained attr = (BehaviorRetained) originalNode.retained; + BehaviorRetained rt = (BehaviorRetained) retained; + + rt.setEnable(attr.getEnable()); + rt.setSchedulingBounds(attr.getSchedulingBounds()); + rt.setSchedulingInterval(attr.getSchedulingInterval()); + // will set to the correct one in updateNodeReferences + rt.setSchedulingBoundingLeaf(attr.getSchedulingBoundingLeaf()); + + } + + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + super.updateNodeReferences(referenceTable); + + BehaviorRetained rt = (BehaviorRetained) retained; + BoundingLeaf bl= rt.getSchedulingBoundingLeaf(); + + // check for schedulingBoundingLeaf + if (bl != null) { + Object o = referenceTable.getNewObjectReference(bl); + rt.setSchedulingBoundingLeaf((BoundingLeaf) o); + + } + } + +} diff --git a/src/classes/share/javax/media/j3d/BehaviorRetained.java b/src/classes/share/javax/media/j3d/BehaviorRetained.java new file mode 100644 index 0000000..54e0635 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BehaviorRetained.java @@ -0,0 +1,508 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.ArrayList; + +/** + * Behavior is an abstract class that contains the framework for all + * behavioral components in Java 3D. + */ + +class BehaviorRetained extends LeafRetained { + // These bitmasks are used to quickly tell what conditions this behavior + // is waiting for. Currently BehaviorStructure only used 4 of them. + static final int WAKEUP_ACTIVATE_INDEX = 0; + static final int WAKEUP_DEACTIVATE_INDEX = 1; + static final int WAKEUP_VP_ENTRY_INDEX = 2; + static final int WAKEUP_VP_EXIT_INDEX = 3; + static final int WAKEUP_TIME_INDEX = 4; + + static final int NUM_WAKEUPS = 5; + + static final int WAKEUP_ACTIVATE = 0x0001; + static final int WAKEUP_DEACTIVATE = 0x0002; + static final int WAKEUP_VP_ENTRY = 0x0004; + static final int WAKEUP_VP_EXIT = 0x0008; + static final int WAKEUP_TIME = 0x0010; + + /** + * The number of scheduling intervals supported by this + * implementation. This is fixed for a particular implementation + * and must be at least 10. + */ + static final int NUM_SCHEDULING_INTERVALS = 10; + + // different types of IndexedUnorderedSet that use in BehaviorStructure + static final int BEHAIVORS_IN_BS_LIST = 0; + static final int SCHEDULE_IN_BS_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + /** + * The Boundary object defining the behavior's scheduling region. + */ + Bounds schedulingRegion = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * The current wakeup condition. + */ + WakeupCondition wakeupCondition = null; + + /** + * This is the new WakeupCondition to be set in + * initialize wakeupOn() + */ + WakeupCondition newWakeupCondition = null; + + /** + * The current view platform for this behavior; this value is + * false until it comes into range of a view platform. + */ + ViewPlatformRetained vp = null; + + /** + * The current activation status for this behavior; this value + * is false until it comes into range of a view platform. + */ + boolean active = false; + + /** + * Flag indicating whether the behavior is enabled. + */ + boolean enable = true; + + /** + * Current scheduling interval. + */ + int schedulingInterval = NUM_SCHEDULING_INTERVALS / 2; + + /** + * This is a flag that tells the behavior scheduler whether the + * user-programmed process stimulus called wakeupOn, if it did + * not, then the wakeupCondition will be set to null. + */ + boolean conditionSet = false; + + /** + * This is a flag that indicates whether we are in an initialize or + * processStimulus callback. If wakeupOn is called for this behavior + * when this flag is not set, an exception will be thrown. + */ + boolean inCallback = false; + + /** + * This is a flag that indicates whether we are in initialize + * callback. If wakeupOn is called for this behavior when + * this flag is true, then its + * buildTree() will delay until insert nodes message + * is get. This is because some localToVworld[] that wakeup + * depends may not initialize when this behavior setLive(). + */ + boolean inInitCallback = false; + + /** + * The transformed schedulingRegion + */ + Bounds transformedRegion = null; + + // A bitmask that indicates that the scheduling region has changed. + int isDirty = 0xffff; + + /** + * A bitmask that represents all conditions that this behavior is waiting on. + */ + int wakeupMask = 0; + + /** + * An array of ints that count how many of each wakup is present + */ + int[] wakeupArray = new int[NUM_WAKEUPS]; + + // use to post message when bounds change, always point to this + Object targets[] = new Object[1]; + + BehaviorRetained() { + this.nodeType = NodeRetained.BEHAVIOR; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + targets[0] = this; + IndexedUnorderSet.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Get the Behavior's scheduling region. + * @return this Behavior's scheduling region information + */ + Bounds getSchedulingBounds() { + Bounds b = null; + + if (schedulingRegion != null) { + b = (Bounds) schedulingRegion.clone(); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + b.transform(invTransform); + } + } + return b; + } + + /** + * Set the Behavior's scheduling region. + * @param region a region that contains the Behavior's new scheduling + * bounds + */ + synchronized void setSchedulingBounds(Bounds region) { + + if (region != null) { + schedulingRegion = (Bounds) region.clone(); + if (staticTransform != null) { + schedulingRegion.transform(staticTransform.transform); + } + } else { + schedulingRegion = null; + } + + if (source != null && source.isLive()) { + sendMessage(J3dMessage.REGION_BOUND_CHANGED); + } + } + + /** + * Set the Sound's scheduling region to the specified Leaf node. + */ + synchronized void setSchedulingBoundingLeaf(BoundingLeaf region) { + + if (source != null && source.isLive()) { + if (boundingLeaf != null) + boundingLeaf.mirrorBoundingLeaf.removeUser(this); + } + + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + } else { + boundingLeaf = null; + } + + if (source != null && source.isLive()) { + if (boundingLeaf != null) + boundingLeaf.mirrorBoundingLeaf.addUser(this); + sendMessage(J3dMessage.REGION_BOUND_CHANGED); + } + } + + /** + * Enables or disables this Behavior. The default state is enabled. + * @param state true or false to enable or disable this Behavior + */ + void setEnable(boolean state) { + if (enable != state) { + enable = state; + if (source != null && source.isLive()) { + sendMessage(state ? J3dMessage.BEHAVIOR_ENABLE: + J3dMessage.BEHAVIOR_DISABLE); + } + } + } + + + /** + * Retrieves the state of the Behavior enable flag. + * @return the Behavior enable state + */ + boolean getEnable() { + return enable; + } + + + /** + * Sets the scheduling interval of this Behavior node to the + * specified value. + * @param schedulingInterval the new scheduling interval + */ + void setSchedulingInterval(int schedulingInterval) { + + if ((source != null) && source.isLive() + && !inCallback) { + // avoid MT safe problem when user thread setting + // this while behavior scheduling using this. + sendMessage(J3dMessage.SCHEDULING_INTERVAL_CHANGED, + new Integer(schedulingInterval)); + } else { + // garantee this setting reflect in next frame + this.schedulingInterval = schedulingInterval; + } + } + + + /** + * Retrieves the current scheduling interval of this Behavior + * node. + * + * @return the current scheduling interval + */ + int getSchedulingInterval() { + return schedulingInterval; + } + + + /** + * Get the Behavior's scheduling region + */ + BoundingLeaf getSchedulingBoundingLeaf() { + return (boundingLeaf != null ? + (BoundingLeaf)boundingLeaf.source : null); + } + + /** + * This setLive routine first calls the superclass's method, then + * it activates all canvases that are associated with the attached + * view. + */ + synchronized void setLive(SetLiveState s) { + + super.doSetLive(s); + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("BehaviorRetained0")); + } + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("BehaviorRetained1")); + } + + s.nodeList.add(this); + s.behaviorNodes.add(this); + s.notifyThreads |= J3dThread.UPDATE_BEHAVIOR; + // process switch leaf + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.BEH_TARGETS); + } + switchState = (SwitchState)s.switchStates.get(0); + + if (boundingLeaf != null) { + boundingLeaf.mirrorBoundingLeaf.addUser(this); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.BEH_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + super.markAsLive(); + } + + /** + * This clearLive routine first calls the superclass's method, then + * it deactivates all canvases that are associated with the attached + * view. + */ + synchronized void clearLive(SetLiveState s) { + super.clearLive(s); + s.nodeList.add(this); + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.BEH_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + s.notifyThreads |= J3dThread.UPDATE_BEHAVIOR; + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.BEH_TARGETS); + } + if (boundingLeaf != null) { + boundingLeaf.mirrorBoundingLeaf.removeUser(this); + } + // BehaviorStructure removeBehavior() will do the + // wakeupCondition.cleanTree() over there. + } + + /** + * This routine execute the user's initialize method + */ + void executeInitialize() { + + synchronized (this) { + boolean inCallbackSaved = inCallback; + boolean inInitCallbackSaved = inInitCallback; + + inCallback = true; + inInitCallback = true; + try { + ((Behavior)this.source).initialize(); + } + catch (RuntimeException e) { + inCallback = inCallbackSaved; + inInitCallback = inInitCallbackSaved; + System.err.println("Exception occurred during Behavior initialization:"); + e.printStackTrace(); + } + inCallback = inCallbackSaved; + inInitCallback = inInitCallbackSaved; + } + } + + /** + * Defines this behavior's wakeup criteria. + * @param criteria The wakeup criterion for this object + */ + void wakeupOn(WakeupCondition criteria) { + // If not call by initialize(), buildTree will + // delay until insertNodes in BehaviorStructure + // Otherwise BehaviorScheduler will invoke + // handleLastWakeupOn() + if (criteria == null) { + throw new NullPointerException(J3dI18N.getString("BehaviorRetained2")); + } + + if (!inInitCallback) { + conditionSet = true; + wakeupCondition = criteria; + } else { + // delay setting wakeup condition in BehaviorStructure + // activateBehaviors(). This is because there may have + // previously wakeupCondition attach to it and + // scheduling even after clearLive() due to message + // delay processing. It is not MT safe to set it + // in user thread. + newWakeupCondition = criteria; + } + + } + + // The above wakeupOn() just remember the reference + // We only need to handle (and ignore the rest) the + // last wakeupOn() condition set in the behavior. + // This handle the case when multiple wakeupOn() + // are invoked in the same processStimulus() + void handleLastWakeupOn(WakeupCondition prevWakeupCond, + BehaviorStructure bs) { + + if (bs == universe.behaviorStructure) { + if (wakeupCondition == prevWakeupCond) { + // reuse the same wakeupCondition + wakeupCondition.resetTree(); + } else { + if (prevWakeupCond != null) { + prevWakeupCond.cleanTree(bs); + } + wakeupCondition.buildTree(null, 0, this); + } + } else { + // No need to do prevWakeupCond.cleanTree(bs) + // since removeBehavior() will do so + } + } + + + /** + * Returns this behavior's wakeup criteria. + * @return criteria The wakeup criteria of this object + */ + WakeupCondition getWakeupCondition() { + return wakeupCondition; + } + + /** + * Post the specified Id. Behaviors use this method to cause sequential + * scheduling of other behavior object. + * @param postId The Id being posted + */ + + void postId(int postId){ + if (source != null && source.isLive()) { + universe.behaviorStructure.handleBehaviorPost((Behavior) source, postId); + } + } + + protected View getView() { + return (universe != null ? + universe.getCurrentView() : null); + } + + synchronized void updateTransformRegion(Bounds bound) { + if (boundingLeaf == null) { + updateTransformRegion(); + } else { + if (bound == null) { + transformedRegion = null; + } else { + transformedRegion = (Bounds) bound.clone(); + transformedRegion.transform( + boundingLeaf.mirrorBoundingLeaf.getCurrentLocalToVworld()); + } + } + } + + synchronized void updateTransformRegion() { + if (boundingLeaf == null || + !boundingLeaf.mirrorBoundingLeaf.switchState.currentSwitchOn) { + if (schedulingRegion == null) { + transformedRegion = null; + } else { + // use schedulingRegion + if (transformedRegion != null) { + transformedRegion.set(schedulingRegion); + } else { + transformedRegion = (Bounds) schedulingRegion.clone(); + } + transformedRegion.transform(getCurrentLocalToVworld()); + + } + } else { + // use boundingLeaf + transformedRegion = + boundingLeaf.mirrorBoundingLeaf.transformedRegion; + + } + } + + + // Note: This routine will only to update the object's + // transformed region + void updateBoundingLeaf(long refTime) { + transformedRegion = (Bounds)boundingLeaf.mirrorBoundingLeaf.transformedRegion; + } + + + void addWakeupCondition() {} + + final void sendMessage(int mtype, Object arg) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_BEHAVIOR; + createMessage.type = mtype; + createMessage.universe = universe; + createMessage.args[0] = targets; + createMessage.args[1]= this; + createMessage.args[2]= arg; + VirtualUniverse.mc.processMessage(createMessage); + } + + final void sendMessage(int mtype) { + sendMessage(mtype, null); + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + if (schedulingRegion != null) { + schedulingRegion.transform(xform.transform); + } + if (source instanceof DistanceLOD) { + ((DistanceLOD)source).mergeTransform(xform); + } + } +} diff --git a/src/classes/share/javax/media/j3d/BehaviorScheduler.java b/src/classes/share/javax/media/j3d/BehaviorScheduler.java new file mode 100644 index 0000000..a152408 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BehaviorScheduler.java @@ -0,0 +1,218 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +class BehaviorScheduler extends J3dThread { + + /** + * The virtual universe that owns this BehaviorScheduler + */ + VirtualUniverse univ = null; + + // reference to behaviourStructure processList + UnorderList processList[]; + + // reference to scheduleList; + IndexedUnorderSet scheduleList; + + // reference to universe.behaviorStructure + BehaviorStructure behaviorStructure; + + // A count for BehaviorScheduler start/stop + int stopCount = -1; + + /** + * These are used for start/stop BehaviorScheduler + */ + long lastStartTime; + long lastStopTime; + + // lock to ensure consistency of interval values read + Object intervalTimeLock = new Object(); + + /** + * Some variables used to name threads correctly + */ + private static int numInstances = 0; + private int instanceNum = -1; + + private synchronized int newInstanceNum() { + return (++numInstances); + } + + int getInstanceNum() { + if (instanceNum == -1) + instanceNum = newInstanceNum(); + return instanceNum; + } + + + BehaviorScheduler(ThreadGroup t, VirtualUniverse universe) { + super(t); + setName("J3D-BehaviorScheduler-" + getInstanceNum()); + this.univ = universe; + behaviorStructure = universe.behaviorStructure; + scheduleList = behaviorStructure.scheduleList; + processList = behaviorStructure.processList; + type = J3dThread.BEHAVIOR_SCHEDULER; + } + + void stopBehaviorScheduler(long[] intervalTime) { + + stopCount = 2; + VirtualUniverse.mc.sendRunMessage(univ, J3dThread.BEHAVIOR_SCHEDULER); + while (!userStop ) { + MasterControl.threadYield(); + } + synchronized (intervalTimeLock) { + intervalTime[0] = lastStartTime; + intervalTime[1] = lastStopTime; + } + } + + void startBehaviorScheduler() { + // don't allow scheduler start until intervalTime is read + synchronized (intervalTimeLock) { + stopCount = -1; + userStop = false; + VirtualUniverse.mc.setWork(); + } + } + + void deactivate() { + active = false; + if (stopCount >= 0) { + userStop = true; + } + } + + /** + * The main loop for the Behavior Scheduler. + * Main method for firing off vector of satisfied conditions that + * are contained in the condMet vector. Method is synchronized + * because it is modifying the current wakeup vectors in the + * clean (emptying out satisfied conditions) and processStimulus + * (adding conditions again if wakeupOn called) calls. + */ + void doWork(long referenceTime) { + BehaviorRetained arr[]; + UnorderList list; + int i, size, interval; + + lastStartTime = System.currentTimeMillis(); + + if (stopCount >= 0) { + VirtualUniverse.mc.sendRunMessage(univ, J3dThread.BEHAVIOR_SCHEDULER); + if (--stopCount == 0) { + userStop = true; + } + } + + + for (interval = 0; + interval < BehaviorRetained.NUM_SCHEDULING_INTERVALS; + interval++) { + + list = processList[interval]; + + if (list.isEmpty()) { + continue; + } + arr = (BehaviorRetained []) list.toArray(false); + + size = list.arraySize(); + + for (i = 0; i < size ; i++) { + BehaviorRetained behavret = arr[i]; + + + synchronized (behavret) { + Behavior behav = (Behavior) behavret.source; + + if (!behav.isLive() || + !behavret.conditionSet || + (behavret.wakeupCondition == null)) { + continue; + } + + if (behavret.wakeupCondition.trigEnum == null) { + behavret.wakeupCondition.trigEnum = + new WakeupCriteriaEnumerator(behavret.wakeupCondition, + WakeupCondition.TRIGGERED_ELEMENTS); + } else { + behavret.wakeupCondition.trigEnum.reset( + behavret.wakeupCondition, + WakeupCondition.TRIGGERED_ELEMENTS); + } + + // BehaviorRetained now cache the old + // wakeupCondition in order to + // reuse it without the heavyweight cleanTree() + // behavret.wakeupCondition.cleanTree(); + + behavret.conditionSet = false; + WakeupCondition wakeupCond = behavret.wakeupCondition; + + synchronized (behavret) { + behavret.inCallback = true; + univ.inBehavior = true; + try { + behav.processStimulus(wakeupCond.trigEnum); + } + catch (RuntimeException e) { + if (wakeupCond != null) { + wakeupCond.cleanTree(behaviorStructure); + } + System.err.println("Exception occurred during Behavior execution:"); + e.printStackTrace(); + } + univ.inBehavior = false; + behavret.inCallback = false; + } + // note that if the behavior wasn't reset, we need to make the + // wakeupcondition equal to null + if (behavret.conditionSet == false) { + if (wakeupCond != null) { + wakeupCond.cleanTree(behaviorStructure); + } + behavret.wakeupCondition = null; + behavret.active = false; + scheduleList.remove(behavret); + } else { + behavret.handleLastWakeupOn(wakeupCond, + behaviorStructure); + } + } + } + list.clear(); + } + + behaviorStructure.handleAWTEvent(); + behaviorStructure.handleBehaviorPost(); + lastStopTime = System.currentTimeMillis(); + + } + + void free() { + behaviorStructure = null; + getThreadData(null, null).thread = null; + univ = null; + for (int i=BehaviorRetained.NUM_SCHEDULING_INTERVALS-1; + i >= 0; i--) { + processList[i].clear(); + } + scheduleList.clear(); + } +} diff --git a/src/classes/share/javax/media/j3d/BehaviorStructure.java b/src/classes/share/javax/media/j3d/BehaviorStructure.java new file mode 100644 index 0000000..f03d9c9 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BehaviorStructure.java @@ -0,0 +1,1640 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; +import java.awt.*; +import java.awt.event.*; +import java.util.Arrays; + +/** + * A behavior structure is a object that organizes behaviors, + * wakeup conditions, and other behavior scheduler entities. + */ + +class BehaviorStructure extends J3dStructure { + + /** + * The list of behaviors + */ + IndexedUnorderSet behaviors; + + /** + * The list of view platforms + */ + IndexedUnorderSet viewPlatforms; + + /** + * An array of schedulable behaviors, use in + * removeViewPlatform() to go through only active behaviors + */ + IndexedUnorderSet scheduleList; + + /** + * An array of process behaviors + */ + UnorderList processList[] = new UnorderList[BehaviorRetained.NUM_SCHEDULING_INTERVALS]; + + /** + * A bounds used for getting a view platform scheduling BoundingSphere + */ + // BoundingSphere tempSphere = new BoundingSphere(); + // BoundingSphere vpsphere = new BoundingSphere(); + Point3d vpCenter = new Point3d(); + Point3d vpTransCenter = new Point3d(); + + /** + * A list of bounds WakeupOnViewPlatformEntry objects that + * have seen ViewPlatformEntry + */ + WakeupIndexedList boundsEntryList; + + /** + * A list of bounds WakeupOnViewPlatformExit objects that have + * seen ViewPlatformEntry + */ + WakeupIndexedList boundsExitList; + + /** + * A list of WakeupOnSensorEntry objects that have seen a sensor + */ + WakeupIndexedList currentSensorEntryList; + + /** + * A list of WakeupOnSensorExit objects that have seen a sensor + */ + WakeupIndexedList currentSensorExitList; + + /** + * The lists of the WakeupCriterion objects that the + * behavior scheduler keeps. + */ + WakeupIndexedList wakeupOnAWTEvent; + WakeupIndexedList wakeupOnActivation; + WakeupIndexedList wakeupOnDeactivation; + WakeupIndexedList wakeupOnBehaviorPost; + WakeupIndexedList wakeupOnElapsedFrames; + WakeupIndexedList wakeupOnViewPlatformEntry; + WakeupIndexedList wakeupOnViewPlatformExit; + WakeupIndexedList wakeupOnSensorEntry; + WakeupIndexedList wakeupOnSensorExit; + + // Temporary array for processTransformChanged() + UnorderList transformViewPlatformList = new UnorderList(ViewPlatformRetained.class); + + + // The number of active wakeup condition in wakeupOnElapsedFrames + int activeWakeupOnFrameCount = 0; + + // The number of active wakeup condition in wakeupOnSensorEntry/Exit + int activeWakeupOnSensorCount = 0; + + /** + * Buffers to hold events when user thread is in processStimulus() + * while this event is receiving. This avoid any lost of event. + * We did not remove individual element from the following list + * (except clear()) so the order is still preserve. + */ + UnorderList awtEventsBuffer = new UnorderList(AWTEvent.class); + + // Use generic integer array to avoid new Integer() for individual element + int postIDBuffer[] = new int[10]; // size of default UnorderList + int clonePostIDBuffer[] = new int[postIDBuffer.length]; + + UnorderList behaviorPostBuffer = new UnorderList(Behavior.class); + + // temp values for transformed hotspot used in + // wakeupOnSensorEntry/ExitupdateSensorsHotspot + Transform3D sensorTransform = new Transform3D(); + Vector3d sensorLoc = new Vector3d(); + Point3d ptSensorLoc = new Point3d(); + + // list of active physical environments + UnorderList physicalEnvironments = new UnorderList(1, PhysicalEnvironment.class); + + + // list of Behavior waiting to be add to behavior list and buildTree() + UnorderList pendingBehaviors = new UnorderList(BehaviorRetained.class); + + // true if branch detach + boolean branchDetach = false; + + // This is used to notify WakeupOnAWTEvent re-enable Canvas3D events + long awtEventTimestamp = 1; + + // used to process transform messages + boolean transformMsg = false; + UpdateTargets targets = null; + + BehaviorStructure(VirtualUniverse u) { + super(u, J3dThread.UPDATE_BEHAVIOR); + + for (int i=BehaviorRetained.NUM_SCHEDULING_INTERVALS-1; + i >= 0; i--) { + processList[i] = new UnorderList(BehaviorRetained.class); + } + behaviors = new IndexedUnorderSet(BehaviorRetained.class, + BehaviorRetained.BEHAIVORS_IN_BS_LIST, u); + viewPlatforms = new IndexedUnorderSet(ViewPlatformRetained.class, + ViewPlatformRetained.VP_IN_BS_LIST, u); + scheduleList = new IndexedUnorderSet(BehaviorRetained.class, + BehaviorRetained.SCHEDULE_IN_BS_LIST, u); + boundsEntryList = new WakeupIndexedList(WakeupOnViewPlatformEntry.class, + WakeupOnViewPlatformEntry.BOUNDSENTRY_IN_BS_LIST, u); + boundsExitList = new WakeupIndexedList(WakeupOnViewPlatformExit.class, + WakeupOnViewPlatformExit.BOUNDSEXIT_IN_BS_LIST, u); + currentSensorEntryList = new WakeupIndexedList(WakeupOnSensorEntry.class, + WakeupOnSensorEntry.SENSORENTRY_IN_BS_LIST, u); + currentSensorExitList = new WakeupIndexedList(WakeupOnSensorExit.class, + WakeupOnSensorExit.SENSOREXIT_IN_BS_LIST, u); + wakeupOnAWTEvent = new WakeupIndexedList(WakeupOnAWTEvent.class, + WakeupOnAWTEvent.COND_IN_BS_LIST, u); + wakeupOnActivation = new WakeupIndexedList(WakeupOnActivation.class, + WakeupOnActivation.COND_IN_BS_LIST, u); + wakeupOnDeactivation = new WakeupIndexedList(WakeupOnDeactivation.class, + WakeupOnDeactivation.COND_IN_BS_LIST, u); + wakeupOnBehaviorPost = new WakeupIndexedList(WakeupOnBehaviorPost.class, + WakeupOnBehaviorPost.COND_IN_BS_LIST, u); + wakeupOnElapsedFrames = new WakeupIndexedList(WakeupOnElapsedFrames.class, + WakeupOnElapsedFrames.COND_IN_BS_LIST, u); + wakeupOnViewPlatformEntry = new WakeupIndexedList(WakeupOnViewPlatformEntry.class, + WakeupOnViewPlatformEntry.COND_IN_BS_LIST, u); + wakeupOnViewPlatformExit = new WakeupIndexedList(WakeupOnViewPlatformExit.class, + WakeupOnViewPlatformExit.COND_IN_BS_LIST, u); + wakeupOnSensorEntry = new WakeupIndexedList(WakeupOnSensorEntry.class, + WakeupOnSensorEntry.COND_IN_BS_LIST, u); + wakeupOnSensorExit = new WakeupIndexedList(WakeupOnSensorExit.class, + WakeupOnSensorExit.COND_IN_BS_LIST, u); + + } + + void processMessages(long referenceTime) { + + J3dMessage[] messages = getMessages(referenceTime); + int nMsg = getNumMessage(); + J3dMessage m; + + if (nMsg > 0) { + for (int i=0; i<nMsg; i++) { + m = messages[i]; + + switch (m.type) { + case J3dMessage.TRANSFORM_CHANGED: // Compress Message + transformMsg = true; + break; + case J3dMessage.COND_MET: + // No need to compress Message since wakeupCondition + // will make sure that only one message is sent. + processConditionMet((BehaviorRetained) m.args[0], + (Boolean) m.args[1]); + break; + case J3dMessage.INSERT_NODES: + insertNodes((Object[])m.args[0]); + break; + case J3dMessage.REMOVE_NODES: + removeNodes(m); + break; + case J3dMessage.BEHAVIOR_ACTIVATE: + activateBehaviors(); + break; + case J3dMessage.BEHAVIOR_ENABLE: + addToScheduleList((BehaviorRetained) m.args[1]); + reEvaluateWakeupCount(); + break; + case J3dMessage.BEHAVIOR_DISABLE: + removeFromScheduleList((BehaviorRetained) m.args[1]); + reEvaluateWakeupCount(); + break; + case J3dMessage.SCHEDULING_INTERVAL_CHANGED: + ((BehaviorRetained) m.args[1]).schedulingInterval + = ((Integer) m.args[2]).intValue(); + break; + case J3dMessage.SWITCH_CHANGED: + processSwitchChanged(m); + // may need to process dirty switched-on transform + if (universe.transformStructure.getLazyUpdate()) { + transformMsg = true; + } + break; + case J3dMessage.BOUNDINGLEAF_CHANGED: + processBoundingLeafChanged((Object []) m.args[3], + (Bounds) m.args[2]); + break; + case J3dMessage.UPDATE_VIEW: + reEvaluatePhysicalEnvironments(); + ViewPlatform v = ((View) + m.args[0]).getViewPlatform(); + if (v != null) { + // ViewPlatform may set to null when deactivate() + processViewPlatformTransform((ViewPlatformRetained) v.retained); + } + break; + case J3dMessage.UPDATE_VIEWPLATFORM: + ViewPlatformRetained vp = (ViewPlatformRetained) m.args[0]; + // update cached scheduling region first + vp.updateActivationRadius(((Float) m.args[1]).floatValue()); + // then process the VP transform + processViewPlatformTransform(vp); + break; + case J3dMessage.REGION_BOUND_CHANGED: + { + BehaviorRetained behav = (BehaviorRetained) m.args[1]; + behav.updateTransformRegion(); + processBehaviorTransform(behav); + } + break; + case J3dMessage.BEHAVIOR_REEVALUATE: + { + BehaviorRetained behav = (BehaviorRetained) m.args[0]; + behav.active = false; + addToScheduleList(behav); + } + break; + } + m.decRefcount(); + } + + if (transformMsg) { + // get the targets from the transform structure + targets = universe.transformStructure.getTargetList(); + + // process the transform changed for each target + UnorderList arrList; + + arrList = targets.targetList[Targets.BEH_TARGETS]; + if (arrList != null) { + processBehXformChanged(arrList); + } + + arrList = targets.targetList[Targets.VPF_TARGETS]; + if (arrList != null) { + processVpfXformChanged(arrList); + } + + transformMsg = false; + targets = null; + } + Arrays.fill(messages, 0, nMsg, null); + } + + // wakeup even when message is null since wakeupOnElapsedFrame + // will wakeup this + + if (activeWakeupOnSensorCount <= 0) { + if (activeWakeupOnFrameCount > 0) { + // Wakeup render thread when there is pending wakeupOnElapsedFrames + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.BEHAVIOR_SCHEDULER| + J3dThread.RENDER_THREAD); + + } else { + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.BEHAVIOR_SCHEDULER); + } + } else { + checkSensorEntryExit(); + // we have to invoke checkSensorEntryExit() next time + if (activeWakeupOnFrameCount > 0) { + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.UPDATE_BEHAVIOR| + J3dThread.BEHAVIOR_SCHEDULER| + J3dThread.RENDER_THREAD); + + } else { + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.UPDATE_BEHAVIOR| + J3dThread.BEHAVIOR_SCHEDULER); + } + } + } + + void insertNodes(Object[] nodes) { + for (int i=0; i<nodes.length; i++) { + Object node = (Object) nodes[i]; + + if (node instanceof BehaviorRetained) { + pendingBehaviors.add(node); + } + else if (node instanceof ViewPlatformRetained) { + addViewPlatform((ViewPlatformRetained) node); + } + } + } + + void activateBehaviors() { + BehaviorRetained behav; + BehaviorRetained behavArr[] = (BehaviorRetained []) + pendingBehaviors.toArray(false); + + for (int i=pendingBehaviors.arraySize()-1; i>=0; i--) { + behav = behavArr[i]; + behav.wakeupCondition = behav.newWakeupCondition; + if (behav.wakeupCondition != null) { + behav.wakeupCondition.buildTree(null, 0, behav); + behav.conditionSet = true; + behaviors.add(behav); + behav.updateTransformRegion(); + addToScheduleList(behav); + } + } + pendingBehaviors.clear(); + } + + void addViewPlatform(ViewPlatformRetained vp) { + int i; + BehaviorRetained behav; + BehaviorRetained behavArr[] = (BehaviorRetained []) behaviors.toArray(false); + + viewPlatforms.add(vp); + vp.updateTransformRegion(); + + if (!vp.isActiveViewPlatform()) { + return; + } + + // re-evaulate all behaviors to see if we need to put + // more behaviors in scheduleList + + for (i=behaviors.arraySize()-1; i>=0; i--) { + addToScheduleList(behavArr[i]); + } + + // handle ViewPlatform Entry + WakeupOnViewPlatformEntry wakeupOnViewPlatformEntryArr[] = + (WakeupOnViewPlatformEntry []) wakeupOnViewPlatformEntry.toArray(false); + WakeupOnViewPlatformEntry wentry; + + for (i=wakeupOnViewPlatformEntry.arraySize()-1; i >=0; i--) { + wentry = wakeupOnViewPlatformEntryArr[i]; + if (!boundsEntryList.contains(wentry) && + wentry.transformedRegion.intersect(vp.center)) { + boundsEntryList.add(wentry); + wentry.triggeredVP = vp; + wentry.setTriggered(); + } + } + + // handle ViewPlatform Exit + WakeupOnViewPlatformExit wakeupOnViewPlatformExitArr[] = + (WakeupOnViewPlatformExit []) wakeupOnViewPlatformExit.toArray(false); + WakeupOnViewPlatformExit wexit; + + for (i=wakeupOnViewPlatformExit.arraySize()-1; i >=0; i--) { + wexit = wakeupOnViewPlatformExitArr[i]; + if (!boundsExitList.contains(wexit) && + wexit.transformedRegion.intersect(vp.center)) { + wexit.triggeredVP = vp; + boundsExitList.add(wexit); + } + } + + } + + void removeNodes(J3dMessage m) { + Object[] nodes = (Object[]) m.args[0]; + boolean behavRemove = false; + + for (int i=0; i<nodes.length; i++) { + Object node = nodes[i]; + if (node instanceof BehaviorRetained) { + behavRemove = true; + removeBehavior((BehaviorRetained) node); + } + else if (node instanceof ViewPlatformRetained) { + removeViewPlatform((ViewPlatformRetained) node); + } + } + + // Since BehaviorScheduler will run after BehaviorStructure + // (not in parallel). It is safe to do cleanup here. + wakeupOnAWTEvent.clearMirror(); + awtEventsBuffer.clearMirror(); + wakeupOnBehaviorPost.clearMirror(); + behaviorPostBuffer.clearMirror(); + wakeupOnSensorEntry.clearMirror(); + wakeupOnSensorExit.clearMirror(); + branchDetach = true; + + if (behavRemove) { + // disable AWT Event from Canvas3D + WakeupOnAWTEvent awtConds[] = (WakeupOnAWTEvent []) + wakeupOnAWTEvent.toArray(); + int eventSize = wakeupOnAWTEvent.arraySize(); + + // Component Event always Enable + boolean focusEnable = false; + boolean keyEnable = false; + boolean mouseMotionEnable = false; + boolean mouseEnable = false; + WakeupOnAWTEvent awtCond; + int awtId; + long eventMask; + boolean incTimestamp = false; + + for (int i=0; i < eventSize; i++) { + awtCond = awtConds[i]; + awtId = awtCond.AwtId; + eventMask = awtCond.EventMask; + + if ((awtId >= FocusEvent.FOCUS_FIRST && awtId <= FocusEvent.FOCUS_LAST) || + (eventMask & AWTEvent.FOCUS_EVENT_MASK) != 0) { + focusEnable = true; + } + if ((awtId >= KeyEvent.KEY_FIRST && awtId <= KeyEvent.KEY_LAST) || + (eventMask & AWTEvent.KEY_EVENT_MASK) != 0) { + keyEnable = true; + } + if ((awtId >= MouseEvent.MOUSE_FIRST) && + (awtId <= MouseEvent.MOUSE_LAST)) { + if ((awtId == MouseEvent.MOUSE_DRAGGED) || + (awtId == MouseEvent.MOUSE_MOVED)) { + mouseMotionEnable = true; + } else { + mouseEnable = true; + } + } else { + if ((eventMask & AWTEvent.MOUSE_EVENT_MASK) != 0) { + mouseEnable = true; + } + if ((eventMask & AWTEvent.MOUSE_MOTION_EVENT_MASK) != 0) { + mouseMotionEnable = true; + } + } + } + + if (!focusEnable && universe.enableFocus) { + incTimestamp = true; + universe.disableFocusEvents(); + } + if (!VirtualUniverse.mc.isD3D() && !keyEnable && universe.enableKey) { + // key event use for toggle to fullscreen/window mode + incTimestamp = true; + universe.disableKeyEvents(); + } + if (!mouseMotionEnable && universe.enableMouseMotion) { + incTimestamp = true; + universe.disableMouseMotionEvents(); + } + if (!mouseEnable && universe.enableMouse) { + incTimestamp = true; + universe.disableMouseEvents(); + } + if (incTimestamp) { + awtEventTimestamp++; + } + } + } + + void removeViewPlatform(ViewPlatformRetained vp) { + BehaviorRetained behav; + int i; + + viewPlatforms.remove(vp); + + BehaviorRetained scheduleArr[] = (BehaviorRetained []) + scheduleList.toArray(false); + + // handle Deactive + for (i=scheduleList.arraySize()-1; i >=0 ; i--) { + behav = scheduleArr[i]; + // This vp may contribute to the reason that + // behavior is in schedule list + if (!intersectVPRegion(behav.transformedRegion)) { + removeFromScheduleList(behav); + } + } + + // handle ViewPlatform Entry + WakeupOnViewPlatformEntry boundsEntryArr[] = + (WakeupOnViewPlatformEntry []) boundsEntryList.toArray(false); + WakeupOnViewPlatformEntry wentry; + ViewPlatformRetained triggeredVP; + + for (i=boundsEntryList.arraySize()-1; i >=0; i--) { + wentry = boundsEntryArr[i]; + // only this thread can modify wentry.transformedRegion, so + // no need to getWithLock() + triggeredVP = intersectVPCenter(wentry.transformedRegion); + if (triggeredVP == null) { + boundsEntryList.remove(wentry); + } + } + + // handle ViewPlatform Exit + WakeupOnViewPlatformExit boundsExitArr[] = + (WakeupOnViewPlatformExit []) boundsExitList.toArray(false); + WakeupOnViewPlatformExit wexit; + + for (i=boundsExitList.arraySize()-1; i >=0; i--) { + wexit = boundsExitArr[i]; + // only this thread can modify wentry.transformedRegion, so + // no need to getWithLock() + triggeredVP = intersectVPCenter(wexit.transformedRegion); + if (triggeredVP == null) { + boundsExitList.remove(wexit); + wexit.setTriggered(); + } + } + } + + void removeBehavior(BehaviorRetained behav) { + behaviors.remove(behav); + + if ((behav.wakeupCondition != null) && + (behav.wakeupCondition.behav != null)) { + behav.wakeupCondition.cleanTree(this); + if (behav.universe == universe) { + behav.conditionSet = false; + } + } + + // cleanup boundsEntryList + // since we didn't remove it on removeVPEntryCondition + WakeupOnViewPlatformEntry boundsEntryArr[] = + (WakeupOnViewPlatformEntry []) boundsEntryList.toArray(false); + WakeupOnViewPlatformEntry wentry; + + for (int i=boundsEntryList.arraySize()-1; i>=0; i--) { + wentry = boundsEntryArr[i]; + if (wentry.behav == behav) { + boundsEntryList.remove(wentry); + } + } + + // cleanup boundsExitList + // since we didn't remove it on removeVPExitCondition + WakeupOnViewPlatformExit boundsExitArr[] = + (WakeupOnViewPlatformExit []) boundsExitList.toArray(false); + WakeupOnViewPlatformExit wexit; + + for (int i=boundsExitList.arraySize()-1; i>=0; i--) { + wexit = boundsExitArr[i]; + if (wexit.behav == behav) { + boundsExitList.remove(wexit); + } + } + + + // cleanup currentSensorEntryList + // since we didn't remove it on removeSensorEntryCondition + WakeupOnSensorEntry currentSensorEntryArr[] = + (WakeupOnSensorEntry []) currentSensorEntryList.toArray(false); + WakeupOnSensorEntry sentry; + + for (int i=currentSensorEntryList.arraySize()-1; i>=0; i--) { + sentry = currentSensorEntryArr[i]; + if (sentry.behav == behav) { + currentSensorEntryList.remove(sentry); + } + } + + + // cleanup currentSensorExitList + // since we didn't remove it on removeSensorExitCondition + WakeupOnSensorExit currentSensorExitArr[] = + (WakeupOnSensorExit []) currentSensorExitList.toArray(false); + WakeupOnSensorExit sexit; + + for (int i=currentSensorExitList.arraySize()-1; i>=0; i--) { + sexit = currentSensorExitArr[i]; + if (sexit.behav == behav) { + currentSensorExitList.remove(sexit); + } + } + removeFromScheduleList(behav); + + } + + + void handleAWTEvent(AWTEvent evt) { + awtEventsBuffer.add(evt); + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.BEHAVIOR_SCHEDULER); + } + + /** + * This routine takes the awt event list and gives then to the awt event + * conditions + */ + void handleAWTEvent() { + WakeupOnAWTEvent awtConds[] = (WakeupOnAWTEvent []) + wakeupOnAWTEvent.toArray(); + AWTEvent events[]; + int eventSize = wakeupOnAWTEvent.arraySize(); + int awtBufferSize; + + synchronized (awtEventsBuffer) { + events = (AWTEvent []) awtEventsBuffer.toArray(); + awtBufferSize = awtEventsBuffer.size(); + awtEventsBuffer.clear(); + } + WakeupOnAWTEvent awtCond; + AWTEvent evt; + int id; + + for (int i=0; i < eventSize; i++) { + awtCond = awtConds[i]; + for (int j=0; j < awtBufferSize; j++) { + evt = events[j]; + id = evt.getID(); + + if (awtCond.AwtId != 0) { + if (awtCond.AwtId == id) { + // TODO: how do we clone this event (do we need to?) + // Bug: 4181321 + awtCond.addAWTEvent(evt); + } + } else { + if (id >= ComponentEvent.COMPONENT_FIRST && + id <= ComponentEvent.COMPONENT_LAST && + (awtCond.EventMask & AWTEvent.COMPONENT_EVENT_MASK) != 0) { + awtCond.addAWTEvent(evt); + } + else if (id >= FocusEvent.FOCUS_FIRST && + id <= FocusEvent.FOCUS_LAST && + (awtCond.EventMask & AWTEvent.FOCUS_EVENT_MASK) != 0) { + awtCond.addAWTEvent(evt); + } + else if (id >= KeyEvent.KEY_FIRST && + id <= KeyEvent.KEY_LAST && + (awtCond.EventMask & AWTEvent.KEY_EVENT_MASK) != 0) { + awtCond.addAWTEvent(evt); + } + else if ((id == MouseEvent.MOUSE_CLICKED || + id == MouseEvent.MOUSE_ENTERED || + id == MouseEvent.MOUSE_EXITED || + id == MouseEvent.MOUSE_PRESSED || + id == MouseEvent.MOUSE_RELEASED) && + (awtCond.EventMask & AWTEvent.MOUSE_EVENT_MASK) != 0) { + awtCond.addAWTEvent(evt); + } + else if ((id == MouseEvent.MOUSE_DRAGGED || + id == MouseEvent.MOUSE_MOVED) && + (awtCond.EventMask & AWTEvent.MOUSE_MOTION_EVENT_MASK) != 0) { + awtCond.addAWTEvent(evt); + } + } + } + } + + + + } + + + void handleBehaviorPost(Behavior behav, int postid) { + + synchronized (behaviorPostBuffer) { + int size = behaviorPostBuffer.size(); + if (postIDBuffer.length == size) { + int oldbuffer[] = postIDBuffer; + postIDBuffer = new int[size << 1]; + System.arraycopy(oldbuffer, 0, postIDBuffer, 0, size); + } + postIDBuffer[size] = postid; + behaviorPostBuffer.add(behav); + } + VirtualUniverse.mc.sendRunMessage(universe, J3dThread.BEHAVIOR_SCHEDULER); + } + + /** + * This goes through all of the criteria waiting for Behavior Posts + * and notifys them. + */ + void handleBehaviorPost() { + Behavior behav; + int postid; + WakeupOnBehaviorPost wakeup; + WakeupOnBehaviorPost wakeupConds[] = (WakeupOnBehaviorPost []) + wakeupOnBehaviorPost.toArray(); + Behavior behavArr[]; + int behavBufferSize; + + synchronized (behaviorPostBuffer) { + behavArr = (Behavior []) behaviorPostBuffer.toArray(); + behavBufferSize = behaviorPostBuffer.size(); + if (clonePostIDBuffer.length < behavBufferSize) { + clonePostIDBuffer = new int[behavBufferSize]; + } + System.arraycopy(postIDBuffer, 0, clonePostIDBuffer, 0, + behavBufferSize); + behaviorPostBuffer.clear(); + } + + int size = wakeupOnBehaviorPost.arraySize(); + for (int i=0; i < size; i++) { + wakeup = wakeupConds[i]; + for (int j=0; j < behavBufferSize; j++) { + behav = behavArr[j]; + postid = clonePostIDBuffer[j]; + if ((wakeup.post == postid || wakeup.post == 0) && + (behav == wakeup.armingBehavior || wakeup.armingBehavior == null)) { + wakeup.triggeringBehavior = behav; + wakeup.triggeringPost = postid; + wakeup.setTriggered(); + } + } + } + + } + + /** + * This goes through all of the criteria waiting for Elapsed Frames + * and notified them. + */ + void incElapsedFrames() { + + WakeupOnElapsedFrames wakeupConds[] = (WakeupOnElapsedFrames []) + wakeupOnElapsedFrames.toArray(true); + int size = wakeupOnElapsedFrames.arraySize(); + int i = 0; + + while (i < size) { + wakeupConds[i++].newFrame(); + } + + if ( size > 0) { + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.BEHAVIOR_SCHEDULER|J3dThread.UPDATE_BEHAVIOR); + } + + if (branchDetach) { + // Since this procedure may call by immediate mode user + // thread, we can't just clear it in removeNodes() + wakeupOnElapsedFrames.clearMirror(); + branchDetach = false; + } + + } + + void removeVPEntryCondition(WakeupCondition w) { + wakeupOnViewPlatformEntry.remove(w); + // don't remove boundsEntryList, it is use next time + // when addVPExitCondition invoke to determine whether to + // trigger an event or not. + + } + + void addVPEntryCondition(WakeupOnViewPlatformEntry w) { + boolean needTrigger = true; + + // see if the matching wakeupOnViewPlatformEntry + // condition exists & do cleanup + WakeupOnViewPlatformEntry boundsEntryArr[] = + (WakeupOnViewPlatformEntry []) boundsEntryList.toArray(false); + WakeupOnViewPlatformEntry wentry; + + for (int i=boundsEntryList.arraySize()-1; i>=0; i--) { + wentry = boundsEntryArr[i]; + if ((wentry.behav == w.behav) && + (wentry.region.equals(w.region))) { + boundsEntryList.remove(i); + // Case where we wakeOr() both condition together. + // we should avoid calling setTrigger() every time. + needTrigger = false; + break; + } + } + + wakeupOnViewPlatformEntry.add(w); + + ViewPlatformRetained triggeredVP = intersectVPCenter(w.transformedRegion); + if (triggeredVP != null) { + boundsEntryList.add(w); + } + + // we always trigger bound is inside during initialize + if (needTrigger && (triggeredVP != null)) { + w.triggeredVP = triggeredVP; + w.setTriggered(); + } + } + + void removeVPExitCondition(WakeupOnViewPlatformExit w) { + wakeupOnViewPlatformExit.remove(w); + // don't remove boundsExitList, it is use next time + // when addVPEntryCondition invoke to determine whether to + // trigger an event or not. + } + + void addVPExitCondition(WakeupOnViewPlatformExit w) { + // Cleanup, since collideEntryList did not remove + // its condition in removeVPEntryCondition + boolean needTrigger = true; + WakeupOnViewPlatformExit boundsExitArr[] = + (WakeupOnViewPlatformExit []) boundsExitList.toArray(false); + WakeupOnViewPlatformExit wexit; + for (int i=boundsExitList.arraySize()-1; i>=0; i--) { + wexit = boundsExitArr[i]; + if ((wexit.behav == w.behav) && + (wexit.region.equals(w.region))) { + boundsExitList.remove(i); + needTrigger = false; + break; + } + } + + ViewPlatformRetained triggeredVP = intersectVPCenter(w.transformedRegion); + wakeupOnViewPlatformExit.add(w); + + if (triggeredVP != null) { + w.triggeredVP = triggeredVP; + boundsExitList.add(w); + } + + if (!needTrigger) { + return; + } + + // see if the matching wakeupOnViewPlatformEntry + // condition exists + + WakeupOnViewPlatformEntry boundsEntryArr[] = + (WakeupOnViewPlatformEntry []) boundsEntryList.toArray(false); + WakeupOnViewPlatformEntry wentry; + + for (int i=boundsEntryList.arraySize()-1; i>=0; i--) { + wentry = boundsEntryArr[i]; + if ((wentry.behav == w.behav) && + (wentry.region.equals(w.region))) { + // Don't remove this since if user wakeupOr() + // Entry and Exit condition together we may have trouble + // boundsEntryList.remove(i); + if (triggeredVP == null) { + w.setTriggered(); + } + break; + } + } + + } + + + void removeSensorEntryCondition(WakeupOnSensorEntry w) { + wakeupOnSensorEntry.remove(w); + // don't remove currentSensorEntryList, it is use next time + // when addSensorExitCondition invoke to determine whether to + // trigger an event or not. + } + + void addSensorEntryCondition(WakeupOnSensorEntry w) { + boolean needTrigger = true; + + // see if the matching wakeupOnSensorEntry + // condition exists + WakeupOnSensorEntry sensorEntryArr[] = + (WakeupOnSensorEntry []) currentSensorEntryList.toArray(false); + WakeupOnSensorEntry wentry; + + for (int i=currentSensorEntryList.arraySize()-1; i>=0; i--) { + wentry = sensorEntryArr[i]; + if ((wentry.behav == w.behav) && + (wentry.region.equals(w.region))) { + currentSensorEntryList.remove(i); + needTrigger = false; + break; + } + } + + wakeupOnSensorEntry.add(w); + + w.updateTransformRegion(); + Sensor target = sensorIntersect(w.transformedRegion); + if (target != null) { + w.setTarget(target); + currentSensorEntryList.add(w); + } + + if (needTrigger && (target != null)) { + w.setTriggered(); + + } + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.UPDATE_BEHAVIOR); + } + + void removeSensorExitCondition(WakeupOnSensorExit w) { + wakeupOnSensorExit.remove(w); + // don't remove currentSensorExitList, it is use next time + // when addSensorEntryCondition invoke to determine whether to + // trigger an event or not + } + + void addSensorExitCondition(WakeupOnSensorExit w) { + // Cleanup + boolean needTrigger = true; + + WakeupOnSensorExit currentSensorExitArr[] = + (WakeupOnSensorExit []) currentSensorExitList.toArray(false); + WakeupOnSensorExit wexit; + for (int i=currentSensorExitList.arraySize()-1; i>=0; i--) { + wexit = currentSensorExitArr[i]; + if ((wexit.behav == w.behav) && + (wexit.region.equals(w.region))) { + currentSensorExitList.remove(i); + needTrigger = false; + break; + } + } + + w.updateTransformRegion(); + Sensor target = sensorIntersect(w.transformedRegion); + wakeupOnSensorExit.add(w); + + if (target != null) { + w.setTarget(target); + currentSensorExitList.add(w); + } + + if (!needTrigger) { + return; + } + // see if the matching wakeupOnSensorEntry + // condition exists + WakeupOnSensorEntry sensorEntryArr[] = + (WakeupOnSensorEntry []) currentSensorEntryList.toArray(false); + WakeupOnSensorEntry wentry; + + for (int i=currentSensorEntryList.arraySize()-1; i>=0; i--) { + wentry = sensorEntryArr[i]; + if ((wentry.behav == w.behav) && + (wentry.region.equals(w.region))) { + // No need to invoke currentSensorEntryList.remove(i); + if (target == null) { + w.setTriggered(); + } + break; + } + } + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.UPDATE_BEHAVIOR); + } + + void processConditionMet(BehaviorRetained behav, + Boolean checkSchedulingRegion) { + + // Since we reuse wakeup condition, the old wakeupCondition + // will not reactivate again while processStimulus is running + // which may set another wakeupCondition. + // Previously we don't reuse wakeupCondition and cleanTree() + // everytime before calling processStimulus() so the flag + // inCallback is not necessary to check. + if (!behav.inCallback && + ((checkSchedulingRegion == Boolean.FALSE) || + behav.active)) { + processList[behav.schedulingInterval].add(behav); + } else { + if (((behav.wakeupMask & + BehaviorRetained.WAKEUP_TIME) != 0) && + (behav.source != null) && + (behav.source.isLive()) && + (behav.wakeupCondition != null)) { + // need to add back wakeupOnElapsedTime condition + // to TimerThread + behav.wakeupCondition.reInsertElapseTimeCond(); + } + } + } + + final void processBehXformChanged(UnorderList arrList) { + BehaviorRetained beh; + Object[] nodes, nodesArr; + + int size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (int i = 0; i < size; i++) { + nodes = (Object[])nodesArr[i]; + for (int j=0; j<nodes.length; j++) { + beh = (BehaviorRetained)nodes[j]; + beh.updateTransformRegion(); + processBehaviorTransform(beh); + } + } + } + + final void processVpfXformChanged(UnorderList arrList) { + ViewPlatformRetained vpf; + Object[] nodes, nodesArr; + + int size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (int i = 0; i < size; i++) { + nodes = (Object[])nodesArr[i]; + for (int j=0; j<nodes.length; j++) { + processViewPlatformTransform((ViewPlatformRetained)nodes[j]); + } + } + } + + final void processTransformChanged(Object leaf[]) { + Object node; + int i; + + // We have to process them in group rather then one by one, + // otherwise we may have both activation/deactivation + // conditions wakeup at the same time when both ViewPlatform + // and Behavior transform under a branch. + + // Update transformRegion first + for (i=0; i < leaf.length; i++) { + node = leaf[i]; + if (node instanceof BehaviorRetained) { + ((BehaviorRetained) node).updateTransformRegion(); + processBehaviorTransform((BehaviorRetained) node); + + } else if (node instanceof ViewPlatformRetained) { + ((ViewPlatformRetained) node).updateTransformRegion(); + transformViewPlatformList.add(node); + } + } + + // finally handle ViewPlatformRetained Transform change + if (transformViewPlatformList.size() > 0) { + ViewPlatformRetained vpArr[] = (ViewPlatformRetained []) + transformViewPlatformList.toArray(false); + + int size = transformViewPlatformList.arraySize(); + for (i=0; i < size; i++) { + processViewPlatformTransform((ViewPlatformRetained) + vpArr[i]); + } + transformViewPlatformList.clear(); + } + } + + + // assume behav.updateTransformRegion() invoke before + final void processBehaviorTransform(BehaviorRetained behav) { + if ((behav.wakeupMask & BehaviorRetained.WAKEUP_VP_ENTRY) != 0) { + updateVPEntryTransformRegion(behav); + } + + if ((behav.wakeupMask & BehaviorRetained.WAKEUP_VP_EXIT) != 0) { + updateVPExitTransformRegion(behav); + } + + if (behav.active) { + if (!intersectVPRegion(behav.transformedRegion)) { + removeFromScheduleList(behav); + } + } else { + addToScheduleList(behav); + } + } + + + void processViewPlatformTransform(ViewPlatformRetained vp) { + int i; + BehaviorRetained behav; + + vp.updateTransformRegion(); + + if (!vp.isActiveViewPlatform()) { + return; + } + + BehaviorRetained behavArr[] = (BehaviorRetained []) behaviors.toArray(false); + + // re-evaulate all behaviors affected by this vp + for (i=behaviors.arraySize()-1; i>=0; i--) { + behav = behavArr[i]; + if (behav.active) { + if (!intersectVPRegion(behav.transformedRegion)) { + removeFromScheduleList(behav); + } + } else { + addToScheduleList(behav); + } + } + + // handle wakeupOnViewPlatformEntry + WakeupOnViewPlatformEntry wakeupOnViewPlatformEntryArr[] = + (WakeupOnViewPlatformEntry []) wakeupOnViewPlatformEntry.toArray(false); + WakeupOnViewPlatformEntry wentry; + int idx; + ViewPlatformRetained triggeredVP; + + for (i=wakeupOnViewPlatformEntry.arraySize()-1; i >=0; i--) { + wentry = wakeupOnViewPlatformEntryArr[i]; + idx = boundsEntryList.indexOf(wentry); + if (idx < 0) { + if (wentry.transformedRegion.intersect(vp.center)) { + boundsEntryList.add(wentry); + wentry.triggeredVP = vp; + wentry.setTriggered(); + } + } else { + triggeredVP = intersectVPCenter(wentry.transformedRegion); + if (triggeredVP == null) { + boundsEntryList.remove(idx); + } + } + } + + // handle wakeupOnViewPlatformExit; + WakeupOnViewPlatformExit wakeupOnViewPlatformExitArr[] = + (WakeupOnViewPlatformExit []) wakeupOnViewPlatformExit.toArray(false); + WakeupOnViewPlatformExit wexit; + + for (i=wakeupOnViewPlatformExit.arraySize()-1; i >=0; i--) { + wexit = wakeupOnViewPlatformExitArr[i]; + idx = boundsExitList.indexOf(wexit); + if (idx < 0) { + if (wexit.transformedRegion.intersect(vp.center)) { + wexit.triggeredVP = vp; + boundsExitList.add(wexit); + + } + } else { + triggeredVP = intersectVPCenter(wexit.transformedRegion); + if (triggeredVP == null) { + boundsExitList.remove(idx); + wexit.setTriggered(); + } + } + } + } + + void updateVPEntryTransformRegion(BehaviorRetained behav) { + WakeupOnViewPlatformEntry wakeupOnViewPlatformEntryArr[] = + (WakeupOnViewPlatformEntry []) wakeupOnViewPlatformEntry.toArray(false); + WakeupOnViewPlatformEntry wentry; + ViewPlatformRetained triggeredVP; + + for (int i=wakeupOnViewPlatformEntry.arraySize()-1; i >=0; i--) { + wentry = wakeupOnViewPlatformEntryArr[i]; + if (wentry.behav == behav) { + wentry.updateTransformRegion(behav); + int idx = boundsEntryList.indexOf(wentry); + + triggeredVP = intersectVPCenter(wentry.transformedRegion); + if (triggeredVP != null) { + if (idx < 0) { + boundsEntryList.add(wentry); + wentry.triggeredVP = triggeredVP; + wentry.setTriggered(); + } + } else { + if (idx >=0) { + boundsEntryList.remove(idx); + } + } + } + + } + } + + + + void updateVPExitTransformRegion(BehaviorRetained behav) { + WakeupOnViewPlatformExit wakeupOnViewPlatformExitArr[] = + (WakeupOnViewPlatformExit []) wakeupOnViewPlatformExit.toArray(false); + WakeupOnViewPlatformExit wexit; + ViewPlatformRetained triggeredVP; + + for (int i=wakeupOnViewPlatformExit.arraySize()-1; i >=0; i--) { + wexit = wakeupOnViewPlatformExitArr[i]; + if (wexit.behav == behav) { + wexit.updateTransformRegion(behav); + wexit = wakeupOnViewPlatformExitArr[i]; + int idx = boundsExitList.indexOf(wexit); + triggeredVP = intersectVPCenter(wexit.transformedRegion); + if (triggeredVP != null) { + if (idx < 0) { + wexit.triggeredVP = triggeredVP; + boundsExitList.add(wexit); + } + } else { + if (idx >= 0) { + boundsExitList.remove(idx); + wexit.setTriggered(); + } + } + } + } + } + + + void reEvaluatePhysicalEnvironments() { + // we can't just add or remove from the list since + // physicalEnvironment may be share by multiple view + View v; + View views[]; + ViewPlatform vp; + ArrayList vpList = universe.viewPlatforms; + + physicalEnvironments.clear(); + + for (int i=vpList.size()-1; i>=0; i--) { + views = ((ViewPlatformRetained) vpList.get(i)).getViewList(); + for (int j=views.length-1; j>=0; j--) { + v = views[j]; + if (v.active && + !physicalEnvironments.contains(v.physicalEnvironment)) { + physicalEnvironments.add(v.physicalEnvironment); + } + } + } + } + + void checkSensorEntryExit() { + int i, idx; + Sensor target; + + // handle WakeupOnSensorEntry + WakeupOnSensorEntry wentry; + WakeupOnSensorEntry wentryArr[] = (WakeupOnSensorEntry []) + wakeupOnSensorEntry.toArray(); + + for (i=wakeupOnSensorEntry.arraySize()-1; i>=0; i--) { + wentry = wentryArr[i]; + idx = currentSensorEntryList.indexOf(wentry); + wentry.updateTransformRegion(); + target = sensorIntersect(wentry.transformedRegion); + if (target != null) { + if (idx < 0) { + currentSensorEntryList.add(wentry); + wentry.setTarget(target); + wentry.setTriggered(); + } + } else { + if (idx >= 0) { + currentSensorEntryList.remove(idx); + } + } + } + + // handle WakeupOnSensorExit + WakeupOnSensorExit wexit; + WakeupOnSensorExit wexitArr[] = (WakeupOnSensorExit []) + wakeupOnSensorExit.toArray(); + + for (i=wakeupOnSensorExit.arraySize()-1; i>=0; i--) { + wexit = wexitArr[i]; + idx = currentSensorExitList.indexOf(wexit); + wexit.updateTransformRegion(); + target = sensorIntersect(wexit.transformedRegion); + if (target != null) { + if (idx < 0) { + currentSensorExitList.add(wexit); + wexit.setTarget(target); + } + } else { + if (idx >= 0) { + currentSensorExitList.remove(idx); + wexit.setTriggered(); + } + } + } + + } + + + /** + * return the Senor that intersect with behregion or null + */ + Sensor sensorIntersect(Bounds behregion) { + + if (behregion == null) + return null; + + PhysicalEnvironment env[] = (PhysicalEnvironment []) + physicalEnvironments.toArray(false); + Sensor sensors[]; + Sensor s; + View v; + for (int i=physicalEnvironments.arraySize()-1; i>=0; i--) { + if (env[i].activeViewRef > 0) { + sensors = env[i].getSensorList(); + if (sensors != null) { + for (int j= env[i].users.size()-1; j>=0; j--) { + v = (View) env[i].users.get(j); + synchronized (sensors) { + for (int k=sensors.length-1; k >=0; k--) { + s = sensors[k]; + if (s != null) { + v.getSensorToVworld(s, sensorTransform); + sensorTransform.get(sensorLoc); + ptSensorLoc.set(sensorLoc); + if (behregion.intersect(ptSensorLoc)) { + return s; + } + } + } + } + } + } + } + } + return null; + } + + + /** + * return true if one of ViewPlatforms intersect behregion + */ + final boolean intersectVPRegion(Bounds behregion) { + if (behregion == null) { + return false; + } + + ViewPlatformRetained vp; + ViewPlatformRetained vpLists[] = (ViewPlatformRetained []) + viewPlatforms.toArray(false); + + for (int i=viewPlatforms.arraySize()- 1; i>=0; i--) { + vp = vpLists[i]; + if (vp.isActiveViewPlatform() && + vp.schedSphere.intersect(behregion)) { + return true; + } + } + return false; + } + + /** + * return true if one of ViewPlatforms center intersect behregion + */ + final ViewPlatformRetained intersectVPCenter(Bounds behregion) { + if (behregion == null) { + return null; + } + + ViewPlatformRetained vp; + ViewPlatformRetained vpLists[] = (ViewPlatformRetained []) + viewPlatforms.toArray(false); + + + for (int i=viewPlatforms.arraySize()- 1; i>=0; i--) { + vp = vpLists[i]; + if (vp.isActiveViewPlatform() && + behregion.intersect(vp.center)) { + return vp; + } + } + return null; + } + + void notifyDeactivationCondition(BehaviorRetained behav) { + WakeupOnDeactivation wakeup; + WakeupOnDeactivation wakeupConds[] = (WakeupOnDeactivation []) + wakeupOnDeactivation.toArray(false); + + for (int i=wakeupOnDeactivation.arraySize()-1; i>=0; i--) { + wakeup = wakeupConds[i]; + if (wakeup.behav == behav) { + wakeup.setTriggered(); + } + } + } + + void notifyActivationCondition(BehaviorRetained behav) { + WakeupOnActivation wakeup; + WakeupOnActivation wakeupConds[] = (WakeupOnActivation []) + wakeupOnActivation.toArray(false); + + for (int i=wakeupOnActivation.arraySize()-1; i>=0; i--) { + wakeup = wakeupConds[i]; + if (wakeup.behav == behav) { + wakeup.setTriggered(); + } + } + } + + + void processSwitchChanged(J3dMessage m) { + + int i,j; + UnorderList arrList; + int size; + Object[] nodes, nodesArr; + + UpdateTargets targets = (UpdateTargets)m.args[0]; + arrList = targets.targetList[Targets.VPF_TARGETS]; + + if (arrList != null) { + ViewPlatformRetained vp; + size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (j=0; j<size; j++) { + nodes = (Object[])nodesArr[j]; + for (i=nodes.length-1; i>=0; i--) { + vp = (ViewPlatformRetained) nodes[i]; + vp.processSwitchChanged(); + } + } + } + + arrList = targets.targetList[Targets.BEH_TARGETS]; + + if (arrList != null) { + BehaviorRetained behav; + size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (j=0; j<size; j++) { + nodes = (Object[])nodesArr[j]; + for (i=nodes.length-1; i>=0; i--) { + behav = (BehaviorRetained) nodes[i]; + if (behav.switchState.currentSwitchOn) { + addToScheduleList(behav); + } else { + removeFromScheduleList(behav); + } + } + } + } + + arrList = targets.targetList[Targets.BLN_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + Object[] objArr = (Object[])m.args[1]; + Object[] obj; + BoundingLeafRetained mbleaf; + + for (int h=0; h<size; h++) { + nodes = (Object[])nodesArr[h]; + obj = (Object[])objArr[h]; + for (i=nodes.length-1; i>=0; i--) { + + Object[] users = (Object[])obj[i]; + Object[] leafObj = new Object[1]; + mbleaf = (BoundingLeafRetained)nodes[i]; + for (j = 0; j < users.length; j++) { + if (users[j] instanceof BehaviorRetained) { + leafObj[0] = users[j]; + processTransformChanged(leafObj); + } + } + } + } + } + } + + void processBoundingLeafChanged(Object users[], + Bounds bound) { + Object leaf; + BehaviorRetained behav; + + for (int i=users.length-1; i>=0; i--) { + leaf = users[i]; + if (leaf instanceof BehaviorRetained) { + behav = (BehaviorRetained) leaf; + behav.updateTransformRegion(bound); + processBehaviorTransform(behav); + } + } + } + + final void removeFromScheduleList(BehaviorRetained behav) { + if (behav.active) { + if ((behav.wakeupMask & + BehaviorRetained.WAKEUP_DEACTIVATE) != 0) { + notifyDeactivationCondition(behav); + } + scheduleList.remove(behav); + behav.active = false; + if (behav.universe != universe) { + J3dMessage m = VirtualUniverse.mc.getMessage(); + m.threads = J3dThread.UPDATE_BEHAVIOR; + m.type = J3dMessage.BEHAVIOR_REEVALUATE; + m.universe = behav.universe; + m.args[0] = behav; + VirtualUniverse.mc.processMessage(m); + } + } + } + + final void addToScheduleList(BehaviorRetained behav) { + + if (!behav.inCallback && + !behav.active && + behav.enable && + behav.switchState.currentSwitchOn && + (behav.wakeupCondition != null) && + ((Behavior) behav.source).isLive() && + intersectVPRegion(behav.transformedRegion)) { + + scheduleList.add(behav); + behav.active = true; + if ((behav.wakeupMask & + BehaviorRetained.WAKEUP_ACTIVATE) != 0) { + notifyActivationCondition(behav); + } + + if (behav.wakeupCondition != null) { + // This reset the conditionMet, otherwise + // if conditionMet is true then WakeupCondition + // will never post message to BehaviorStructure + behav.wakeupCondition.conditionMet = false; + } + } + } + + /** + * This prevents wakeupCondition sent out message and sets + * conditionMet to true, but the + * BehaviorStructure/BehaviorScheduler is not fast enough to + * process the message and reset conditionMet to false + * when view deactivate/unregister. + */ + void resetConditionMet() { + resetConditionMet(wakeupOnAWTEvent); + resetConditionMet(wakeupOnActivation); + resetConditionMet(wakeupOnDeactivation); + resetConditionMet(wakeupOnBehaviorPost); + resetConditionMet(wakeupOnElapsedFrames); + resetConditionMet(wakeupOnViewPlatformEntry); + resetConditionMet(wakeupOnViewPlatformExit); + resetConditionMet(wakeupOnSensorEntry); + resetConditionMet(wakeupOnSensorExit); + } + + static void resetConditionMet(WakeupIndexedList list) { + WakeupCondition wakeups[] = (WakeupCondition []) list.toArray(false); + int i = list.size()-1; + while (i >= 0) { + wakeups[i--].conditionMet = false; + } + } + + void reEvaluateWakeupCount() { + WakeupOnElapsedFrames wakeupConds[] = (WakeupOnElapsedFrames []) + wakeupOnElapsedFrames.toArray(true); + int size = wakeupOnElapsedFrames.arraySize(); + int i = 0; + WakeupOnElapsedFrames cond; + + activeWakeupOnFrameCount = 0; + + while (i < size) { + cond = wakeupConds[i++]; + if (!cond.passive && + (cond.behav != null) && + cond.behav.enable) { + activeWakeupOnFrameCount++; + } + } + + + activeWakeupOnSensorCount = 0; + WakeupOnSensorEntry wentry; + WakeupOnSensorEntry wentryArr[] = (WakeupOnSensorEntry []) + wakeupOnSensorEntry.toArray(); + + for (i=wakeupOnSensorEntry.arraySize()-1; i>=0; i--) { + wentry = wentryArr[i]; + if ((wentry.behav != null) && + (wentry.behav.enable)) { + activeWakeupOnSensorCount++; + } + } + + WakeupOnSensorExit wexit; + WakeupOnSensorExit wexitArr[] = (WakeupOnSensorExit []) + wakeupOnSensorExit.toArray(); + + for (i=wakeupOnSensorExit.arraySize()-1; i>=0; i--) { + wexit = wexitArr[i]; + if ((wexit.behav != null) && + (wexit.behav.enable)) { + activeWakeupOnSensorCount++; + } + } + + } + + void cleanup() { + behaviors.clear(); + viewPlatforms.clear(); + scheduleList.clear(); + boundsEntryList.clear(); + boundsExitList.clear(); + currentSensorEntryList.clear(); + currentSensorExitList.clear(); + wakeupOnAWTEvent.clear(); + wakeupOnActivation.clear(); + wakeupOnDeactivation.clear(); + wakeupOnBehaviorPost.clear(); + wakeupOnElapsedFrames.clear(); + wakeupOnViewPlatformEntry.clear(); + wakeupOnViewPlatformExit.clear(); + wakeupOnSensorEntry.clear(); + wakeupOnSensorExit.clear(); + } +} diff --git a/src/classes/share/javax/media/j3d/Billboard.java b/src/classes/share/javax/media/j3d/Billboard.java new file mode 100644 index 0000000..34e262c --- /dev/null +++ b/src/classes/share/javax/media/j3d/Billboard.java @@ -0,0 +1,661 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; +import javax.vecmath.Point3f; +import javax.vecmath.Point3d; +import javax.vecmath.Vector3d; +import javax.vecmath.Vector3f; +import javax.vecmath.AxisAngle4d; + +/** + * The Billboard behavior node operates on the TransformGroup node + * to cause the local +z axis of the TransformGroup to point at + * the viewer's eye position. This is done regardless of the transforms + * above the specified TransformGroup node in the scene graph. + * + * <p> + * If the alignment mode is ROTATE_ABOUT_AXIS, the rotation will be + * around the specified axis. If the alignment mode is + * ROTATE_ABOUT_POINT, the rotation will be about the specified + * point, with an additional rotation to align the +y axis of the + * TransformGroup with the +y axis in the View. + * + * <p> + * Note that in a multiple View system, the alignment is done to + * the primary View only. + * + * <p> + * Billboard nodes are ideal for drawing screen aligned-text or + * for drawing roughly-symmetrical objects. A typical use might + * consist of a quadrilateral that contains a texture of a tree. + * + * @see OrientedShape3D + */ +public class Billboard extends Behavior { + /** + * Specifies that rotation should be about the specified axis. + */ + public static final int ROTATE_ABOUT_AXIS = 0; + + /** + * Specifies that rotation should be about the specified point and + * that the children's Y-axis should match the view object's Y-axis. + */ + public static final int ROTATE_ABOUT_POINT = 1; + + // Wakeup condition for Billboard node + WakeupOnElapsedFrames wakeupFrame = new WakeupOnElapsedFrames(0, true); + + + // Specifies the billboard's mode of operation. One of ROTATE_AXIAL, + // ROTATE_POINT_VIEW, or ROTATE_POINT_WORLD. + int mode = ROTATE_ABOUT_AXIS; + + // Axis about which to rotate. + Vector3f axis = new Vector3f(0.0f, 1.0f, 0.0f); + Point3f rotationPoint = new Point3f(0.0f, 0.0f, 1.0f); + private Vector3d nAxis = new Vector3d(0.0, 1.0, 0.0); // normalized axis + + // TransformGroup to operate on. + TransformGroup tg = null; + + + // reused temporaries + private Point3d viewPosition = new Point3d(); + private Point3d yUpPoint = new Point3d(); + + private Vector3d eyeVec = new Vector3d(); + private Vector3d yUp = new Vector3d(); + private Vector3d zAxis = new Vector3d(); + private Vector3d yAxis = new Vector3d(); + private Vector3d vector = new Vector3d(); + + private AxisAngle4d aa = new AxisAngle4d(); + + static final double EPSILON = 1.0e-6; + + /** + * Constructs a Billboard node with default parameters. + * The default values are as follows: + * <ul> + * alignment mode : ROTATE_ABOUT_AXIS<br> + * alignment axis : Y-axis (0,1,0)<br> + * rotation point : (0,0,1)<br> + * target transform group: null<br> + *</ul> + */ + public Billboard() { + nAxis.x = 0.0; + nAxis.y = 1.0; + nAxis.z = 0.0; + } + + + /** + * Constructs a Billboard node with default parameters that operates + * on the specified TransformGroup node. + * The default alignment mode is ROTATE_ABOUT_AXIS rotation with the axis + * pointing along the Y axis. + * @param tg the TransformGroup node that this Billboard + * node operates upon + */ + public Billboard(TransformGroup tg) { + this.tg = tg; + nAxis.x = 0.0; + nAxis.y = 1.0; + nAxis.z = 0.0; + + } + + + /** + * Constructs a Billboard node with the specified axis and mode + * that operates on the specified TransformGroup node. + * The specified axis must not be parallel to the <i>Z</i> + * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not + * possible for the +<i>Z</i> axis to point at the viewer's eye + * position by rotating about itself. The target transform will + * be set to the identity if the axis is (0,0,<i>z</i>). + * + * @param tg the TransformGroup node that this Billboard + * node operates upon + * @param mode alignment mode, one of ROTATE_ABOUT_AXIS or + * ROTATE_ABOUT_POINT + * @param axis the ray about which the billboard rotates + */ + public Billboard(TransformGroup tg, int mode, Vector3f axis) { + this.tg = tg; + this.mode = mode; + this.axis.set(axis); + double invMag; + invMag = 1.0/Math.sqrt(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + nAxis.x = (double)axis.x*invMag; + nAxis.y = (double)axis.y*invMag; + nAxis.z = (double)axis.z*invMag; + + } + + /** + * Constructs a Billboard node with the specified rotation point and mode + * that operates on the specified TransformGroup node. + * @param tg the TransformGroup node that this Billboard + * node operates upon + * @param mode alignment mode, one of ROTATE_ABOUT_AXIS or + * ROTATE_ABOUT_POINT + * @param point the position about which the billboard rotates + */ + public Billboard(TransformGroup tg, int mode, Point3f point) { + this.tg = tg; + this.mode = mode; + this.rotationPoint.set(point); + } + + /** + * Sets the alignment mode. + * @param mode one of: ROTATE_ABOUT_AXIS or ROTATE_ABOUT_POINT + */ + public void setAlignmentMode(int mode) { + this.mode = mode; + } + + + /** + * Gets the alignment mode. + * @return one of: ROTATE_ABOUT_AXIS or ROTATE_ABOUT_POINT + */ + public int getAlignmentMode() { + return this.mode; + } + + + /** + * Sets the alignment axis. + * The specified axis must not be parallel to the <i>Z</i> + * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not + * possible for the +<i>Z</i> axis to point at the viewer's eye + * position by rotating about itself. The target transform will + * be set to the identity if the axis is (0,0,<i>z</i>). + * + * @param axis the ray about which the billboard rotates + */ + public void setAlignmentAxis(Vector3f axis) { + this.axis.set(axis); + double invMag; + invMag = 1.0/Math.sqrt(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + nAxis.x = (double)axis.x*invMag; + nAxis.y = (double)axis.y*invMag; + nAxis.z = (double)axis.z*invMag; + + } + + + /** + * Sets the alignment axis. + * The specified axis must not be parallel to the <i>Z</i> + * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not + * possible for the +<i>Z</i> axis to point at the viewer's eye + * position by rotating about itself. The target transform will + * be set to the identity if the axis is (0,0,<i>z</i>). + * + * @param x the x component of the ray about which the billboard rotates + * @param y the y component of the ray about which the billboard rotates + * @param z the z component of the ray about which the billboard rotates + */ + public void setAlignmentAxis(float x, float y, float z) { + this.axis.set(x, y, z); + this.axis.set(axis); + double invMag; + invMag = 1.0/Math.sqrt(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + nAxis.x = (double)axis.x*invMag; + nAxis.y = (double)axis.y*invMag; + nAxis.z = (double)axis.z*invMag; + + } + + + /** + * Gets the alignment axis and sets the parameter to this value. + * @param axis the vector that will contain the ray about which + * the billboard rotates + */ + public void getAlignmentAxis(Vector3f axis) { + axis.set(this.axis); + } + + /** + * Sets the rotation point. + * @param point the point about which the billboard rotates + */ + public void setRotationPoint(Point3f point) { + this.rotationPoint.set(point); + } + + + /** + * Sets the rotation point. + * @param x the x component of the point about which the billboard rotates + * @param y the y component of the point about which the billboard rotates + * @param z the z component of the point about which the billboard rotates + */ + public void setRotationPoint(float x, float y, float z) { + this.rotationPoint.set(x, y, z); + } + + + /** + * Gets the rotation point and sets the parameter to this value. + * @param point the position the Billboard rotates about + */ + public void getRotationPoint(Point3f point) { + point.set(this.rotationPoint); + } + /** + * Sets the tranformGroup for this Billboard object. + * @param tg the transformGroup node which replaces the current + * transformGroup node for this Billboard + */ + public void setTarget(TransformGroup tg ) { + this.tg = tg; + } + + /** + * Returns a copy of the transformGroup associated with this Billboard. + * @return the TranformGroup for this Billboard + */ + public TransformGroup getTarget() { + return(tg); + } + + + /** + * Initialize method that sets up initial wakeup criteria. + */ + public void initialize() { + // Insert wakeup condition into queue + wakeupOn(wakeupFrame); + } + + + /** + * Process stimulus method that computes appropriate transform. + * @param criteria an enumeration of the criteria that caused the + * stimulus + */ + public void processStimulus(Enumeration criteria) { + double angle = 0.0; + double mag,sign; + double tx,ty,tz; + + if( tg == null ){ + wakeupOn(wakeupFrame); + return; + } + + // get viewplatforms's location in virutal world + View v = this.getView(); + if( v == null ) { + wakeupOn(wakeupFrame); + return; + } + Canvas3D canvas = (Canvas3D)v.getCanvas3D(0); + boolean status; + + Transform3D xform = VirtualUniverse.mc.getTransform3D(null); + Transform3D bbXform = VirtualUniverse.mc.getTransform3D(null); + Transform3D prevTransform = VirtualUniverse.mc.getTransform3D(null); + + ((TransformGroupRetained) tg.retained).getTransform(prevTransform); + + if (mode == ROTATE_ABOUT_AXIS ) { // rotate about axis + canvas.getCenterEyeInImagePlate(viewPosition); + canvas.getImagePlateToVworld(xform); // xform is imagePlateToLocal + xform.transform(viewPosition); + + // get billboard's transform + + // since we are using getTransform() to get the transform + // of the transformGroup, we need to use getLocalToVworld() + // to get the localToVworld which includes the static transform + + ((NodeRetained)tg.retained).getLocalToVworld(xform); + + xform.invert(); // xform is now vWorldToLocal + + // transform the eye position into the billboard's coordinate system + xform.transform(viewPosition); + + // eyeVec is a vector from the local origin to the eye pt in local + eyeVec.set(viewPosition); + eyeVec.normalize(); + + // project the eye into the rotation plane + status = projectToPlane(eyeVec, nAxis); + + // If the first project was successful .. + if (status) { + // project the z axis into the rotation plane + zAxis.x = 0.0; + zAxis.y = 0.0; + zAxis.z = 1.0; + status = projectToPlane(zAxis, nAxis); + } + + + ((TransformGroupRetained) tg.retained).getTransform(xform); + if (status) { + // compute the sign of the angle by checking if the cross product + // of the two vectors is in the same direction as the normal axis + vector.cross(eyeVec, zAxis); + if (vector.dot(nAxis) > 0.0) { + sign = 1.0; + } else { + sign = -1.0; + } + + // compute the angle between the projected eye vector and the + // projected z + double dot = eyeVec.dot(zAxis); + + if (dot > 1.0f) { + dot = 1.0f; + } else if (dot < -1.0f) { + dot = -1.0f; + } + + angle = sign*Math.acos(dot); + + // use -angle because xform is to *undo* rotation by angle + aa.x = nAxis.x; + aa.y = nAxis.y; + aa.z = nAxis.z; + aa.angle = -angle; + bbXform.set(aa); + if( !prevTransform.epsilonEquals(bbXform, EPSILON)) { + // Optimization for Billboard since it use passive + // behavior + // set the transform on the Billboard TG + tg.setTransform(bbXform); + } + } + else { + bbXform.setIdentity(); + if (!prevTransform.epsilonEquals(bbXform, EPSILON)) { + tg.setTransform(bbXform); + } + } + + } else { // rotate about point + // Need to rotate Z axis to point to eye, and Y axis to be + // parallel to view platform Y axis, rotating around rotation pt + + Transform3D zRotate = VirtualUniverse.mc.getTransform3D(null); + + // get the eye point + canvas.getCenterEyeInImagePlate(viewPosition); + + // derive the yUp point + yUpPoint.set(viewPosition); + yUpPoint.y += 0.01; // one cm in Physical space + + // transform the points to the Billboard's space + canvas.getImagePlateToVworld(xform); // xform is ImagePlateToVworld + + xform.transform(viewPosition); + xform.transform(yUpPoint); + + // get billboard's transform + + // since we are using getTransform() to get the transform + // of the transformGroup, we need to use getLocalToVworld() + // to get the localToVworld which includes the static transform + + ((NodeRetained)tg.retained).getLocalToVworld(xform); + + xform.invert(); // xform is vWorldToLocal + + // transfom points to local coord sys + xform.transform(viewPosition); + xform.transform(yUpPoint); + + // Make a vector from viewPostion to 0,0,0 in the BB coord sys + eyeVec.set(viewPosition); + eyeVec.normalize(); + + // create a yUp vector + yUp.set(yUpPoint); + yUp.sub(viewPosition); + yUp.normalize(); + + + // find the plane to rotate z + zAxis.x = 0.0; + zAxis.y = 0.0; + zAxis.z = 1.0; + + // rotation axis is cross product of eyeVec and zAxis + vector.cross(eyeVec, zAxis); // vector is cross product + + // if cross product is non-zero, vector is rotation axis and + // rotation angle is acos(eyeVec.dot(zAxis))); + double length = vector.length(); + + if (length > 0.0001) { + double dot = eyeVec.dot(zAxis); + + if (dot > 1.0f) { + dot = 1.0f; + } else if (dot < -1.0f) { + dot = -1.0f; + } + + angle = Math.acos(dot); + aa.x = vector.x; + aa.y = vector.y; + aa.z = vector.z; + aa.angle = -angle; + zRotate.set(aa); + } else { + // no rotation needed, set to identity (scale = 1.0) + zRotate.set(1.0); + } + + // Transform the yAxis by zRotate + yAxis.x = 0.0; + yAxis.y = 1.0; + yAxis.z = 0.0; + zRotate.transform(yAxis); + + // project the yAxis onto the plane perp to the eyeVec + status = projectToPlane(yAxis, eyeVec); + + if (status) { + // project the yUp onto the plane perp to the eyeVec + status = projectToPlane(yUp, eyeVec); + } + + ((TransformGroupRetained) tg.retained).getTransform(xform); + if (status) { + // rotation angle is acos(yUp.dot(yAxis)); + double dot = yUp.dot(yAxis); + + // Fix numerical error, otherwise acos return NULL + if (dot > 1.0f) { + dot = 1.0f; + } else if (dot < -1.0f) { + dot = -1.0f; + } + + angle = Math.acos(dot); + + // check the sign by looking a the cross product vs the eyeVec + vector.cross(yUp, yAxis); // vector is cross product + if (eyeVec.dot(vector) < 0) { + angle *= -1; + } + aa.x = eyeVec.x; + aa.y = eyeVec.y; + aa.z = eyeVec.z; + aa.angle = -angle; + + xform.set(aa); // xform is now yRotate + + // rotate around the rotation point + vector.x = rotationPoint.x; + vector.y = rotationPoint.y; + vector.z = rotationPoint.z; // vector to translate to RP + bbXform.set(vector); // translate to RP + bbXform.mul(xform); // yRotate + bbXform.mul(zRotate); // zRotate + vector.scale(-1.0); // vector to translate back + xform.set(vector); // xform to translate back + bbXform.mul(xform); // translate back + + + if (!prevTransform.epsilonEquals(bbXform, EPSILON)) { + // set the transform on the Billboard TG + tg.setTransform(bbXform); + } + } + else { + bbXform.setIdentity(); + if (!prevTransform.epsilonEquals(bbXform, EPSILON)) { + tg.setTransform(bbXform); + } + } + VirtualUniverse.mc.addToTransformFreeList(zRotate); + } + + // Insert wakeup condition into queue + wakeupOn(wakeupFrame); + + VirtualUniverse.mc.addToTransformFreeList(xform); + VirtualUniverse.mc.addToTransformFreeList(bbXform); + VirtualUniverse.mc.addToTransformFreeList(prevTransform); +} + +private boolean projectToPlane(Vector3d projVec, Vector3d planeVec) { + double dis = planeVec.dot(projVec); + projVec.x = projVec.x - planeVec.x*dis; + projVec.y = projVec.y - planeVec.y*dis; + projVec.z = projVec.z - planeVec.z*dis; + + double length = projVec.length(); + + if (length < EPSILON) { + return false; + } + projVec.scale(1 / length); + return true; +} + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Billboard b = new Billboard(); + b.duplicateNode(this, forceDuplicate); + return b; + } + + + /** + * Copies all Billboard information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + Billboard bb = (Billboard) originalNode; + + setAlignmentMode(bb.getAlignmentMode()); + + Vector3f v = new Vector3f(); + bb.getAlignmentAxis(v); + setAlignmentAxis(v); + + Point3f p = new Point3f(); + bb.getRotationPoint(p); + setRotationPoint(p); + + // this will be updated by updateNodeReferences() later + setTarget(bb.getTarget()); + } + + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + super.updateNodeReferences(referenceTable); + + // check for new TransformGroup + TransformGroup g = getTarget(); + if (g != null) { + setTarget((TransformGroup) referenceTable.getNewObjectReference(g)); + } + } +} + diff --git a/src/classes/share/javax/media/j3d/BoundingBox.java b/src/classes/share/javax/media/j3d/BoundingBox.java new file mode 100644 index 0000000..a6d8f4f --- /dev/null +++ b/src/classes/share/javax/media/j3d/BoundingBox.java @@ -0,0 +1,1975 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * This class defines an axis aligned bounding box which is used for + * bounding regions. + * + */ + +public class BoundingBox extends Bounds { + + /** + * The corner of the bounding box with the numerically smallest + * values. + */ + Point3d lower; + + /** + * The corner of the bounding box with the numerically largest + * values. + */ + Point3d upper; + + private Point3d centroid = null; + private static final double EPS = 1.0E-8; + + // reusable temp objects + private BoundingSphere tmpSphere = null; + private BoundingBox tmpBox = null; + private BoundingPolytope tmpPolytope = null; + private Point3d tmpP3d = new Point3d(); + + + /** + * Constructs and initializes a BoundingBox given min,max in x,y,z. + * @param lower the "small" corner + * @param upper the "large" corner + */ + public BoundingBox(Point3d lower, Point3d upper) { + boundId = BOUNDING_BOX; + this.lower = new Point3d(lower); + this.upper = new Point3d(upper); + updateBoundsStates(); + } + + /** + * Constructs and initializes a 2X bounding box about the + * origin. The lower corner is initialized to (-1.0d, -1.0d, -1.0d) + * and the opper corner is initialized to (1.0d, 1.0d, 1.0d). + */ + public BoundingBox() { + boundId = BOUNDING_BOX; + lower = new Point3d(-1.0d, -1.0d, -1.0d); + upper = new Point3d( 1.0d, 1.0d, 1.0d); + updateBoundsStates(); + } + + /** + * Constructs a BoundingBox from a bounding object. + * @param boundsObject a bounds object + */ + public BoundingBox(Bounds boundsObject) { + int i; + boundId = BOUNDING_BOX; + if( boundsObject == null ) { + // Negative volume. + lower = new Point3d( 1.0d, 1.0d, 1.0d); + upper = new Point3d(-1.0d, -1.0d, -1.0d); + } + else if( boundsObject.boundsIsInfinite ) { + lower = new Point3d( Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY); + upper = new Point3d(Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY); + } + else if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + + lower = new Point3d(box.lower.x, box.lower.y, box.lower.z); + upper = new Point3d(box.upper.x, box.upper.y, box.upper.z); + + } + else if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + + lower = new Point3d(sphere.center.x-sphere.radius, + sphere.center.y-sphere.radius, + sphere.center.z-sphere.radius); + + upper = new Point3d(sphere.center.x+sphere.radius, + sphere.center.y+sphere.radius, + sphere.center.z+sphere.radius); + + } + else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + if( polytope.nVerts < 1 ) { // handle degenerate case + lower = new Point3d(-1.0d, -1.0d, -1.0d); + upper = new Point3d( 1.0d, 1.0d, 1.0d); + } else { + lower = new Point3d( polytope.verts[0].x, polytope.verts[0].y, + polytope.verts[0].z); + upper = new Point3d( polytope.verts[0].x, polytope.verts[0].y, + polytope.verts[0].z); + + for(i=1;i<polytope.nVerts;i++) { + if( polytope.verts[i].x < lower.x ) + lower.x = polytope.verts[i].x; + if( polytope.verts[i].y < lower.y ) + lower.y = polytope.verts[i].y; + if( polytope.verts[i].z < lower.z ) + lower.z = polytope.verts[i].z; + if( polytope.verts[i].x > upper.x ) + upper.x = polytope.verts[i].x; + if( polytope.verts[i].y > upper.y ) + upper.y = polytope.verts[i].y; + if( polytope.verts[i].z > upper.z ) + upper.z = polytope.verts[i].z; + } + } + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox0")); + } + + updateBoundsStates(); + } + + /** + * Constructs a BoundingBox from an array of bounding objects. + * @param bounds an array of bounding objects + */ + public BoundingBox(Bounds[] bounds) { + int i=0; + + upper = new Point3d(); + lower = new Point3d(); + boundId = BOUNDING_BOX; + + if( bounds == null || bounds.length <= 0 ) { + // Negative volume. + lower = new Point3d( 1.0d, 1.0d, 1.0d); + upper = new Point3d(-1.0d, -1.0d, -1.0d); + updateBoundsStates(); + return; + } + + // find first non empty bounds object + while( bounds[i] == null && i < bounds.length) { + i++; + } + + if( i >= bounds.length ) { // all bounds objects were empty + // Negative volume. + lower = new Point3d( 1.0d, 1.0d, 1.0d); + upper = new Point3d(-1.0d, -1.0d, -1.0d); + updateBoundsStates(); + return; + } + + this.set(bounds[i++]); + if(boundsIsInfinite) + return; + + for(;i<bounds.length;i++) { + if( bounds[i] == null ); // do nothing + else if( bounds[i].boundsIsEmpty); // do nothing + else if( bounds[i].boundsIsInfinite ) { + lower.x = lower.y = lower.z = Double.NEGATIVE_INFINITY; + upper.x = upper.y = upper.z = Double.POSITIVE_INFINITY; + break; // We're done. + } + else if(bounds[i].boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)bounds[i]; + + if( lower.x > box.lower.x) lower.x = box.lower.x; + if( lower.y > box.lower.y) lower.y = box.lower.y; + if( lower.z > box.lower.z) lower.z = box.lower.z; + if( upper.x < box.upper.x) upper.x = box.upper.x; + if( upper.y < box.upper.y) upper.y = box.upper.y; + if( upper.z < box.upper.z) upper.z = box.upper.z; + + } + else if(bounds[i].boundId == BOUNDING_SPHERE) { + BoundingSphere sphere = (BoundingSphere)bounds[i]; + if( lower.x > (sphere.center.x - sphere.radius)) + lower.x = sphere.center.x - sphere.radius; + if( lower.y > (sphere.center.y - sphere.radius)) + lower.y = sphere.center.y - sphere.radius; + if( lower.z > (sphere.center.z - sphere.radius)) + lower.z = sphere.center.z - sphere.radius; + if( upper.x < (sphere.center.x + sphere.radius)) + upper.x = sphere.center.x + sphere.radius; + if( upper.y < (sphere.center.y + sphere.radius)) + upper.y = sphere.center.y + sphere.radius; + if( upper.z < (sphere.center.z + sphere.radius)) + upper.z = sphere.center.z + sphere.radius; + } + else if(bounds[i].boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)bounds[i]; + + for(i=0;i<polytope.nVerts;i++) { // TODO handle polytope with no verts + if( polytope.verts[i].x < lower.x ) + lower.x = polytope.verts[i].x; + if( polytope.verts[i].y < lower.y ) + lower.y = polytope.verts[i].y; + if( polytope.verts[i].z < lower.z ) + lower.z = polytope.verts[i].z; + if( polytope.verts[i].x > upper.x ) + upper.x = polytope.verts[i].x; + if( polytope.verts[i].y > upper.y ) + upper.y = polytope.verts[i].y; + if( polytope.verts[i].z > upper.z ) + upper.z = polytope.verts[i].z; + } + } + else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox1")); + } + } + updateBoundsStates(); + } + + /** + * Gets the lower corner of this bounding box. + * @param p1 a Point to receive the lower corner of the bounding box + */ + public void getLower(Point3d p1) { + p1.x = lower.x; + p1.y = lower.y; + p1.z = lower.z; + } + + /** + * Sets the lower corner of this bounding box. + * @param xmin minimum x value of boundining box + * @param ymin minimum y value of boundining box + * @param zmin minimum z value of boundining box + */ + public void setLower(double xmin, double ymin, double zmin ) { + lower.x = xmin; + lower.y = ymin; + lower.z = zmin; + + updateBoundsStates(); + } + + /** + * Sets the lower corner of this bounding box. + * @param p1 a Point defining the new lower corner of the bounding box + */ + public void setLower(Point3d p1) { + + lower.x = p1.x; + lower.y = p1.y; + lower.z = p1.z; + + updateBoundsStates(); + } + + /** + * Gets the upper corner of this bounding box. + * @param p1 a Point to receive the upper corner of the bounding box + */ + public void getUpper(Point3d p1) { + p1.x = upper.x; + p1.y = upper.y; + p1.z = upper.z; + } + + /** + * Sets the upper corner of this bounding box. + * @param xmax max x value of boundining box + * @param ymax max y value of boundining box + * @param zmax max z value of boundining box + */ + public void setUpper(double xmax, double ymax, double zmax ) { + upper.x = xmax; + upper.y = ymax; + upper.z = zmax; + + updateBoundsStates(); + } + + /** + * Sets the upper corner of this bounding box. + * @param p1 a Point defining the new upper corner of the bounding box + */ + public void setUpper(Point3d p1) { + upper.x = p1.x; + upper.y = p1.y; + upper.z = p1.z; + + updateBoundsStates(); + } + + /** + * Sets the the value of this BoundingBox + * @param boundsObject another bounds object + */ + public void set(Bounds boundsObject) { + int i; + + if(( boundsObject == null ) ||( boundsObject.boundsIsEmpty)) { + // Negative volume. + lower.x = lower.y = lower.z = 1.0d; + upper.x = upper.y = upper.z = -1.0d; + + } else if( boundsObject.boundsIsInfinite ) { + lower.x = lower.y = lower.z = Double.NEGATIVE_INFINITY; + upper.x = upper.y = upper.z = Double.POSITIVE_INFINITY; + + } else if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + + lower.x = box.lower.x; + lower.y = box.lower.y; + lower.z = box.lower.z; + upper.x = box.upper.x; + upper.y = box.upper.y; + upper.z = box.upper.z; + + } else if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + lower.x = sphere.center.x - sphere.radius; + lower.y = sphere.center.y - sphere.radius; + lower.z = sphere.center.z - sphere.radius; + upper.x = sphere.center.x + sphere.radius; + upper.y = sphere.center.y + sphere.radius; + upper.z = sphere.center.z + sphere.radius; + + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + lower.x = upper.x = polytope.verts[0].x; + lower.y = upper.y = polytope.verts[0].y; + lower.z = upper.z = polytope.verts[0].z; + + for(i=1;i<polytope.nVerts;i++) { + if( polytope.verts[i].x < lower.x ) lower.x = polytope.verts[i].x; + if( polytope.verts[i].y < lower.y ) lower.y = polytope.verts[i].y; + if( polytope.verts[i].z < lower.z ) lower.z = polytope.verts[i].z; + if( polytope.verts[i].x > upper.x ) upper.x = polytope.verts[i].x; + if( polytope.verts[i].y > upper.y ) upper.y = polytope.verts[i].y; + if( polytope.verts[i].z > upper.z ) upper.z = polytope.verts[i].z; + } + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox0")); + } + + updateBoundsStates(); + } + + + /** + * Creates a copy of this bounding box. + * @return a new bounding box + */ + public Object clone() { + return new BoundingBox(this.lower, this.upper); + } + + + /** + * Indicates whether the specified <code>bounds</code> object is + * equal to this BoundingBox object. They are equal if the + * specified <code>bounds</code> object is an instance of + * BoundingBox and all of the data + * members of <code>bounds</code> are equal to the corresponding + * data members in this BoundingBox. + * @param bounds the object with which the comparison is made. + * @return true if this BoundingBox is equal to <code>bounds</code>; + * otherwise false + * + * @since Java 3D 1.2 + */ + public boolean equals(Object bounds) { + try { + BoundingBox box = (BoundingBox)bounds; + return (lower.equals(box.lower) && + upper.equals(box.upper)); + } + catch (NullPointerException e) { + return false; + } + catch (ClassCastException e) { + return false; + } + } + + + /** + * Returns a hash code value for this BoundingBox object + * based on the data values in this object. Two different + * BoundingBox objects with identical data values (i.e., + * BoundingBox.equals returns true) will return the same hash + * code value. Two BoundingBox objects with different data + * members may return the same hash code value, although this is + * not likely. + * @return a hash code value for this BoundingBox object. + * + * @since Java 3D 1.2 + */ + public int hashCode() { + long bits = 1L; + bits = 31L * bits + Double.doubleToLongBits(lower.x); + bits = 31L * bits + Double.doubleToLongBits(lower.y); + bits = 31L * bits + Double.doubleToLongBits(lower.z); + bits = 31L * bits + Double.doubleToLongBits(upper.x); + bits = 31L * bits + Double.doubleToLongBits(upper.y); + bits = 31L * bits + Double.doubleToLongBits(upper.z); + return (int) (bits ^ (bits >> 32)); + } + + + /** + * Combines this bounding box with a bounding object so that the + * resulting bounding box encloses the original bounding box and the + * specified bounds object. + * @param boundsObject another bounds object + */ + public void combine(Bounds boundsObject) { + + if((boundsObject == null) || (boundsObject.boundsIsEmpty) + || (boundsIsInfinite)) + return; + + if((boundsIsEmpty) || (boundsObject.boundsIsInfinite)) { + this.set(boundsObject); + return; + } + + if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + + if( lower.x > box.lower.x) lower.x = box.lower.x; + if( lower.y > box.lower.y) lower.y = box.lower.y; + if( lower.z > box.lower.z) lower.z = box.lower.z; + if( upper.x < box.upper.x) upper.x = box.upper.x; + if( upper.y < box.upper.y) upper.y = box.upper.y; + if( upper.z < box.upper.z) upper.z = box.upper.z; + + } + else if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + if( lower.x > (sphere.center.x - sphere.radius)) + lower.x = sphere.center.x - sphere.radius; + if( lower.y > (sphere.center.y - sphere.radius)) + lower.y = sphere.center.y - sphere.radius; + if( lower.z > (sphere.center.z - sphere.radius)) + lower.z = sphere.center.z - sphere.radius; + if( upper.x < (sphere.center.x + sphere.radius)) + upper.x = sphere.center.x + sphere.radius; + if( upper.y < (sphere.center.y + sphere.radius)) + upper.y = sphere.center.y + sphere.radius; + if( upper.z < (sphere.center.z + sphere.radius)) + upper.z = sphere.center.z + sphere.radius; + + } + else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + int i; + for(i=1;i<polytope.nVerts;i++) { + if( polytope.verts[i].x < lower.x ) lower.x = polytope.verts[i].x; + if( polytope.verts[i].y < lower.y ) lower.y = polytope.verts[i].y; + if( polytope.verts[i].z < lower.z ) lower.z = polytope.verts[i].z; + if( polytope.verts[i].x > upper.x ) upper.x = polytope.verts[i].x; + if( polytope.verts[i].y > upper.y ) upper.y = polytope.verts[i].y; + if( polytope.verts[i].z > upper.z ) upper.z = polytope.verts[i].z; + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox3")); + } + + updateBoundsStates(); + } + + /** + * Combines this bounding box with an array of bounding objects + * so that the resulting bounding box encloses the original bounding + * box and the array of bounding objects. + * @param bounds an array of bounds objects + */ + public void combine(Bounds[] bounds) { + int i=0; + + if( (bounds == null) || (bounds.length <= 0) + || (boundsIsInfinite)) + return; + + // find first non empty bounds object + while( (i<bounds.length) && ((bounds[i]==null) || bounds[i].boundsIsEmpty)) { + i++; + } + if( i >= bounds.length) + return; // no non empty bounds so do not modify current bounds + + if(boundsIsEmpty) + this.set(bounds[i++]); + + if(boundsIsInfinite) + return; + + for(;i<bounds.length;i++) { + if( bounds[i] == null ); // do nothing + else if( bounds[i].boundsIsEmpty); // do nothing + else if( bounds[i].boundsIsInfinite ) { + lower.x = lower.y = lower.z = Double.NEGATIVE_INFINITY; + upper.x = upper.y = upper.z = Double.POSITIVE_INFINITY; + break; // We're done. + } + else if( bounds[i].boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)bounds[i]; + + if( lower.x > box.lower.x) lower.x = box.lower.x; + if( lower.y > box.lower.y) lower.y = box.lower.y; + if( lower.z > box.lower.z) lower.z = box.lower.z; + if( upper.x < box.upper.x) upper.x = box.upper.x; + if( upper.y < box.upper.y) upper.y = box.upper.y; + if( upper.z < box.upper.z) upper.z = box.upper.z; + } + else if( bounds[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)bounds[i]; + if( lower.x > (sphere.center.x - sphere.radius)) + lower.x = sphere.center.x - sphere.radius; + if( lower.y > (sphere.center.y - sphere.radius)) + lower.y = sphere.center.y - sphere.radius; + if( lower.z > (sphere.center.z - sphere.radius)) + lower.z = sphere.center.z - sphere.radius; + if( upper.x < (sphere.center.x + sphere.radius)) + upper.x = sphere.center.x + sphere.radius; + if( upper.y < (sphere.center.y + sphere.radius)) + upper.y = sphere.center.y + sphere.radius; + if( upper.z < (sphere.center.z + sphere.radius)) + upper.z = sphere.center.z + sphere.radius; + } + else if(bounds[i].boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)bounds[i]; + for(i=1;i<polytope.nVerts;i++) { + if( polytope.verts[i].x < lower.x ) lower.x = polytope.verts[i].x; + if( polytope.verts[i].y < lower.y ) lower.y = polytope.verts[i].y; + if( polytope.verts[i].z < lower.z ) lower.z = polytope.verts[i].z; + if( polytope.verts[i].x > upper.x ) upper.x = polytope.verts[i].x; + if( polytope.verts[i].y > upper.y ) upper.y = polytope.verts[i].y; + if( polytope.verts[i].z > upper.z ) upper.z = polytope.verts[i].z; + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox4")); + } + } + + updateBoundsStates(); + } + + /** + * Combines this bounding box with a point so that the resulting + * bounding box encloses the original bounding box and the point. + * @param point a 3d point in space + */ + public void combine(Point3d point) { + + if( boundsIsInfinite) { + return; + } + + if( boundsIsEmpty) { + upper.x = lower.x = point.x; + upper.y = lower.y = point.y; + upper.z = lower.z = point.z; + } else { + if( point.x > upper.x) upper.x = point.x; + if( point.y > upper.y) upper.y = point.y; + if( point.z > upper.z) upper.z = point.z; + + if( point.x < lower.x) lower.x = point.x; + if( point.y < lower.y) lower.y = point.y; + if( point.z < lower.z) lower.z = point.z; + } + + updateBoundsStates(); + } + + /** + * Combines this bounding box with an array of points so that the + * resulting bounding box encloses the original bounding box and the + * array of points. + * @param points an array of 3d points in space + */ + public void combine(Point3d[] points) { + + int i; + + if( boundsIsInfinite) { + return; + } + + if( boundsIsEmpty) { + this.setUpper(points[0]); + this.setLower(points[0]); + } + + for(i=0;i<points.length;i++) { + if( points[i].x > upper.x) upper.x = points[i].x; + if( points[i].y > upper.y) upper.y = points[i].y; + if( points[i].z > upper.z) upper.z = points[i].z; + + if( points[i].x < lower.x) lower.x = points[i].x; + if( points[i].y < lower.y) lower.y = points[i].y; + if( points[i].z < lower.z) lower.z = points[i].z; + } + + updateBoundsStates(); + } + + /** + * Modifies the bounding box so that it bounds the volume + * generated by transforming the given bounding object. + * @param boundsObject the bounding object to be transformed + * @param matrix a transformation matrix + */ + public void transform( Bounds boundsObject, Transform3D matrix) { + + if( boundsObject == null || boundsObject.boundsIsEmpty) { + // Negative volume. + lower.x = lower.y = lower.z = 1.0d; + upper.x = upper.y = upper.z = -1.0d; + updateBoundsStates(); + return; + } + + if(boundsObject.boundsIsInfinite) { + lower.x = lower.y = lower.z = Double.NEGATIVE_INFINITY; + upper.x = upper.y = upper.z = Double.POSITIVE_INFINITY; + updateBoundsStates(); + return; + } + + if(boundsObject.boundId == BOUNDING_BOX){ + if (tmpBox == null) { + tmpBox = new BoundingBox( (BoundingBox)boundsObject); + } else { + tmpBox.set((BoundingBox)boundsObject); + } + tmpBox.transform(matrix); + this.set(tmpBox); + } + else if(boundsObject.boundId == BOUNDING_SPHERE) { + if (tmpSphere == null) { + tmpSphere = new BoundingSphere( (BoundingSphere)boundsObject); + } else { + tmpSphere.set((BoundingSphere)boundsObject); + } + tmpSphere.transform(matrix); + this.set(tmpSphere); + } + else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + if (tmpPolytope == null) { + tmpPolytope = + new BoundingPolytope((BoundingPolytope) boundsObject); + } else { + tmpPolytope.set((BoundingPolytope)boundsObject); + } + tmpPolytope.transform(matrix); + this.set(tmpPolytope); + + } + else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox5")); + } + } + + /** + * Transforms this bounding box by the given matrix. + * @param matrix a transformation matrix + */ + public void transform(Transform3D matrix) { + + if(boundsIsInfinite) + return; + + double ux, uy, uz, lx, ly, lz; + ux = upper.x; uy = upper.y; uz = upper.z; + lx = lower.x; ly = lower.y; lz = lower.z; + + tmpP3d.set(ux, uy, uz); + matrix.transform( tmpP3d ); + upper.x = tmpP3d.x; + upper.y = tmpP3d.y; + upper.z = tmpP3d.z; + lower.x = tmpP3d.x; + lower.y = tmpP3d.y; + lower.z = tmpP3d.z; + + tmpP3d.set(lx, uy, uz); + matrix.transform( tmpP3d ); + if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x; + if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y; + if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z; + if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x; + if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y; + if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z; + + tmpP3d.set(lx, ly, uz); + matrix.transform( tmpP3d ); + if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x; + if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y; + if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z; + if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x; + if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y; + if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z; + + tmpP3d.set(ux, ly, uz); + matrix.transform( tmpP3d ); + if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x; + if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y; + if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z; + if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x; + if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y; + if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z; + + tmpP3d.set(lx, uy, lz); + matrix.transform( tmpP3d ); + if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x; + if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y; + if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z; + if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x; + if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y; + if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z; + + tmpP3d.set(ux, uy, lz); + matrix.transform( tmpP3d ); + if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x; + if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y; + if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z; + if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x; + if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y; + if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z; + + tmpP3d.set(lx, ly, lz); + matrix.transform( tmpP3d ); + if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x; + if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y; + if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z; + if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x; + if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y; + if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z; + + tmpP3d.set(ux, ly, lz); + matrix.transform( tmpP3d ); + if ( tmpP3d.x > upper.x ) upper.x = tmpP3d.x; + if ( tmpP3d.y > upper.y ) upper.y = tmpP3d.y; + if ( tmpP3d.z > upper.z ) upper.z = tmpP3d.z; + if ( tmpP3d.x < lower.x ) lower.x = tmpP3d.x; + if ( tmpP3d.y < lower.y ) lower.y = tmpP3d.y; + if ( tmpP3d.z < lower.z ) lower.z = tmpP3d.z; + + } + + /** + * Test for intersection with a ray. + * @param origin the starting point of the ray + * @param direction the direction of the ray + * @param position3 a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect(Point3d origin, Vector3d direction, Point4d position ) { + double t1,t2,tmp,tnear,tfar,invDir,invMag; + double dirx, diry, dirz; + + /* + System.out.println("BoundingBox.intersect(p,d,p) called\n"); + System.out.println("bounds = " + lower + " -> " + upper); + */ + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = origin.x; + position.y = origin.y; + position.z = origin.z; + position.w = 0.0; + return true; + } + + double dirLen = direction.x*direction.x + direction.y*direction.y + + direction.z*direction.z; + + // Handle zero length direction vector. + if(dirLen == 0.0) + return intersect(origin, position); + + invMag = 1.0/Math.sqrt(dirLen); + dirx = direction.x*invMag; + diry = direction.y*invMag; + dirz = direction.z*invMag; + + /* + System.out.println("dir = " + dirx + ", " + diry + ", " + dirz); + System.out.println("origin = " + origin); + */ + + // initialize tnear and tfar to handle dir.? == 0 cases + tnear = -Double.MAX_VALUE; + tfar = Double.MAX_VALUE; + + if(dirx == 0.0) { + //System.out.println("dirx == 0.0"); + if (origin.x < lower.x || origin.x > upper.x ) { + //System.out.println( "parallel to x plane and outside"); + return false; + } + } else { + invDir = 1.0/dirx; + t1 = (lower.x-origin.x)*invDir; + t2 = (upper.x-origin.x)*invDir; + + //System.out.println("x t1 = " + t1 + " t2 = " + t2); + if( t1 > t2) { + tnear = t2; + tfar = t1; + }else { + tnear = t1; + tfar = t2; + } + if( tfar < 0.0 ) { + //System.out.println( "x failed: tnear="+tnear+" tfar="+tfar); + return false; + } + //System.out.println("x tnear = " + tnear + " tfar = " + tfar); + } + // y + if (diry == 0.0) { + //System.out.println("diry == 0.0"); + if( origin.y < lower.y || origin.y > upper.y ){ + //System.out.println( "parallel to y plane and outside"); + return false; + } + } else { + invDir = 1.0/diry; + //System.out.println("invDir = " + invDir); + t1 = (lower.y-origin.y)*invDir; + t2 = (upper.y-origin.y)*invDir; + + if( t1 > t2) { + tmp = t1; + t1 = t2; + t2 = tmp; + } + //System.out.println("y t1 = " + t1 + " t2 = " + t2); + if( t1 > tnear) tnear = t1; + if( t2 < tfar ) tfar = t2; + + if( (tfar < 0.0) || (tnear > tfar)){ + //System.out.println( "y failed: tnear="+tnear+" tfar="+tfar); + return false; + } + //System.out.println("y tnear = " + tnear + " tfar = " + tfar); + } + + // z + if (dirz == 0.0) { + //System.out.println("dirz == 0.0"); + if( origin.z < lower.z || origin.z > upper.z ) { + //System.out.println( "parallel to z plane and outside"); + return false; + } + } else { + invDir = 1.0/dirz; + t1 = (lower.z-origin.z)*invDir; + t2 = (upper.z-origin.z)*invDir; + + if( t1 > t2) { + tmp = t1; + t1 = t2; + t2 = tmp; + } + //System.out.println("z t1 = " + t1 + " t2 = " + t2); + if( t1 > tnear) tnear = t1; + if( t2 < tfar ) tfar = t2; + + if( (tfar < 0.0) || (tnear > tfar)){ + //System.out.println( "z failed: tnear="+tnear+" tfar="+tfar); + return false; + } + //System.out.println("z tnear = " + tnear + " tfar = " + tfar); + } + + if((tnear < 0.0) && (tfar >= 0.0)) { + // origin is inside the BBox. + position.x = origin.x + dirx*tfar; + position.y = origin.y + diry*tfar; + position.z = origin.z + dirz*tfar; + position.w = tfar; + } + else { + position.x = origin.x + dirx*tnear; + position.y = origin.y + diry*tnear; + position.z = origin.z + dirz*tnear; + position.w = tnear; + } + + return true; + + } + + + /** + * Test for intersection with a point. + * @param point the pick point + * @param position a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect(Point3d point, Point4d position ) { + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = point.x; + position.y = point.y; + position.z = point.z; + position.w = 0.0; + return true; + } + + if( point.x <= upper.x && point.x >= lower.x && + point.y <= upper.y && point.y >= lower.y && + point.z <= upper.z && point.z >= lower.z) { + position.x = point.x; + position.y = point.y; + position.z = point.z; + position.w = 0.0; + return true; + } else + return false; + + } + + /** + * Test for intersection with a segment. + * @param start a point defining the start of the line segment + * @param end a point defining the end of the line segment + * @param position a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect( Point3d start, Point3d end, Point4d position ) { + double t1,t2,tmp,tnear,tfar,invDir,invMag; + double dirx, diry, dirz; + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = start.x; + position.y = start.y; + position.z = start.z; + position.w = 0.0; + return true; + } + + dirx = end.x - start.x; + diry = end.y - start.y; + dirz = end.z - start.z; + + double dirLen = dirx*dirx + diry*diry + dirz*dirz; + + // Optimization : Handle zero length direction vector. + if(dirLen == 0.0) + return intersect(start, position); + + dirLen = Math.sqrt(dirLen); + // System.out.println("dirLen is " + dirLen); + invMag = 1.0/dirLen; + dirx = dirx*invMag; + diry = diry*invMag; + dirz = dirz*invMag; + + /* + System.out.println("dir = " + dir); + System.out.println("start = " + start); + System.out.println("lower = " + lower); + System.out.println("upper = " + upper); + */ + + // initialize tnear and tfar to handle dir.? == 0 cases + tnear = -Double.MAX_VALUE; + tfar = Double.MAX_VALUE; + + if(dirx == 0.0) { + //System.out.println("dirx == 0.0"); + if (start.x < lower.x || start.x > upper.x ) { + //System.out.println( "parallel to x plane and outside"); + return false; + } + } else { + invDir = 1.0/dirx; + t1 = (lower.x-start.x)*invDir; + t2 = (upper.x-start.x)*invDir; + + //System.out.println("x t1 = " + t1 + " t2 = " + t2); + if( t1 > t2) { + tnear = t2; + tfar = t1; + }else { + tnear = t1; + tfar = t2; + } + if( tfar < 0.0 ) { + //System.out.println( "x failed: tnear="+tnear+" tfar="+tfar); + return false; + } + //System.out.println("x tnear = " + tnear + " tfar = " + tfar); + } + // y + if (diry == 0.0) { + //System.out.println("diry == 0.0"); + if( start.y < lower.y || start.y > upper.y ){ + //System.out.println( "parallel to y plane and outside"); + return false; + } + } else { + invDir = 1.0/diry; + //System.out.println("invDir = " + invDir); + t1 = (lower.y-start.y)*invDir; + t2 = (upper.y-start.y)*invDir; + + if( t1 > t2) { + tmp = t1; + t1 = t2; + t2 = tmp; + } + //System.out.println("y t1 = " + t1 + " t2 = " + t2); + if( t1 > tnear) tnear = t1; + if( t2 < tfar ) tfar = t2; + + if( (tfar < 0.0) || (tnear > tfar)){ + //System.out.println( "y failed: tnear="+tnear+" tfar="+tfar); + return false; + } + //System.out.println("y tnear = " + tnear + " tfar = " + tfar); + } + + // z + if (dirz == 0.0) { + //System.out.println("dirz == 0.0"); + if( start.z < lower.z || start.z > upper.z ) { + //System.out.println( "parallel to z plane and outside"); + return false; + } + } else { + invDir = 1.0/dirz; + t1 = (lower.z-start.z)*invDir; + t2 = (upper.z-start.z)*invDir; + + if( t1 > t2) { + tmp = t1; + t1 = t2; + t2 = tmp; + } + //System.out.println("z t1 = " + t1 + " t2 = " + t2); + if( t1 > tnear) tnear = t1; + if( t2 < tfar ) tfar = t2; + + if( (tfar < 0.0) || (tnear > tfar)){ + //System.out.println( "z failed: tnear="+tnear+" tfar="+tfar); + return false; + } + //System.out.println("z tnear = " + tnear + " tfar = " + tfar); + } + + if((tnear < 0.0) && (tfar >= 0.0)) { + // origin is inside the BBox. + position.x = start.x + dirx*tfar; + position.y = start.y + diry*tfar; + position.z = start.z + dirz*tfar; + position.w = tfar; + } + else { + if(tnear>dirLen) { + // Segment is behind BBox. + /* + System.out.println("PickSegment : intersected postion : " + position + + " tnear " + tnear + " tfar " + tfar ); + */ + return false; + } + position.x = start.x + dirx*tnear; + position.y = start.y + diry*tnear; + position.z = start.z + dirz*tnear; + + position.w = tnear; + } + + /* + System.out.println("tnear = " + tnear + " tfar = " + tfar + " w " + + position.w); + System.out.println("lower = " + lower); + System.out.println("upper = " + upper + "\n"); + */ + return true; + + } + + /** + * Test for intersection with a ray. + * @param origin the starting point of the ray + * @param direction the direction of the ray + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Point3d origin, Vector3d direction ) { + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + return true; + } + + Point3d p=new Point3d(); + return intersect( origin, direction, p ); + } + + /** + * A protected intersect method that returns the point of intersection. + * Used by Picking methods to sort or return closest picked item. + */ + boolean intersect(Point3d origin, Vector3d direction, Point3d intersect ) { + double theta=0.0; + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + intersect.x = origin.x; + intersect.y = origin.y; + intersect.z = origin.z; + return true; + } + + if (direction.x > 0.0 ) + theta = Math.max( theta, (lower.x - origin.x)/direction.x ); + if (direction.x < 0.0 ) + theta = Math.max( theta, (upper.x - origin.x)/direction.x ); + if (direction.y > 0.0 ) + theta = Math.max( theta, (lower.y - origin.y)/direction.y ); + if (direction.y < 0.0 ) + theta = Math.max( theta, (upper.y - origin.y)/direction.y ); + if (direction.z > 0.0 ) + theta = Math.max( theta, (lower.z - origin.z)/direction.z ); + if (direction.z < 0.0 ) + theta = Math.max( theta, (upper.z - origin.z)/direction.z ); + + intersect.x = origin.x + theta*direction.x; + intersect.y = origin.y + theta*direction.y; + intersect.z = origin.z + theta*direction.z; + + if (intersect.x < (lower.x-EPS)) return false; + if (intersect.x > (upper.x+EPS)) return false; + if (intersect.y < (lower.y-EPS)) return false; + if (intersect.y > (upper.y+EPS)) return false; + if (intersect.z < (lower.z-EPS)) return false; + if (intersect.z > (upper.z+EPS)) return false; + + return true; + + } + + /** + * Test for intersection with a point. + * @param point a point defining a position in 3-space + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Point3d point ) { + + if( boundsIsEmpty ) { + return false; + } + if( boundsIsInfinite ) { + return true; + } + + if( point.x <= upper.x && point.x >= lower.x && + point.y <= upper.y && point.y >= lower.y && + point.z <= upper.z && point.z >= lower.z) + return true; + else + return false; + } + /** + * Tests whether the bounding box is empty. A bounding box is + * empty if it is null (either by construction or as the result of + * a null intersection) or if its volume is negative. A bounding box + * with a volume of zero is <i>not</i> empty. + * @return true if the bounding box is empty; otherwise, it returns false + */ + public boolean isEmpty() { + + return boundsIsEmpty; + } + + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @return true or false indicating if an intersection occured + */ + boolean intersect(Bounds boundsObject, Point4d position) { + return intersect(boundsObject); + } + + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds boundsObject) { + + if( boundsObject == null ) { + return false; + } + + if( boundsIsEmpty || boundsObject.boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite || boundsObject.boundsIsInfinite ) { + return true; + } + + if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + // both boxes are axis aligned + if( upper.x > box.lower.x && box.upper.x > lower.x && + upper.y > box.lower.y && box.upper.y > lower.y && + upper.z > box.lower.z && box.upper.z > lower.z ) + return true; + else + return false; + } else if( boundsObject.boundId == BOUNDING_SPHERE) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + double rad_sq = sphere.radius*sphere.radius; + double dis = 0.0; + + if( sphere.center.x < lower.x ) + dis = (sphere.center.x-lower.x)*(sphere.center.x-lower.x); + else + if( sphere.center.x > upper.x ) + dis = (sphere.center.x-upper.x)*(sphere.center.x-upper.x); + + if( sphere.center.y < lower.y ) + dis += (sphere.center.y-lower.y)*(sphere.center.y-lower.y); + else + if( sphere.center.y > upper.y ) + dis += (sphere.center.y-upper.y)*(sphere.center.y-upper.y); + + if( sphere.center.z < lower.z ) + dis += (sphere.center.z-lower.z)*(sphere.center.z-lower.z); + else + if( sphere.center.z > upper.z ) + dis += (sphere.center.z-upper.z)*(sphere.center.z-upper.z); + + if( dis <= rad_sq ) + return true; + else + return false; + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + // intersect an axis aligned box with a polytope + return intersect_ptope_abox ( (BoundingPolytope)boundsObject, this ); + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox6")); + } + + } + + /** + * Test for intersection with an array of bounds objects. + * @param boundsObjects an array of bounding objects + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds[] boundsObjects) { + + double distsq, radsq; + int i; + + if( boundsObjects == null || boundsObjects.length <= 0 ) { + return false; + } + + if( boundsIsEmpty ) { + return false; + } + + for(i = 0; i < boundsObjects.length; i++){ + if( boundsObjects[i] == null || boundsObjects[i].boundsIsEmpty) ; + else if( boundsIsInfinite || boundsObjects[i].boundsIsInfinite ) { + return true; // We're done here. + } + else if( boundsObjects[i].boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObjects[i]; + // both boxes are axis aligned + if( upper.x > box.lower.x && box.upper.x > lower.x && + upper.y > box.lower.y && box.upper.y > lower.y && + upper.z > box.lower.z && box.upper.z > lower.z ) + return true; + } + else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + double rad_sq = sphere.radius*sphere.radius; + double dis = 0.0; + + if( sphere.center.x < lower.x ) + dis = (sphere.center.x-lower.x)*(sphere.center.x-lower.x); + else + if( sphere.center.x > upper.x ) + dis = (sphere.center.x-upper.x)*(sphere.center.x-upper.x); + + if( sphere.center.y < lower.y ) + dis += (sphere.center.y-lower.y)*(sphere.center.y-lower.y); + else + if( sphere.center.y > upper.y ) + dis += (sphere.center.y-upper.y)*(sphere.center.y-upper.y); + + if( sphere.center.z < lower.z ) + dis += (sphere.center.z-lower.z)*(sphere.center.z-lower.z); + else + if( sphere.center.z > upper.z ) + dis += (sphere.center.z-upper.z)*(sphere.center.z-upper.z); + + if( dis <= rad_sq ) + return true; + + } + else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + if( intersect_ptope_abox ( (BoundingPolytope)boundsObjects[i], this )) + return true; + } + else { + // System.out.println("intersect ?? "); + } + } + + return false; + } + + /** + * Test for intersection with another bounding box. + * @param boundsObject another bounding object + * @param newBoundBox the new bounding box which is the intersection of + * the boundsObject and this BoundingBox + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds boundsObject, BoundingBox newBoundBox) { + + if((boundsObject == null) || boundsIsEmpty || boundsObject.boundsIsEmpty ) { + // Negative volume. + newBoundBox.setLower( 1.0d, 1.0d, 1.0d); + newBoundBox.setUpper(-1.0d, -1.0d, -1.0d); + return false; + } + + + if(boundsIsInfinite && (!boundsObject.boundsIsInfinite)) { + newBoundBox.set(boundsObject); + return true; + } + else if((!boundsIsInfinite) && boundsObject.boundsIsInfinite) { + newBoundBox.set(this); + return true; + } + else if(boundsIsInfinite && boundsObject.boundsIsInfinite) { + newBoundBox.set(this); + return true; + } + else if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + // both boxes are axis aligned + if( upper.x > box.lower.x && box.upper.x > lower.x && + upper.y > box.lower.y && box.upper.y > lower.y && + upper.z > box.lower.z && box.upper.z > lower.z ){ + + + if(upper.x > box.upper.x) + newBoundBox.upper.x = box.upper.x; + else + newBoundBox.upper.x = upper.x; + + if(upper.y > box.upper.y) + newBoundBox.upper.y = box.upper.y; + else + newBoundBox.upper.y = upper.y; + + if(upper.z > box.upper.z) + newBoundBox.upper.z = box.upper.z; + else + newBoundBox.upper.z = upper.z; + + if(lower.x < box.lower.x) + newBoundBox.lower.x = box.lower.x; + else + newBoundBox.lower.x = lower.x; + + if(lower.y < box.lower.y) + newBoundBox.lower.y = box.lower.y; + else + newBoundBox.lower.y = lower.y; + + if(lower.z < box.lower.z) + newBoundBox.lower.z = box.lower.z; + else + newBoundBox.lower.z = lower.z; + + newBoundBox.updateBoundsStates(); + return true; + } else { + // Negative volume. + newBoundBox.setLower( 1.0d, 1.0d, 1.0d); + newBoundBox.setUpper(-1.0d, -1.0d, -1.0d); + return false; + } + } + else if( boundsObject.boundId == BOUNDING_SPHERE) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + if( this.intersect( sphere) ) { + BoundingBox sbox = new BoundingBox( sphere ); + this.intersect( sbox, newBoundBox ); + return true; + } else { + // Negative volume. + newBoundBox.setLower( 1.0d, 1.0d, 1.0d); + newBoundBox.setUpper(-1.0d, -1.0d, -1.0d); + return false; + } + + // System.out.println("intersect Sphere "); + } + else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + if( this.intersect( polytope)) { + BoundingBox pbox = new BoundingBox( polytope); // convert polytope to box + this.intersect( pbox, newBoundBox ); + return true; + } else { + // Negative volume. + newBoundBox.setLower( 1.0d, 1.0d, 1.0d); + newBoundBox.setUpper(-1.0d, -1.0d, -1.0d); + return false; + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox7")); + } + } + + /** + * Test for intersection with an array of bounds objects. + * @param boundsObjects an array of bounds objects + * @param newBoundBox the new bounding box which is the intersection of + * the boundsObject and this BoundingBox + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds[] boundsObjects, BoundingBox newBoundBox) { + + if( boundsObjects == null || boundsObjects.length <= 0 || boundsIsEmpty ) { + // Negative volume. + newBoundBox.setLower( 1.0d, 1.0d, 1.0d); + newBoundBox.setUpper(-1.0d, -1.0d, -1.0d); + return false; + } + + int i=0; + // find first non null bounds object + while( boundsObjects[i] == null && i < boundsObjects.length) { + i++; + } + + if( i >= boundsObjects.length ) { // all bounds objects were empty + // Negative volume. + newBoundBox.setLower( 1.0d, 1.0d, 1.0d); + newBoundBox.setUpper(-1.0d, -1.0d, -1.0d); + return false; + } + + + boolean status = false; + BoundingBox tbox = new BoundingBox(); + + for(;i<boundsObjects.length;i++) { + if( boundsObjects[i] == null || boundsObjects[i].boundsIsEmpty) ; + else if( boundsObjects[i].boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObjects[i]; + // both boxes are axis aligned + if( upper.x > box.lower.x && box.upper.x > lower.x && + upper.y > box.lower.y && box.upper.y > lower.y && + upper.z > box.lower.z && box.upper.z > lower.z ){ + + if(upper.x > box.upper.x) + newBoundBox.upper.x = box.upper.x; + else + newBoundBox.upper.x = upper.x; + + if(upper.y > box.upper.y) + newBoundBox.upper.y = box.upper.y; + else + newBoundBox.upper.y = upper.y; + + if(upper.z > box.upper.z) + newBoundBox.upper.z = box.upper.z; + else + newBoundBox.upper.z = upper.z; + + if(lower.x < box.lower.x) + newBoundBox.lower.x = box.lower.x; + else + newBoundBox.lower.x = lower.x; + + if(lower.y < box.lower.y) + newBoundBox.lower.y = box.lower.y; + else + newBoundBox.lower.y = lower.y; + + if(lower.z < box.lower.z) + newBoundBox.lower.z = box.lower.z; + else + newBoundBox.lower.z = lower.z; + status = true; + newBoundBox.updateBoundsStates(); + } + } + else if( boundsObjects[i].boundId == BOUNDING_SPHERE) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + if( this.intersect(sphere)) { + BoundingBox sbox = new BoundingBox( sphere ); // convert sphere to box + this.intersect(sbox,tbox); // insersect two boxes + if( status ) { + newBoundBox.combine( tbox ); + } else { + newBoundBox.set( tbox ); + status = true; + } + } + + } + else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i]; + if( this.intersect( polytope)) { + BoundingBox pbox = new BoundingBox( polytope ); // convert polytope to box + this.intersect(pbox,tbox); // insersect two boxes + if ( status ) { + newBoundBox.combine( tbox ); + } else { + newBoundBox.set( tbox ); + status = true; + } + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox6")); + } + + if(newBoundBox.boundsIsInfinite) + break; // We're done. + } + if( status == false ) { + // Negative volume. + newBoundBox.setLower( 1.0d, 1.0d, 1.0d); + newBoundBox.setUpper(-1.0d, -1.0d, -1.0d); + } + return status; + } + + + /** + * Finds closest bounding object that intersects this bounding box. + * @param boundsObjects an array of bounds objects + * @return closest bounding object + */ + public Bounds closestIntersection( Bounds[] boundsObjects) { + + if( boundsObjects == null || boundsObjects.length <= 0 ) { + return null; + } + + if( boundsIsEmpty ) { + return null; + } + + getCenter(); + + double dis,far_dis,pdist,x,y,z,rad_sq; + double cenX = 0.0, cenY = 0.0, cenZ = 0.0; + boolean contains = false; + boolean inside; + boolean intersect = false; + double smallest_distance = Double.MAX_VALUE; + int i,j,index=0; + + for(i = 0; i < boundsObjects.length; i++){ + if( boundsObjects[i] == null ) ; + + else if( this.intersect( boundsObjects[i])) { + intersect = true; + if( boundsObjects[i].boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObjects[i]; + cenX = (box.upper.x+box.lower.x)/2.0; + cenY = (box.upper.y+box.lower.y)/2.0; + cenZ = (box.upper.z+box.lower.z)/2.0; + dis = Math.sqrt( (centroid.x-cenX)*(centroid.x-cenX) + + (centroid.y-cenY)*(centroid.y-cenY) + + (centroid.z-cenZ)*(centroid.z-cenZ) ); + inside = false; + + if( lower.x <= box.lower.x && + lower.y <= box.lower.y && + lower.z <= box.lower.z && + upper.x >= box.upper.x && + upper.y >= box.upper.y && + upper.z >= box.upper.z ) { // box is contained + inside = true; + } + if( inside ) { + if( !contains ){ // initialize smallest_distance for the first containment + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + + } + else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + dis = Math.sqrt( (centroid.x-sphere.center.x)* + (centroid.x-sphere.center.x) + + (centroid.y-sphere.center.y)* + (centroid.y-sphere.center.y) + + (centroid.z-sphere.center.z)* + (centroid.z-sphere.center.z) ); + + inside = false; + + // sphere sphere.center is inside box + if(sphere.center.x <= upper.x && sphere.center.x >= lower.x && + sphere.center.y <= upper.y && sphere.center.y >= lower.y && + sphere.center.z <= upper.z && sphere.center.z >= lower.z ) { + // check if sphere intersects any side + if (sphere.center.x - lower.x >= sphere.radius && + upper.x - sphere.center.x >= sphere.radius && + sphere.center.y - lower.y >= sphere.radius && + upper.y - sphere.center.y >= sphere.radius && + sphere.center.z - lower.z >= sphere.radius && + upper.z - sphere.center.z >= sphere.radius ) { + // contains the sphere + inside = true; + } + } + if (inside ) { + // initialize smallest_distance for the first containment + if( !contains ){ + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } + else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = + (BoundingPolytope)boundsObjects[i]; + dis = Math.sqrt( (centroid.x-polytope.centroid.x)* + (centroid.x-polytope.centroid.x) + + (centroid.y-polytope.centroid.y)* + (centroid.y-polytope.centroid.y) + + (centroid.z-polytope.centroid.z)* + (centroid.z-polytope.centroid.z) ); + inside = true; + for(j=0;j<polytope.nVerts;j++) { + if( polytope.verts[j].x < lower.x || + polytope.verts[j].y < lower.y || + polytope.verts[j].z < lower.z || + polytope.verts[j].x > upper.x || + polytope.verts[j].y > upper.y || + polytope.verts[j].z > upper.z ) { // box contains polytope + inside = false; + + } + + } + if( inside ) { + if( !contains ){ // initialize smallest_distance for the first containment + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingBox9")); + } + } + } + + if ( intersect ) + return boundsObjects[index]; + else + return null; + } + + /** + * Tests for intersection of box and frustum. + * @param frustum + * @return true if they intersect + */ + boolean intersect( CachedFrustum frustum ) { + + if (boundsIsEmpty) + return false; + + if(boundsIsInfinite) + return true; + + // System.out.println("intersect frustum with box="+this.toString()); + // System.out.println("frustum "+frustum.toString()); + // check if box and bounding box of frustum intersect + if (upper.x > frustum.lower.x && + lower.x < frustum.upper.x && + upper.y > frustum.lower.y && + lower.y < frustum.upper.y && + upper.z > frustum.lower.z && + lower.z < frustum.upper.z ) { + // check if all box points out any frustum plane + int i = 5; + while (i>=0){ + Vector4d vc = frustum.clipPlanes[i--]; + if ((( upper.x*vc.x + upper.y*vc.y + + upper.z*vc.z + vc.w ) < EPS ) && + (( upper.x*vc.x + lower.y*vc.y + + upper.z*vc.z + vc.w ) < EPS ) && + (( upper.x*vc.x + lower.y*vc.y + + lower.z*vc.z + vc.w ) < EPS ) && + (( upper.x*vc.x + upper.y*vc.y + + lower.z*vc.z + vc.w ) < EPS ) && + (( lower.x*vc.x + upper.y*vc.y + + upper.z*vc.z + vc.w ) < EPS ) && + (( lower.x*vc.x + lower.y*vc.y + + upper.z*vc.z + vc.w ) < EPS ) && + (( lower.x*vc.x + lower.y*vc.y + + lower.z*vc.z + vc.w ) < EPS ) && + (( lower.x*vc.x + upper.y*vc.y + + lower.z*vc.z + vc.w ) < EPS )) { + // all corners outside this frustum plane + return false; + } + } + + // check if any box corner is inside of the frustum silhoette edges in the 3 views + // y-z + Point4d edge; + for (i=frustum.nxEdges-1; i >=0; i--){ + edge = frustum.xEdges[frustum.xEdgeList[i]]; + if ( ((upper.y*edge.y + + upper.z*edge.z + edge.w ) < EPS ) || + (( upper.y*edge.y + + lower.z*edge.z + edge.w ) < EPS) || + (( lower.y*edge.y + + upper.z*edge.z + edge.w ) < EPS ) || + (( lower.y*edge.y + + lower.z*edge.z + edge.w ) < EPS )) { + break; + } + } + + if ( i < 0) { + return false; // all box corners outside yz silhouette edges + } + + + // x-z + for(i=frustum.nyEdges-1; i >=0; i--){ + edge = frustum.yEdges[frustum.yEdgeList[i]]; + if ((( upper.x*edge.x + + upper.z*edge.z + edge.w ) < EPS ) || + (( upper.x*edge.x + + lower.z*edge.z + edge.w ) < EPS ) || + (( lower.x*edge.x + + upper.z*edge.z + edge.w ) < EPS ) || + (( lower.x*edge.x + + lower.z*edge.z + edge.w ) < EPS ) ) { + break; + } + } + if (i < 0) { + return false; // all box corners outside xz silhouette edges + } + + // x-y + for(i=frustum.nzEdges-1; i >=0; i--){ + edge = frustum.zEdges[frustum.zEdgeList[i]]; + if ((( upper.y*edge.y + + upper.z*edge.z + edge.w ) < EPS ) || + (( upper.y*edge.y + + lower.z*edge.z + edge.w ) < EPS ) || + (( lower.y*edge.y + + upper.z*edge.z + edge.w ) < EPS ) || + (( lower.y*edge.y + + lower.z*edge.z + edge.w ) < EPS ) ) { + break; + } + } + + if (i < 0) { + return false; // all box corners outside xy silhouette edges + } + } else { + // System.out.println("false box and bounding box of frustum do not intersect"); + return false; + } + return true; + } + + /** + * Returns a string representation of this class. + */ + public String toString() { + return new String( "Bounding box: Lower="+lower.x+" "+ + lower.y+" "+lower.z+" Upper="+upper.x+" "+ + upper.y+" "+upper.z ); + } + + private void updateBoundsStates() { + if((lower.x == Double.NEGATIVE_INFINITY) && + (lower.y == Double.NEGATIVE_INFINITY) && + (lower.z == Double.NEGATIVE_INFINITY) && + (upper.x == Double.POSITIVE_INFINITY) && + (upper.y == Double.POSITIVE_INFINITY) && + (upper.z == Double.POSITIVE_INFINITY)) { + boundsIsEmpty = false; + boundsIsInfinite = true; + return; + } + + if (checkBoundsIsNaN()) { + boundsIsEmpty = true; + boundsIsInfinite = false; + return; + } + else { + boundsIsInfinite = false; + if( lower.x > upper.x || + lower.y > upper.y || + lower.z > upper.z ) { + boundsIsEmpty = true; + } else { + boundsIsEmpty = false; + } + } + } + + // For a infinite bounds. What is the centroid ? + Point3d getCenter() { + if(centroid == null) { + centroid = new Point3d(); + } + + centroid.x = (upper.x+lower.x)*0.5; + centroid.y = (upper.y+lower.y)*0.5; + centroid.z = (upper.z+lower.z)*0.5; + + return centroid; + } + + void translate(BoundingBox bbox, Vector3d value) { + if (bbox == null || bbox.boundsIsEmpty) { + // Negative volume. + setLower( 1.0d, 1.0d, 1.0d); + setUpper(-1.0d, -1.0d, -1.0d); + return; + } + if(bbox.boundsIsInfinite) { + this.set(bbox); + return; + } + + lower.x = bbox.lower.x + value.x; + lower.y = bbox.lower.y + value.y; + lower.z = bbox.lower.z + value.z; + upper.x = bbox.upper.x + value.x; + upper.y = bbox.upper.y + value.y; + upper.z = bbox.upper.z + value.z; + } + + + /** + * if the passed the "region" is same type as this object + * then do a copy, otherwise clone the Bounds and + * return + */ + Bounds copy(Bounds r) { + if (r != null && this.boundId == r.boundId) { + BoundingBox region = (BoundingBox) r; + region.lower.x = lower.x; + region.lower.y = lower.y; + region.lower.z = lower.z; + region.upper.x = upper.x; + region.upper.y = upper.y; + region.upper.z = upper.z; + region.boundsIsEmpty = boundsIsEmpty; + region.boundsIsInfinite = boundsIsInfinite; + return region; + } + else { + return (Bounds) this.clone(); + } + } + + // Check is any of the bounds is a NaN, if yes, then + // set it an empty bounds + boolean checkBoundsIsNaN() { + if (Double.isNaN(lower.x+lower.y+lower.z+upper.x+upper.y+upper.z)) { + return true; + } + + return false; + } + + int getPickType() { + return PickShape.PICKBOUNDINGBOX; + } +} + diff --git a/src/classes/share/javax/media/j3d/BoundingLeaf.java b/src/classes/share/javax/media/j3d/BoundingLeaf.java new file mode 100644 index 0000000..15fea39 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BoundingLeaf.java @@ -0,0 +1,159 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * The BoundingLeaf node defines a bounding region object that can be + * referenced by other nodes to define a region of influence + * (Fog and Light nodes), an application region (Background, Clip, + * and Soundscape nodes), or a scheduling region (Sound and + * Behavior nodes). The bounding region is defined in the local + * coordinate system of the BoundingLeaf node. A reference to a + * BoundingLeaf node can be used in place + * of a locally defined bounds object for any of the aforementioned regions. + * <P> + * This allows an application to specify a bounding region in one coordinate system + * (the local coordinate system of the BoundingLeaf node) other than the local + * coordinate system of the node that references the bounds. For an example of how + * this might be used, consider a closed room with a number of track lights. Each + * light can move independent of the other lights and, as such, needs its own local + * coordinate system. However, the bounding volume is used by all the lights in the + * boundary of the room, which doesn't move when the lights move. In this example, + * the BoundingLeaf node allows the bounding region to be defined in the local + * coordinate system of the room, rather than in the local coordinate system of a + * particular light. All lights can then share this single bounding volume. + */ +public class BoundingLeaf extends Leaf { + /** + * Specifies that this BoundingLeaf node allows read access to its + * bounding region object. + */ + public static final int + ALLOW_REGION_READ = CapabilityBits.BOUNDING_LEAF_ALLOW_REGION_READ; + + /** + * Specifies that this BoundingLeaf node allows write access to its + * bounding region object. + */ + public static final int + ALLOW_REGION_WRITE = CapabilityBits.BOUNDING_LEAF_ALLOW_REGION_WRITE; + + /** + * Constructs a BoundingLeaf node with a null (empty) bounding region. + */ + public BoundingLeaf() { + ((BoundingLeafRetained)this.retained).createBoundingLeaf(); + } + + /** + * Constructs a BoundingLeaf node with the specified bounding region. + * @param region the bounding region of this leaf node + */ + public BoundingLeaf(Bounds region) { + ((BoundingLeafRetained)this.retained).createBoundingLeaf(); + ((BoundingLeafRetained)this.retained).initRegion(region); + } + + /** + * Sets this BoundingLeaf node's bounding region. + * @param region the bounding region of this leaf node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setRegion(Bounds region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_REGION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("BoundingLeaf0")); + + if (isLive()) + ((BoundingLeafRetained)this.retained).setRegion(region); + else + ((BoundingLeafRetained)this.retained).initRegion(region); + } + + /** + * Retrieves this BoundingLeaf's bounding region. + * @return the bounding region of this leaf node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getRegion() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_REGION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("BoundingLeaf1")); + + return ((BoundingLeafRetained)this.retained).getRegion(); + } + + /** + * Creates the BoundingLeafRetained object that this + * BoundingLeaf object will point to. + */ + void createRetained() { + this.retained = new BoundingLeafRetained(); + this.retained.setSource(this); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + BoundingLeaf bl = new BoundingLeaf(); + bl.duplicateNode(this, forceDuplicate); + return bl; + } + + + + /** + * Copies all BoundingLeaf information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + ((BoundingLeafRetained) retained).initRegion( + ((BoundingLeafRetained) originalNode.retained).getRegion()); + } + +} diff --git a/src/classes/share/javax/media/j3d/BoundingLeafRetained.java b/src/classes/share/javax/media/j3d/BoundingLeafRetained.java new file mode 100644 index 0000000..887151a --- /dev/null +++ b/src/classes/share/javax/media/j3d/BoundingLeafRetained.java @@ -0,0 +1,269 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; + +/** + * The BoundingLeaf node defines a bounding region object that can be + * referenced by other nodes to define a region of influence, an + * application region, or a scheduling region. + */ +class BoundingLeafRetained extends LeafRetained { + // Statics used when something in the boundingleaf changes + static final int REGION_CHANGED = 0x0001; + static final Integer REGION_CHANGED_MESSAGE = new Integer(REGION_CHANGED); + + // The bounding region object defined by this node + Bounds region = null; + + + // For the mirror object, this region is the transformed region + // (the region of the original bounding leaf object transformed + // by the cache transform) + Bounds transformedRegion = null; + + BoundingLeafRetained mirrorBoundingLeaf; + + // A list of Objects that refer, directly or indirectly, to this + // bounding leaf object + ArrayList users = new ArrayList(); + + // Target threads to be notified when light changes + int targetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + + + // Target threads for tranform change + int transformTargetThreads = + J3dThread.UPDATE_RENDERING_ENVIRONMENT | J3dThread.UPDATE_GEOMETRY; + + BoundingLeafRetained() { + this.nodeType = NodeRetained.BOUNDINGLEAF; + } + + void createBoundingLeaf() { + this.nodeType = NodeRetained.BOUNDINGLEAF; + mirrorBoundingLeaf = new BoundingLeafRetained(); + } + + /** + * Initialize the bounding region + */ + void initRegion(Bounds region) { + if (region != null) { + this.region = (Bounds) region.clone(); + } + else { + this.region = null; + } + if (staticTransform != null) { + this.region.transform(staticTransform.transform); + } + } + + /** + * Set the bounding region + */ + void setRegion(Bounds region) { + initRegion(region); + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = mirrorBoundingLeaf.targetThreads; + createMessage.type = J3dMessage.BOUNDINGLEAF_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= REGION_CHANGED_MESSAGE; + if (region != null) { + createMessage.args[2] = (Bounds)(region.clone()); + } else { + createMessage.args[2] = null; + } + createMessage.args[3] = mirrorBoundingLeaf.users.toArray(); + VirtualUniverse.mc.processMessage(createMessage); + } + + + /** + * Get the bounding region + */ + Bounds getRegion() { + Bounds b = null; + if (this.region != null) { + b = (Bounds) this.region.clone(); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + b.transform(invTransform); + } + } + return b; + } + + + void setLive(SetLiveState s) { + super.doSetLive(s); + + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("BoundingLeafRetained0")); + } + + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("BoundingLeafRetained1")); + } + + + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(mirrorBoundingLeaf, + Targets.BLN_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + mirrorBoundingLeaf.localToVworld = new Transform3D[1][]; + mirrorBoundingLeaf.localToVworldIndex = new int[1][]; + mirrorBoundingLeaf.localToVworld[0] = this.localToVworld[0]; + mirrorBoundingLeaf.localToVworldIndex[0] = this.localToVworldIndex[0]; + mirrorBoundingLeaf.parent = parent; + if (region != null) { + mirrorBoundingLeaf.region = (Bounds)region.clone(); + mirrorBoundingLeaf.transformedRegion = (Bounds)region.clone(); + mirrorBoundingLeaf.transformedRegion.transform( + mirrorBoundingLeaf.getCurrentLocalToVworld()); + } else { + mirrorBoundingLeaf.region = null; + mirrorBoundingLeaf.transformedRegion = null; + } + // process switch leaf + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(mirrorBoundingLeaf, + Targets.BLN_TARGETS); + } + mirrorBoundingLeaf.switchState = (SwitchState)s.switchStates.get(0); + super.markAsLive(); + } + + + /** Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateImmediateMirrorObject(Object[] objs) { + + int component = ((Integer)objs[1]).intValue(); + Bounds b = ((Bounds)objs[2]); + Transform3D t; + + if ((component & REGION_CHANGED) != 0) { + mirrorBoundingLeaf.region = b; + if (b != null) { + mirrorBoundingLeaf.transformedRegion = (Bounds)b.clone(); + t = mirrorBoundingLeaf.getCurrentLocalToVworld(); + mirrorBoundingLeaf.transformedRegion.transform(b, t); + } + else { + mirrorBoundingLeaf.transformedRegion = null; + } + + } + } + + /** + * Add a user to the list of users. + * There is no if (node.source.isLive()) check since + * mirror objects are the users of the mirror bounding leaf + * and they do not have a source. + */ + synchronized void addUser(LeafRetained node) { + users.add(node); + if (node.nodeType == NodeRetained.BACKGROUND || + node.nodeType == NodeRetained.CLIP || + node.nodeType == NodeRetained.ALTERNATEAPPEARANCE || + node instanceof FogRetained || + node instanceof LightRetained) { + transformTargetThreads |= J3dThread.UPDATE_RENDER; + } + else if (node instanceof BehaviorRetained) { + transformTargetThreads |= J3dThread.UPDATE_BEHAVIOR; + targetThreads |= J3dThread.UPDATE_BEHAVIOR; + } + else if (node instanceof SoundRetained || + node.nodeType == NodeRetained.SOUNDSCAPE) { + transformTargetThreads |= J3dThread.UPDATE_SOUND; + } + + } + + /** + * Remove user from the list of users. + * There is no if (node.source.isLive()) check since + * mirror objects are the users of the mirror bounding leaf + * and they do not have a source. + */ + synchronized void removeUser(LeafRetained u) { + int i; + users.remove(users.indexOf(u)); + // For now reconstruct the transform target threads from scratch + transformTargetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + targetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + + for (i =0; i < users.size(); i++) { + LeafRetained node = (LeafRetained)users.get(i); + if (node.nodeType == NodeRetained.BACKGROUND || + node.nodeType == NodeRetained.CLIP || + node.nodeType == NodeRetained.ALTERNATEAPPEARANCE || + node instanceof FogRetained || + node instanceof LightRetained) { + transformTargetThreads |= J3dThread.UPDATE_RENDER; + } + else if (node.nodeType == NodeRetained.BEHAVIOR) { + transformTargetThreads |= J3dThread.UPDATE_BEHAVIOR; + targetThreads |= J3dThread.UPDATE_BEHAVIOR; + } + else if (node instanceof SoundRetained || + node.nodeType == NodeRetained.SOUNDSCAPE) { + transformTargetThreads |= J3dThread.UPDATE_SOUND; + } + } + } + + + // This function is called on the mirror bounding leaf + void updateImmediateTransformChange() { + Transform3D t; + t = getCurrentLocalToVworld(); + if (region != null) { + transformedRegion.transform(region, t); + } + } + + void clearLive(SetLiveState s) { + super.clearLive(); + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(mirrorBoundingLeaf, + Targets.BLN_TARGETS); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(mirrorBoundingLeaf, + Targets.BLN_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + region.transform(xform.transform); + } + +} diff --git a/src/classes/share/javax/media/j3d/BoundingPolytope.java b/src/classes/share/javax/media/j3d/BoundingPolytope.java new file mode 100644 index 0000000..2c6ae33 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BoundingPolytope.java @@ -0,0 +1,1741 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * A BoundingPolytope defines a polyhedral bounding region using the + * intersection of four or more half spaces. The region defined by a + * BoundingPolytope is always convex and must be closed. + * <p> + * Each plane in the BoundingPolytope specifies a half-space defined + * by the equation: + * <ul> + * Ax + By + Cz + D <= 0 + * </ul> + * where A, B, C, D are the parameters that specify the plane. The + * parameters are passed in the x, y, z, and w fields, respectively, + * of a Vector4d object. The intersection of the set of half-spaces + * corresponding to the planes in this BoundingPolytope defines the + * bounding region. + */ + +public class BoundingPolytope extends Bounds { + + /** + * An array of bounding planes. + */ + Vector4d[] planes; + double[] mag; // magnitude of plane vector + double[] pDotN; // point on plane dotted with normal + Point3d[] verts; // vertices of polytope + int nVerts; // number of verts in polytope + Point3d centroid = new Point3d(); // centroid of polytope + + Point3d boxVerts[]; + boolean allocBoxVerts = false; + + /** + * Constructs a BoundingPolytope using the specified planes. + * @param planes a set of planes defining the polytope. + * @exception IllegalArgumentException if the length of the + * specified array of planes is less than 4. + */ + public BoundingPolytope(Vector4d[] planes) { + if (planes.length < 4) { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope11")); + } + + boundId = BOUNDING_POLYTOPE; + int i; + double invMag; + this.planes = new Vector4d[planes.length]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + + for(i=0;i<planes.length;i++) { + + // normalize the plane normals + mag[i] = Math.sqrt(planes[i].x*planes[i].x + planes[i].y*planes[i].y + + planes[i].z*planes[i].z); + invMag = 1.0/mag[i]; + this.planes[i] = new Vector4d( planes[i].x*invMag, planes[i].y*invMag, + planes[i].z*invMag, planes[i].w*invMag ); + + } + computeAllVerts(); // TODO lazy evaluate + } + + /** + * Constructs a BoundingPolytope and initializes it to a set of 6 + * planes that defines a cube such that -1 <= x,y,z <= 1. The + * values of the planes are as follows: + * <ul> + * planes[0] : x <= 1 (1,0,0,-1)<br> + * planes[1] : -x <= 1 (-1,0,0,-1)<br> + * planes[2] : y <= 1 (0,1,0,-1)<br> + * planes[3] : -y <= 1 (0,-1,0,-1)<br> + * planes[4] : z <= 1 (0,0,1,-1)<br> + * planes[5] : -z <= 1 (0,0,-1,-1)<br> + * </ul> + */ + public BoundingPolytope() { + boundId = BOUNDING_POLYTOPE; + planes = new Vector4d[6]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + + planes[0] = new Vector4d( 1.0, 0.0, 0.0, -1.0 ); + planes[1] = new Vector4d(-1.0, 0.0, 0.0, -1.0 ); + planes[2] = new Vector4d( 0.0, 1.0, 0.0, -1.0 ); + planes[3] = new Vector4d( 0.0,-1.0, 0.0, -1.0 ); + planes[4] = new Vector4d( 0.0, 0.0, 1.0, -1.0 ); + planes[5] = new Vector4d( 0.0, 0.0,-1.0, -1.0 ); + mag[0] = 1.0; + mag[1] = 1.0; + mag[2] = 1.0; + mag[3] = 1.0; + mag[4] = 1.0; + mag[5] = 1.0; + + computeAllVerts(); // TODO lazy evaluate + } + + + /** + * Constructs a BoundingPolytope from the specified bounds object. + * The new polytope will circumscribe the region specified by the + * input bounds. + * @param boundsObject the bounds object from which this polytope + * is constructed. + */ + public BoundingPolytope(Bounds boundsObject ) { + int i; + + boundId = BOUNDING_POLYTOPE; + + if( boundsObject == null ) { + boundsIsEmpty = true; + boundsIsInfinite = false; + initEmptyPolytope(); + computeAllVerts(); // TODO lazy evaluate + return; + } + + boundsIsEmpty = boundsObject.boundsIsEmpty; + boundsIsInfinite = boundsObject.boundsIsInfinite; + + if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + planes = new Vector4d[6]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + + planes[0] = new Vector4d( 1.0, 0.0, 0.0, -(sphere.center.x+sphere.radius) ); + planes[1] = new Vector4d(-1.0, 0.0, 0.0, sphere.center.x-sphere.radius ); + planes[2] = new Vector4d( 0.0, 1.0, 0.0, -(sphere.center.y+sphere.radius) ); + planes[3] = new Vector4d( 0.0,-1.0, 0.0, sphere.center.y-sphere.radius ); + planes[4] = new Vector4d( 0.0, 0.0, 1.0, -(sphere.center.z+sphere.radius) ); + planes[5] = new Vector4d( 0.0, 0.0,-1.0, sphere.center.z-sphere.radius ); + mag[0] = 1.0; + mag[1] = 1.0; + mag[2] = 1.0; + mag[3] = 1.0; + mag[4] = 1.0; + mag[5] = 1.0; + computeAllVerts(); // TODO lazy evaluate + + } else if( boundsObject.boundId == BOUNDING_BOX ){ + BoundingBox box = (BoundingBox)boundsObject; + planes = new Vector4d[6]; + pDotN = new double[planes.length]; + mag = new double[planes.length]; + + planes[0] = new Vector4d( 1.0, 0.0, 0.0, -box.upper.x ); + planes[1] = new Vector4d(-1.0, 0.0, 0.0, box.lower.x ); + planes[2] = new Vector4d( 0.0, 1.0, 0.0, -box.upper.y ); + planes[3] = new Vector4d( 0.0,-1.0, 0.0, box.lower.y ); + planes[4] = new Vector4d( 0.0, 0.0, 1.0, -box.upper.z ); + planes[5] = new Vector4d( 0.0, 0.0,-1.0, box.lower.z ); + mag[0] = 1.0; + mag[1] = 1.0; + mag[2] = 1.0; + mag[3] = 1.0; + mag[4] = 1.0; + mag[5] = 1.0; + computeAllVerts(); // TODO lazy evaluate + + } else if( boundsObject.boundId == BOUNDING_POLYTOPE ) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + planes = new Vector4d[polytope.planes.length]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + nVerts = polytope.nVerts; + verts = new Point3d[nVerts]; + for(i=0;i<planes.length;i++) { + planes[i] = new Vector4d(polytope.planes[i]); + mag[i] = polytope.mag[i]; + pDotN[i] = polytope.pDotN[i]; + } + for(i=0;i<verts.length;i++) { + verts[i] = new Point3d(polytope.verts[i]); + } + centroid = polytope.centroid; + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope0")); + } + } + + /** + * Constructs a BoundingPolytope from the specified array of bounds + * objects. The new polytope will circumscribe the union of the + * regions specified by the input bounds objects. + * @param boundsObjects the array bounds objects from which this + * polytope is constructed. + */ + public BoundingPolytope(Bounds[] boundsObjects) { + int i=0; + + boundId = BOUNDING_POLYTOPE; + if( boundsObjects == null || boundsObjects.length <= 0 ) { + boundsIsEmpty = true; + boundsIsInfinite = false; + initEmptyPolytope(); + computeAllVerts(); // TODO lazy evaluate + return; + } + // find first non empty bounds object + while( boundsObjects[i] == null && i < boundsObjects.length) { + i++; + } + + if( i >= boundsObjects.length ) { // all bounds objects were empty + boundsIsEmpty = true; + boundsIsInfinite = false; + initEmptyPolytope(); + computeAllVerts(); // TODO lazy evaluate + return; + } + + boundsIsEmpty = boundsObjects[i].boundsIsEmpty; + boundsIsInfinite = boundsObjects[i].boundsIsInfinite; + + if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + planes = new Vector4d[6]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + + planes[0] = new Vector4d( 1.0, 0.0, 0.0, -(sphere.center.x+sphere.radius) ); + planes[1] = new Vector4d(-1.0, 0.0, 0.0, sphere.center.x-sphere.radius ); + planes[2] = new Vector4d( 0.0, 1.0, 0.0, -(sphere.center.y+sphere.radius) ); + planes[3] = new Vector4d( 0.0,-1.0, 0.0, sphere.center.y-sphere.radius ); + planes[4] = new Vector4d( 0.0, 0.0, 1.0, -(sphere.center.z+sphere.radius) ); + planes[5] = new Vector4d( 0.0, 0.0,-1.0, sphere.center.z-sphere.radius ); + mag[0] = 1.0; + mag[1] = 1.0; + mag[2] = 1.0; + mag[3] = 1.0; + mag[4] = 1.0; + mag[5] = 1.0; + + computeAllVerts(); // TODO lazy evaluate + } else if( boundsObjects[i].boundId == BOUNDING_BOX ){ + BoundingBox box = (BoundingBox)boundsObjects[i]; + planes = new Vector4d[6]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + + planes[0] = new Vector4d( 1.0, 0.0, 0.0, -box.upper.x ); + planes[1] = new Vector4d(-1.0, 0.0, 0.0, box.lower.x ); + planes[2] = new Vector4d( 0.0, 1.0, 0.0, -box.upper.y ); + planes[3] = new Vector4d( 0.0,-1.0, 0.0, box.lower.y ); + planes[4] = new Vector4d( 0.0, 0.0, 1.0, -box.upper.z ); + planes[5] = new Vector4d( 0.0, 0.0,-1.0, box.lower.z ); + mag[0] = 1.0; + mag[1] = 1.0; + mag[2] = 1.0; + mag[3] = 1.0; + mag[4] = 1.0; + mag[5] = 1.0; + + computeAllVerts(); // TODO lazy evaluate + } else if( boundsObjects[i].boundId == BOUNDING_POLYTOPE ) { + BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i]; + planes = new Vector4d[polytope.planes.length]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + nVerts = polytope.nVerts; + verts = new Point3d[nVerts]; + for(i=0;i<planes.length;i++) { + planes[i] = new Vector4d(polytope.planes[i]); + pDotN[i] = polytope.pDotN[i]; + mag[i] = polytope.mag[i]; + } + for(i=0;i<verts.length;i++) { + verts[i] = new Point3d(polytope.verts[i]); + } + centroid = polytope.centroid; + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope1")); + } + for(i+=1;i<boundsObjects.length;i++) { + this.combine(boundsObjects[i]); + } + } + + /** + * Sets the bounding planes for this polytope. + * @param planes the new set of planes for this polytope + * @exception IllegalArgumentException if the length of the + * specified array of planes is less than 4. + */ + public void setPlanes(Vector4d[] planes) { + if (planes.length < 4) { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope11")); + } + + int i; + double invMag; + + this.planes = new Vector4d[planes.length]; + pDotN = new double[planes.length]; + mag = new double[planes.length]; + boundsIsEmpty = false; + + if( planes.length <= 0 ) { + boundsIsEmpty = true; + boundsIsInfinite = false; + computeAllVerts(); // TODO lazy evaluate + return; + } + + for(i=0;i<planes.length;i++) { + // normalize the plane normals + mag[i] = Math.sqrt(planes[i].x*planes[i].x + planes[i].y*planes[i].y + + planes[i].z*planes[i].z); + invMag = 1.0/mag[i]; + this.planes[i] = new Vector4d( planes[i].x*invMag, planes[i].y*invMag, + planes[i].z*invMag, planes[i].w*invMag ); + } + computeAllVerts(); // TODO lazy evaluate + + } + + /** + * Returns the equations of the bounding planes for this bounding polytope. + * The equations are copied into the specified array. + * The array must be large enough to hold all of the vectors. + * The individual array elements must be allocated by the caller. + * @param planes an array Vector4d to receive the bounding planes + */ + public void getPlanes(Vector4d[] planes) + { + int i; + + for(i=0;i<planes.length;i++) { + planes[i].x = this.planes[i].x*mag[i]; + planes[i].y = this.planes[i].y*mag[i]; + planes[i].z = this.planes[i].z*mag[i]; + planes[i].w = this.planes[i].w*mag[i]; + } + } + + public int getNumPlanes() { + return planes.length; + } + + /** + * Sets the planes for this BoundingPolytope by keeping its current + * number and position of planes and computing new planes positions + * to enclose the given bounds object. + * @param boundsObject another bounds object + */ + public void set(Bounds boundsObject) { + int i,k; + double dis; + + // no polytope exists yet so initialize one using the boundsObject + if( boundsObject == null ) { + boundsIsEmpty = true; + boundsIsInfinite = false; + computeAllVerts(); // TODO lazy evaluate + + }else if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + + if( boundsIsEmpty) { + initEmptyPolytope(); // no ptope exist so must initialize to default + computeAllVerts(); + } + + for(i=0;i<planes.length;i++) { // D = -(N dot C + radius) + planes[i].w = -(sphere.center.x*planes[i].x + + sphere.center.y*planes[i].y + + sphere.center.z*planes[i].z + sphere.radius); + } + + boundsIsEmpty = boundsObject.boundsIsEmpty; + boundsIsInfinite = boundsObject.boundsIsInfinite; + computeAllVerts(); // TODO lazy evaluate + + } else if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + double ux,uy,uz,lx,ly,lz,newD; + + if( boundsIsEmpty) { + initEmptyPolytope(); // no ptope exist so must initialize to default + computeAllVerts(); + } + + for(i=0;i<planes.length;i++) { + ux = box.upper.x*planes[i].x; + uy = box.upper.y*planes[i].y; + uz = box.upper.z*planes[i].z; + lx = box.lower.x*planes[i].x; + ly = box.lower.y*planes[i].y; + lz = box.lower.z*planes[i].z; + planes[i].w = -(ux + uy + uz ); // initalize plane to upper vert + if( (newD = ux + uy + lz ) + planes[i].w > 0.0) planes[i].w = -newD; + if( (newD = ux + ly + uz ) + planes[i].w > 0.0) planes[i].w = -newD; + if( (newD = ux + ly + lz ) + planes[i].w > 0.0) planes[i].w = -newD; + + if( (newD = lx + uy + uz ) + planes[i].w > 0.0) planes[i].w = -newD; + if( (newD = lx + uy + lz ) + planes[i].w > 0.0) planes[i].w = -newD; + if( (newD = lx + ly + uz ) + planes[i].w > 0.0) planes[i].w = -newD; + if( (newD = lx + ly + lz ) + planes[i].w > 0.0) planes[i].w = -newD; + } + + boundsIsEmpty = boundsObject.boundsIsEmpty; + boundsIsInfinite = boundsObject.boundsIsInfinite; + computeAllVerts(); // TODO lazy evaluate + + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + if( planes.length != polytope.planes.length) { + planes = new Vector4d[polytope.planes.length]; + for(k=0;k<polytope.planes.length;k++) planes[k] = new Vector4d(); + mag = new double[polytope.planes.length]; + pDotN = new double[polytope.planes.length]; + } + + + for(i=0;i<polytope.planes.length;i++) { + planes[i].x = polytope.planes[i].x; + planes[i].y = polytope.planes[i].y; + planes[i].z = polytope.planes[i].z; + planes[i].w = polytope.planes[i].w; + mag[i] = polytope.mag[i]; + } + nVerts = polytope.nVerts; + verts = new Point3d[nVerts]; + for (k=0; k<nVerts; k++) { + verts[k] = new Point3d(polytope.verts[k]); + } + + boundsIsEmpty = boundsObject.boundsIsEmpty; + boundsIsInfinite = boundsObject.boundsIsInfinite; + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope2")); + } + + } + + + /** + * Creates a copy of a polytope. + * @return a new BoundingPolytope + */ + public Object clone() { + return new BoundingPolytope(planes); + } + + + /** + * Indicates whether the specified <code>bounds</code> object is + * equal to this BoundingPolytope object. They are equal if the + * specified <code>bounds</code> object is an instance of + * BoundingPolytope and all of the data + * members of <code>bounds</code> are equal to the corresponding + * data members in this BoundingPolytope. + * @param bounds the object with which the comparison is made. + * @return true if this BoundingPolytope is equal to <code>bounds</code>; + * otherwise false + * + * @since Java 3D 1.2 + */ + public boolean equals(Object bounds) { + try { + BoundingPolytope polytope = (BoundingPolytope)bounds; + if (planes.length != polytope.planes.length) + return false; + for (int i = 0; i < planes.length; i++) + if (!planes[i].equals(polytope.planes[i])) + return false; + + return true; + } + catch (NullPointerException e) { + return false; + } + catch (ClassCastException e) { + return false; + } + } + + + /** + * Returns a hash code value for this BoundingPolytope object + * based on the data values in this object. Two different + * BoundingPolytope objects with identical data values (i.e., + * BoundingPolytope.equals returns true) will return the same hash + * code value. Two BoundingPolytope objects with different data + * members may return the same hash code value, although this is + * not likely. + * @return a hash code value for this BoundingPolytope object. + * + * @since Java 3D 1.2 + */ + public int hashCode() { + long bits = 1L; + + for (int i = 0; i < planes.length; i++) { + bits = 31L * bits + Double.doubleToLongBits(planes[i].x); + bits = 31L * bits + Double.doubleToLongBits(planes[i].y); + bits = 31L * bits + Double.doubleToLongBits(planes[i].z); + bits = 31L * bits + Double.doubleToLongBits(planes[i].w); + } + + return (int) (bits ^ (bits >> 32)); + } + + + /** + * Combines this bounding polytope with a bounding object so that the + * resulting bounding polytope encloses the original bounding polytope and the + * given bounds object. + * @param boundsObject another bounds object + */ + public void combine(Bounds boundsObject) { + BoundingSphere sphere; + + if((boundsObject == null) || (boundsObject.boundsIsEmpty) + || (boundsIsInfinite)) + return; + + + if((boundsIsEmpty) || (boundsObject.boundsIsInfinite)) { + this.set(boundsObject); + return; + } + + boundsIsEmpty = boundsObject.boundsIsEmpty; + boundsIsInfinite = boundsObject.boundsIsInfinite; + + if( boundsObject.boundId == BOUNDING_SPHERE ) { + sphere = (BoundingSphere)boundsObject; + int i; + double dis; + for(i = 0; i < planes.length; i++){ + dis = sphere.radius+ sphere.center.x*planes[i].x + + sphere.center.y*planes[i].y + sphere.center.z * + planes[i].z + planes[i].w; + if( dis > 0.0 ) { + planes[i].w += -dis; + } + } + } else if( boundsObject instanceof BoundingBox){ + BoundingBox b = (BoundingBox)boundsObject; + if( !allocBoxVerts){ + boxVerts = new Point3d[8]; + for(int j=0;j<8;j++)boxVerts[j] = new Point3d(); + allocBoxVerts = true; + } + boxVerts[0].set(b.lower.x, b.lower.y, b.lower.z ); + boxVerts[1].set(b.lower.x, b.upper.y, b.lower.z ); + boxVerts[2].set(b.upper.x, b.lower.y, b.lower.z ); + boxVerts[3].set(b.upper.x, b.upper.y, b.lower.z ); + boxVerts[4].set(b.lower.x, b.lower.y, b.upper.z ); + boxVerts[5].set(b.lower.x, b.upper.y, b.upper.z ); + boxVerts[6].set(b.upper.x, b.lower.y, b.upper.z ); + boxVerts[7].set(b.upper.x, b.upper.y, b.upper.z ); + this.combine(boxVerts); + + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + this.combine(polytope.verts); + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope3")); + } + + computeAllVerts(); + } + + /** + * Combines this bounding polytope with an array of bounding objects so that the + * resulting bounding polytope encloses the original bounding polytope and the + * given array of bounds object. + * @param boundsObjects an array of bounds objects + */ + public void combine(Bounds[] boundsObjects) { + int i=0; + double dis; + + if( (boundsObjects == null) || (boundsObjects.length <= 0) + || (boundsIsInfinite)) + return; + + // find first non empty bounds object + while( (i<boundsObjects.length) && ((boundsObjects[i]==null) + || boundsObjects[i].boundsIsEmpty)) { + i++; + } + if( i >= boundsObjects.length) + return; // no non empty bounds so do not modify current bounds + + if(boundsIsEmpty) + this.set(boundsObjects[i++]); + + if(boundsIsInfinite) + return; + + for(;i<boundsObjects.length;i++) { + if( boundsObjects[i] == null ); // do nothing + else if( boundsObjects[i].boundsIsEmpty ); // do nothing + else if( boundsObjects[i].boundsIsInfinite ) { + this.set(boundsObjects[i]); + break; // We're done; + } + else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + for(int j = 0; j < planes.length; j++){ + dis = sphere.radius+ sphere.center.x*planes[j].x + + sphere.center.y*planes[j].y + sphere.center.z* + planes[j].z + planes[j].w; + if( dis > 0.0 ) { + planes[j].w += -dis; + } + } + } else if( boundsObjects[i].boundId == BOUNDING_BOX){ + BoundingBox b = (BoundingBox)boundsObjects[i]; + if( !allocBoxVerts){ + boxVerts = new Point3d[8]; + for(int j=0;j<8;j++)boxVerts[j] = new Point3d(); + allocBoxVerts = true; + } + boxVerts[0].set(b.lower.x, b.lower.y, b.lower.z ); + boxVerts[1].set(b.lower.x, b.upper.y, b.lower.z ); + boxVerts[2].set(b.upper.x, b.lower.y, b.lower.z ); + boxVerts[3].set(b.upper.x, b.upper.y, b.lower.z ); + boxVerts[4].set(b.lower.x, b.lower.y, b.upper.z ); + boxVerts[5].set(b.lower.x, b.upper.y, b.upper.z ); + boxVerts[6].set(b.upper.x, b.lower.y, b.upper.z ); + boxVerts[7].set(b.upper.x, b.upper.y, b.upper.z ); + this.combine(boxVerts); + + } else if(boundsObjects[i] instanceof BoundingPolytope) { + BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i]; + this.combine(polytope.verts); + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope4")); + } + + computeAllVerts(); + } + } + + /** + * Combines this bounding polytope with a point. + * @param point a 3d point in space + */ + public void combine(Point3d point) { + int i; + double dis; + + if(boundsIsInfinite) { + return; + } + + if( boundsIsEmpty ){ + planes = new Vector4d[6]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + nVerts = 1; + verts = new Point3d[nVerts]; + verts[0] = new Point3d( point.x, point.y, point.z); + + for(i=0;i<planes.length;i++) { + pDotN[i] = 0.0; + } + planes[0] = new Vector4d( 1.0, 0.0, 0.0, -point.x ); + planes[1] = new Vector4d(-1.0, 0.0, 0.0, point.x ); + planes[2] = new Vector4d( 0.0, 1.0, 0.0, -point.y ); + planes[3] = new Vector4d( 0.0,-1.0, 0.0, point.y ); + planes[4] = new Vector4d( 0.0, 0.0, 1.0, -point.z ); + planes[5] = new Vector4d( 0.0, 0.0,-1.0, point.z ); + mag[0] = 1.0; + mag[1] = 1.0; + mag[2] = 1.0; + mag[3] = 1.0; + mag[4] = 1.0; + mag[5] = 1.0; + centroid.x = point.x; + centroid.y = point.y; + centroid.z = point.z; + boundsIsEmpty = false; + boundsIsInfinite = false; + } else { + + for(i = 0; i < planes.length; i++){ + dis = point.x*planes[i].x + point.y*planes[i].y + point.z* + planes[i].z + planes[i].w; + if( dis > 0.0 ) { + planes[i].w += -dis; + } + } + computeAllVerts(); + } + } + + /** + * Combines this bounding polytope with an array of points. + * @param points an array of 3d points in space + */ + public void combine(Point3d[] points) { + int i,j; + double dis; + + if( boundsIsInfinite) { + return; + } + + if( boundsIsEmpty ){ + planes = new Vector4d[6]; + mag = new double[planes.length]; + pDotN = new double[planes.length]; + nVerts = points.length; + verts = new Point3d[nVerts]; + verts[0] = new Point3d( points[0].x, points[0].y, points[0].z); + + for(i=0;i<planes.length;i++) { + pDotN[i] = 0.0; + } + planes[0] = new Vector4d( 1.0, 0.0, 0.0, -points[0].x ); + planes[1] = new Vector4d(-1.0, 0.0, 0.0, points[0].x ); + planes[2] = new Vector4d( 0.0, 1.0, 0.0, -points[0].y ); + planes[3] = new Vector4d( 0.0,-1.0, 0.0, points[0].y ); + planes[4] = new Vector4d( 0.0, 0.0, 1.0, -points[0].z ); + planes[5] = new Vector4d( 0.0, 0.0,-1.0, points[0].z ); + mag[0] = 1.0; + mag[1] = 1.0; + mag[2] = 1.0; + mag[3] = 1.0; + mag[4] = 1.0; + mag[5] = 1.0; + centroid.x = points[0].x; + centroid.y = points[0].y; + centroid.z = points[0].z; + boundsIsEmpty = false; + boundsIsInfinite = false; + } + + for(j = 0; j < points.length; j++){ + for(i = 0; i < planes.length; i++){ + dis = points[j].x*planes[i].x + points[j].y*planes[i].y + + points[j].z*planes[i].z + planes[i].w; + if( dis > 0.0 ) { + planes[i].w += -dis; + } + } + } + + computeAllVerts(); + } + + /** + * Modifies the bounding polytope so that it bounds the volume + * generated by transforming the given bounding object. + * @param boundsObject the bounding object to be transformed + * @param matrix a transformation matrix + */ + public void transform( Bounds boundsObject, Transform3D matrix) { + + if( boundsObject == null || boundsObject.boundsIsEmpty) { + boundsIsEmpty = true; + boundsIsInfinite = false; + computeAllVerts(); + return; + } + + if(boundsObject.boundsIsInfinite) { + this.set(boundsObject); + return; + } + + if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = new BoundingSphere((BoundingSphere)boundsObject); + sphere.transform(matrix); + this.set(sphere); + } else if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = new BoundingBox( (BoundingBox)boundsObject); + box.transform(matrix); + this.set(box); + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = new BoundingPolytope( (BoundingPolytope)boundsObject); + polytope.transform(matrix); + this.set(polytope); + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope5")); + } + } + + /** + * Transforms this bounding polytope by the given transformation matrix. + * @param matrix a transformation matrix + */ + public void transform( Transform3D matrix) { + + if(boundsIsInfinite) + return; + + int i; + double invMag; + Transform3D invTrans = VirtualUniverse.mc.getTransform3D(matrix); + + invTrans.invert(); + invTrans.transpose(); + + for(i = 0; i < planes.length; i++){ + planes[i].x = planes[i].x * mag[i]; + planes[i].y = planes[i].y * mag[i]; + planes[i].z = planes[i].z * mag[i]; + planes[i].w = planes[i].w * mag[i]; + invTrans.transform( planes[i] ); + } + + VirtualUniverse.mc.addToTransformFreeList(invTrans); + + for(i=0;i<planes.length;i++) { + + // normalize the plane normals + mag[i] = Math.sqrt(planes[i].x*planes[i].x + planes[i].y*planes[i].y + + planes[i].z*planes[i].z); + invMag = 1.0/mag[i]; + this.planes[i] = new Vector4d( planes[i].x*invMag, planes[i].y*invMag, + planes[i].z*invMag, planes[i].w*invMag ); + + } + + for (i=0; i < verts.length; i++) { + matrix.transform(verts[i]); + } + + } + + /** + * Test for intersection with a ray. + * @param origin is a the starting point of the ray + * @param direction is the direction of the ray + * @param intersectPoint is a point defining the location of the intersection + * @return true or false indicating if an intersection occured + */ + boolean intersect(Point3d origin, Vector3d direction, Point3d intersectPoint ) { + + double t,v0,vd,x,y,z,invMag; + double dx, dy, dz; + int i; + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + intersectPoint.x = origin.x; + intersectPoint.y = origin.y; + intersectPoint.z = origin.z; + return true; + } + + invMag = 1.0/Math.sqrt(direction.x*direction.x + + direction.y*direction.y + direction.z*direction.z); + dx = direction.x*invMag; + dy = direction.y*invMag; + dz = direction.z*invMag; + + // compute intersection point of ray and each plane then test if point is in polytope + for(i=0;i<planes.length;i++) { + vd = planes[i].x*dx + planes[i].y*dy + planes[i].z*dz; + v0 = -(planes[i].x*origin.x + planes[i].y*origin.y + + planes[i].z*origin.z + planes[i].w); + if(vd != 0.0) { // ray is parallel to plane + t = v0/vd; + + if( t >= 0.0) { // plane is behind origin + + x = origin.x + dx*t; // compute intersection point + y = origin.y + dy*t; + z = origin.z + dz*t; + + if( pointInPolytope(x,y,z) ) { + intersectPoint.x = x; + intersectPoint.y = y; + intersectPoint.z = z; + return true; // ray intersects a face of polytope + } + } + } + } + + return false; + } + + /** + * Test for intersection with a ray + * @param origin is a the starting point of the ray + * @param direction is the direction of the ray + * @param position is a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect(Point3d origin, Vector3d direction, Point4d position ) { + double t,v0,vd,x,y,z,invMag; + double dx, dy, dz; + int i,j; + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = origin.x; + position.y = origin.y; + position.z = origin.z; + position.w = 0.0; + return true; + } + + invMag = 1.0/Math.sqrt(direction.x*direction.x + direction.y* + direction.y + direction.z*direction.z); + dx = direction.x*invMag; + dy = direction.y*invMag; + dz = direction.z*invMag; + + for(i=0;i<planes.length;i++) { + vd = planes[i].x*dx + planes[i].y*dy + planes[i].z*dz; + v0 = -(planes[i].x*origin.x + planes[i].y*origin.y + + planes[i].z*origin.z + planes[i].w); + // System.out.println("v0="+v0+" vd="+vd); + if(vd != 0.0) { // ray is parallel to plane + t = v0/vd; + + if( t >= 0.0) { // plane is behind origin + + x = origin.x + dx*t; // compute intersection point + y = origin.y + dy*t; + z = origin.z + dz*t; + // System.out.println("t="+t+" point="+x+" "+y+" "+z); + + if( pointInPolytope(x,y,z) ) { + position.x = x; + position.y = y; + position.z = z; + position.w = t; + return true; // ray intersects a face of polytope + } + } + } + } + + return false; + + } + + /** + * Test for intersection with a point + * @param point is the pick point + * @param position is a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect(Point3d point, Point4d position ) { + int i; + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = point.x; + position.y = point.y; + position.z = point.z; + position.w = 0.0; + return true; + } + + for(i = 0; i < this.planes.length; i++){ + if(( point.x*this.planes[i].x + + point.y*this.planes[i].y + + point.z*this.planes[i].z + planes[i].w ) > 0.0 ) + return false; + + } + return true; + + } + + /** + * Test for intersection with a segment + * @param start is a point defining the start of the line segment + * @param end is a point defining the end of the line segment + * @param position is a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect( Point3d start, Point3d end, Point4d position ) { + double t,v0,vd,x,y,z; + int i,j; + + //System.out.println("line segment intersect : planes.length " + planes.length); + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = start.x; + position.y = start.y; + position.z = start.z; + position.w = 0.0; + return true; + } + + Point3d direction = new Point3d(); + + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + for(i=0;i<planes.length;i++) { + vd = planes[i].x*direction.x + planes[i].y*direction.y + + planes[i].z*direction.z; + v0 = -(planes[i].x*start.x + planes[i].y*start.y + + planes[i].z*start.z + planes[i].w); + // System.out.println("v0="+v0+" vd="+vd); + if(vd != 0.0) { // ray is parallel to plane + t = v0/vd; + + // System.out.println("t is " + t); + + if( t >= 0.0) { // plane is behind start + + x = start.x + direction.x*t; // compute intersection point + y = start.y + direction.y*t; + z = start.z + direction.z*t; + // System.out.println("t="+t+" point="+x+" "+y+" "+z); + + if( pointInPolytope(x,y,z) ) { + // if((t*t) > (end.x-start.x)*(end.x-start.x) + + // (end.y-start.y)*(end.y-start.y) + + // (end.z-start.z)*(end.z-start.z)) { + if(t <= 1.0) { + position.x = x; + position.y = y; + position.z = z; + position.w = t; + return true; // ray intersects a face of polytope + } + } + } + } + } + + return false; + + } + + /** + * Test for intersection with a ray. + * @param origin the starting point of the ray + * @param direction the direction of the ray + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Point3d origin, Vector3d direction ) { + + // compute intersection point of ray and each plane then test if point is in polytope + + double t,v0,vd,x,y,z; + int i,j; + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + return true; + } + + for(i=0;i<planes.length;i++) { + vd = planes[i].x*direction.x + planes[i].y*direction.y + + planes[i].z*direction.z; + v0 = -(planes[i].x*origin.x + planes[i].y*origin.y + + planes[i].z*origin.z + planes[i].w); + if(vd != 0.0) { // ray is parallel to plane + t = v0/vd; + + if( t >= 0.0) { // plane is behind origin + + x = origin.x + direction.x*t; // compute intersection point + y = origin.y + direction.y*t; + z = origin.z + direction.z*t; + + if( pointInPolytope(x,y,z) ) { + return true; // ray intersects a face of polytope + } else { + // System.out.println("point outside polytope"); + } + } + } + } + + return false; + + } + + /** + * Tests whether the bounding polytope is empty. A bounding polytope is + * empty if it is null (either by construction or as the result of + * a null intersection) or if its volume is negative. A bounding polytope + * with a volume of zero is <i>not</i> empty. + * @return true if the bounding polytope is empty; + * otherwise, it returns false + */ + public boolean isEmpty() { + // if nVerts > 0 after computeAllVerts(), that means + // there is some intersection between 3 planes. + return (boundsIsEmpty || (nVerts <= 0)); + } + + /** + * Test for intersection with a point. + * @param point a Point defining a position in 3-space + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Point3d point ) { + + int i; + if( boundsIsEmpty ) { + return false; + } + if( boundsIsInfinite ) { + return true; + } + + for(i = 0; i < this.planes.length; i++){ + if(( point.x*this.planes[i].x + + point.y*this.planes[i].y + + point.z*this.planes[i].z + planes[i].w ) > 0.0 ) + return false; + + } + return true; + } + + + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @return true or false indicating if an intersection occured + */ + boolean intersect(Bounds boundsObject, Point4d position) { + return intersect(boundsObject); + } + + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds boundsObject) { + + if( boundsObject == null ) { + return false; + } + + if( boundsIsEmpty || boundsObject.boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite || boundsObject.boundsIsInfinite ) { + return true; + } + + if( boundsObject.boundId == BOUNDING_SPHERE ) { + return intersect_ptope_sphere( this, (BoundingSphere)boundsObject); + } else if( boundsObject.boundId == BOUNDING_BOX){ + return intersect_ptope_abox( this, (BoundingBox)boundsObject); + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + return intersect_ptope_ptope( this, (BoundingPolytope)boundsObject); + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope6")); + } + } + + /** + * Test for intersection with another bounds object. + * @param boundsObjects an array of bounding objects + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds[] boundsObjects) { + + double distsq, radsq; + BoundingSphere sphere; + int i; + if( boundsObjects == null || boundsObjects.length <= 0 ) { + return false; + } + + if( boundsIsEmpty ) { + return false; + } + + for(i = 0; i < boundsObjects.length; i++){ + if( boundsObjects[i] == null || boundsObjects[i].boundsIsEmpty) ; + else if( boundsIsInfinite || boundsObjects[i].boundsIsInfinite ) { + return true; // We're done here. + } + if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + sphere = (BoundingSphere)boundsObjects[i]; + radsq = sphere.radius; + radsq *= radsq; + distsq = sphere.center.distanceSquared(sphere.center); + if (distsq < radsq) { + return true; + } + } else if(boundsObjects[i].boundId == BOUNDING_BOX){ + if( this.intersect(boundsObjects[i])) return true; + } else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + if( this.intersect(boundsObjects[i])) return true; + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope7")); + } + } + + return false; + } + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @param newBoundPolytope the new bounding polytope, which is the intersection of + * the boundsObject and this BoundingPolytope + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds boundsObject, BoundingPolytope newBoundPolytope) { + int i; + + if((boundsObject == null) || boundsIsEmpty || boundsObject.boundsIsEmpty ) { + newBoundPolytope.boundsIsEmpty = true; + newBoundPolytope.boundsIsInfinite = false; + newBoundPolytope.computeAllVerts(); + return false; + } + if(boundsIsInfinite && (!boundsObject.boundsIsInfinite)) { + newBoundPolytope.set(boundsObject); + return true; + } + else if((!boundsIsInfinite) && boundsObject.boundsIsInfinite) { + newBoundPolytope.set(this); + return true; + } + else if(boundsIsInfinite && boundsObject.boundsIsInfinite) { + newBoundPolytope.set(this); + return true; + } + + + BoundingBox tbox = new BoundingBox(); // convert sphere to box + + if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + if( this.intersect( sphere)) { + BoundingBox sbox = new BoundingBox( sphere ); // convert sphere to box + BoundingBox pbox = new BoundingBox( this ); // convert polytope to box + pbox.intersect(sbox, tbox); // insersect two boxes + newBoundPolytope.set( tbox ); + return true; + } + } else if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + if( this.intersect( box)) { + BoundingBox pbox = new BoundingBox( this ); // convert polytope to box + pbox.intersect(box, tbox); // insersect two boxes + newBoundPolytope.set( tbox ); + return true; + } + + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + if( this.intersect( polytope)) { + Vector4d newPlanes[] = new Vector4d[planes.length + polytope.planes.length]; + for(i=0;i<planes.length;i++) { + newPlanes[i] = new Vector4d(planes[i]); + } + for(i=0;i<polytope.planes.length;i++) { + newPlanes[planes.length + i] = new Vector4d(polytope.planes[i]); + } + BoundingPolytope newPtope= new BoundingPolytope( newPlanes ); + + newBoundPolytope.set(newPtope); + return true; + } + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope8")); + } + + newBoundPolytope.boundsIsEmpty = true; + newBoundPolytope.boundsIsInfinite = false; + newBoundPolytope.computeAllVerts(); + + return false; + } + + /** + * Test for intersection with an array of bounds objects. + * @param boundsObjects an array of bounds objects + * @param newBoundingPolytope the new bounding polytope, which is the intersection of + * the boundsObject and this BoundingPolytope + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds[] boundsObjects, BoundingPolytope newBoundingPolytope) { + + if( boundsObjects == null || boundsObjects.length <= 0 || boundsIsEmpty ) { + newBoundingPolytope.boundsIsEmpty = true; + newBoundingPolytope.boundsIsInfinite = false; + newBoundingPolytope.computeAllVerts(); + return false; + } + + int i=0; + // find first non null bounds object + while( boundsObjects[i] == null && i < boundsObjects.length) { + i++; + } + + if( i >= boundsObjects.length ) { // all bounds objects were empty + newBoundingPolytope.boundsIsEmpty = true; + newBoundingPolytope.boundsIsInfinite = false; + newBoundingPolytope.computeAllVerts(); + return false; + } + + boolean status = false; + BoundingBox tbox = new BoundingBox(); // convert sphere to box + + for(i=0;i<boundsObjects.length;i++) { + if( boundsObjects[i] == null || boundsObjects[i].boundsIsEmpty) ; + else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + if( this.intersect( sphere)) { + BoundingBox sbox = new BoundingBox( sphere ); // convert sphere to box + BoundingBox pbox = new BoundingBox( this ); // convert polytope to box + pbox.intersect(sbox, tbox); // insersect two boxes + if ( status ) { + newBoundingPolytope.combine( tbox ); + } else { + newBoundingPolytope.set( tbox ); + status = true; + } + } + } else if( boundsObjects[i].boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObjects[i]; + if( this.intersect( box) ){ + BoundingBox pbox = new BoundingBox( this ); // convert polytope to box + pbox.intersect(box,tbox); // insersect two boxes + if ( status ) { + newBoundingPolytope.combine( tbox ); + } else { + newBoundingPolytope.set( tbox ); + status = true; + } + } else { + } + + } else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i]; + if( this.intersect( polytope)) { + Vector4d newPlanes[] = new Vector4d[planes.length + polytope.planes.length]; + for(i=0;i<planes.length;i++) { + newPlanes[i] = new Vector4d(planes[i]); + } + for(i=0;i<polytope.planes.length;i++) { + newPlanes[planes.length + i] = new Vector4d(polytope.planes[i]); + } + BoundingPolytope newPtope= new BoundingPolytope( newPlanes ); + if ( status ) { + newBoundingPolytope.combine( newPtope ); + } else { + newBoundingPolytope.set( newPtope ); + status = true; + } + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope8")); + } + + if(newBoundingPolytope.boundsIsInfinite) + break; // We're done. + + } + + if( status == false ) { + newBoundingPolytope.boundsIsEmpty = true; + newBoundingPolytope.boundsIsInfinite = false; + newBoundingPolytope.computeAllVerts(); + } + return status; + + } + /** + * Finds closest bounding object that intersects this bounding polytope. + * @param boundsObjects is an array of bounds objects + * @return closest bounding object + */ + public Bounds closestIntersection( Bounds[] boundsObjects) { + + if( boundsObjects == null || boundsObjects.length <= 0 ) { + return null; + } + + if( boundsIsEmpty ) { + return null; + } + + double dis,disToPlane; + boolean contains = false; + boolean inside; + double smallest_distance = Double.MAX_VALUE; + int i,j,index=0; + double cenX = 0.0, cenY = 0.0, cenZ = 0.0; + + for(i = 0; i < boundsObjects.length; i++){ + if( boundsObjects[i] == null ); + + else if( this.intersect( boundsObjects[i])) { + if( boundsObjects[i] instanceof BoundingSphere ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + dis = Math.sqrt( (centroid.x-sphere.center.x)*(centroid.x-sphere.center.x) + + (centroid.y-sphere.center.y)*(centroid.y-sphere.center.y) + + (centroid.z-sphere.center.z)*(centroid.z-sphere.center.z) ); + inside = true; + for(j=0;j<planes.length;j++) { + if( ( sphere.center.x*planes[j].x + + sphere.center.y*planes[j].y + + sphere.center.z*planes[j].z + planes[i].w ) > 0.0 ) { // check if sphere center in polytope + disToPlane = sphere.center.x*planes[j].x + + sphere.center.y*planes[j].y + + sphere.center.z*planes[j].z + planes[j].w; + + // check if distance from center to plane is larger than radius + if( disToPlane > sphere.radius ) inside = false; + } + } + if( inside) { // contains the sphere + if( !contains ){ // initialize smallest_distance for the first containment + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if( boundsObjects[i] instanceof BoundingBox){ + BoundingBox box = (BoundingBox)boundsObjects[i]; + cenX = (box.upper.x+box.lower.x)/2.0; + cenY = (box.upper.y+box.lower.y)/2.0; + cenZ = (box.upper.z+box.lower.z)/2.0; + dis = Math.sqrt( (centroid.x-cenX)*(centroid.x-cenX) + + (centroid.y-cenY)*(centroid.y-cenY) + + (centroid.z-cenZ)*(centroid.z-cenZ) ); + inside = true; + if( !pointInPolytope( box.upper.x, box.upper.y, box.upper.z ) ) inside = false; + if( !pointInPolytope( box.upper.x, box.upper.y, box.lower.z ) ) inside = false; + if( !pointInPolytope( box.upper.x, box.lower.y, box.upper.z ) ) inside = false; + if( !pointInPolytope( box.upper.x, box.lower.y, box.lower.z ) ) inside = false; + if( !pointInPolytope( box.lower.x, box.upper.y, box.upper.z ) ) inside = false; + if( !pointInPolytope( box.lower.x, box.upper.y, box.lower.z ) ) inside = false; + if( !pointInPolytope( box.lower.x, box.lower.y, box.upper.z ) ) inside = false; + if( !pointInPolytope( box.lower.x, box.lower.y, box.lower.z ) ) inside = false; + + if( inside ) { // contains box + if( !contains ){ // initialize smallest_distance for the first containment + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + + } else if(boundsObjects[i] instanceof BoundingPolytope) { + BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i]; + dis = Math.sqrt( (centroid.x-polytope.centroid.x)*(centroid.x-polytope.centroid.x) + + (centroid.y-polytope.centroid.y)*(centroid.y-polytope.centroid.y) + + (centroid.z-polytope.centroid.z)*(centroid.z-polytope.centroid.z) ); + inside = true; + for(j=0;j<polytope.nVerts;j++) { + if ( !pointInPolytope( polytope.verts[j].x, polytope.verts[j].y, polytope.verts[j].z ) ) + inside = false; + } + if( inside ) { + if( !contains ){ // initialize smallest_distance for the first containment + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingPolytope10")); + } + } + } + + return boundsObjects[index]; + } + + /** + * Returns a string representation of this class + */ + public String toString() { + int i; + + String description = new String("BoundingPolytope:\n Num Planes ="+planes.length); + for(i = 0; i < planes.length; i++){ + description = description+"\n"+mag[i]*planes[i].x+" "+ + mag[i]*planes[i].y+" "+mag[i]*planes[i].z+" "+mag[i]*planes[i].w; + } + + return description; + } + + private void computeVertex( int a, int b, int c ) { + double det,x,y,z; + + det = planes[a].x*planes[b].y*planes[c].z + planes[a].y*planes[b].z*planes[c].x + + planes[a].z*planes[b].x*planes[c].y - planes[a].z*planes[b].y*planes[c].x - + planes[a].y*planes[b].x*planes[c].z - planes[a].x*planes[b].z*planes[c].y; + + // System.out.println("\n det="+det); + if( det*det < EPSILON ){ + // System.out.println("parallel planes="+a+" "+b+" "+c); + return; // two planes are parallel + } + + det = 1.0/det; + + x = (planes[b].y*planes[c].z - planes[b].z*planes[c].y) * pDotN[a]; + y = (planes[b].z*planes[c].x - planes[b].x*planes[c].z) * pDotN[a]; + z = (planes[b].x*planes[c].y - planes[b].y*planes[c].x) * pDotN[a]; + + x += (planes[c].y*planes[a].z - planes[c].z*planes[a].y) * pDotN[b]; + y += (planes[c].z*planes[a].x - planes[c].x*planes[a].z) * pDotN[b]; + z += (planes[c].x*planes[a].y - planes[c].y*planes[a].x) * pDotN[b]; + + x += (planes[a].y*planes[b].z - planes[a].z*planes[b].y) * pDotN[c]; + y += (planes[a].z*planes[b].x - planes[a].x*planes[b].z) * pDotN[c]; + z += (planes[a].x*planes[b].y - planes[a].y*planes[b].x) * pDotN[c]; + + x = x*det; + y = y*det; + z = z*det; + + if (pointInPolytope( x, y, z ) ) { + if (nVerts >= verts.length) { + Point3d newVerts[] = new Point3d[nVerts << 1]; + for(int i=0;i<nVerts;i++) { + newVerts[i] = verts[i]; + } + verts = newVerts; + } + verts[nVerts++] = new Point3d( x,y,z); + } + } + + + private void computeAllVerts() { + int i,a,b,c; + double x,y,z; + + nVerts = 0; + + if( boundsIsEmpty) { + verts = null; + return; + } + + verts = new Point3d[planes.length*planes.length]; + + for(i=0;i<planes.length;i++) { + pDotN[i] = -planes[i].x*planes[i].w*planes[i].x - + planes[i].y*planes[i].w*planes[i].y - + planes[i].z*planes[i].w*planes[i].z; + } + + for(a=0;a<planes.length-2;a++) { + for(b=a+1;b<planes.length-1;b++) { + for(c=b+1;c<planes.length;c++) { + computeVertex(a,b,c); + } + } + } + // TODO correctly compute centroid + + x=y=z=0.0; + Point3d newVerts[] = new Point3d[nVerts]; + + for(i=0;i<nVerts;i++) { + x += verts[i].x; + y += verts[i].y; + z += verts[i].z; + // copy the verts into an array of the correct size + newVerts[i] = verts[i]; + } + + this.verts = newVerts; // copy the verts into an array of the correct size + + centroid.x = x/nVerts; + centroid.y = y/nVerts; + centroid.z = z/nVerts; + + checkBoundsIsEmpty(); + + } + + private boolean pointInPolytope( double x, double y, double z ){ + + for (int i = 0; i < planes.length; i++){ + if(( x*planes[i].x + + y*planes[i].y + + z*planes[i].z + planes[i].w ) > EPSILON ) { + return false; + } + + } + return true; + } + + private void checkBoundsIsEmpty() { + boundsIsEmpty = (planes.length < 4); + } + + private void initEmptyPolytope() { + planes = new Vector4d[6]; + pDotN = new double[6]; + mag = new double[6]; + verts = new Point3d[planes.length*planes.length]; + nVerts = 0; + + planes[0] = new Vector4d( 1.0, 0.0, 0.0, -1.0 ); + planes[1] = new Vector4d(-1.0, 0.0, 0.0, -1.0 ); + planes[2] = new Vector4d( 0.0, 1.0, 0.0, -1.0 ); + planes[3] = new Vector4d( 0.0,-1.0, 0.0, -1.0 ); + planes[4] = new Vector4d( 0.0, 0.0, 1.0, -1.0 ); + planes[5] = new Vector4d( 0.0, 0.0,-1.0, -1.0 ); + mag[0] = 1.0; + mag[1] = 1.0; + mag[2] = 1.0; + mag[3] = 1.0; + mag[4] = 1.0; + mag[5] = 1.0; + + checkBoundsIsEmpty(); + } + + Point3d getCenter() { + return centroid; + } + + /** + * if the passed the "region" is same type as this object + * then do a copy, otherwise clone the Bounds and + * return + */ + Bounds copy(Bounds r) { + int i, k; + + if (r != null && this.boundId == r.boundId) { + BoundingPolytope region = (BoundingPolytope) r; + if( region.planes.length !=planes.length) { + region.planes = new Vector4d[planes.length]; + + for(k=0;k< region.planes.length;k++) + region.planes[k] = new Vector4d(); + + region.mag = new double[planes.length]; + region.pDotN = new double[planes.length]; + region.verts = new Point3d[nVerts]; + region.nVerts = nVerts; + for(k=0;k<nVerts;k++) + region.verts[k] = new Point3d(verts[k]); + } + + + for(i=0;i<planes.length;i++) { + region.planes[i].x = planes[i].x; + region.planes[i].y = planes[i].y; + region.planes[i].z = planes[i].z; + region.planes[i].w = planes[i].w; + region.mag[i] = mag[i]; + } + + region.boundsIsEmpty = boundsIsEmpty; + region.boundsIsInfinite = boundsIsInfinite; + return region; + } + else { + return (Bounds) this.clone(); + } + } + + int getPickType() { + return PickShape.PICKBOUNDINGPOLYTOPE; + } +} diff --git a/src/classes/share/javax/media/j3d/BoundingSphere.java b/src/classes/share/javax/media/j3d/BoundingSphere.java new file mode 100644 index 0000000..796adac --- /dev/null +++ b/src/classes/share/javax/media/j3d/BoundingSphere.java @@ -0,0 +1,1765 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * This class defines a spherical bounding region which is defined by a + * center point and a radius. + */ + +public class BoundingSphere extends Bounds { + + /** + * The center of the bounding sphere. + */ + Point3d center; + + /** + * The radius of the bounding sphere. + */ + double radius; + + Point3d boxVerts[]; + boolean allocBoxVerts = false; + + // reusable temp objects + private BoundingBox tmpBox = null; + private BoundingPolytope tmpPolytope = null; + + /** + * Constructs and initializes a BoundingSphere from a center and radius. + * @param center the center of the bounding sphere + * @param radius the radius of the bounding sphere + */ + public BoundingSphere(Point3d center, double radius) { + this.center = new Point3d(center); + this.radius = radius; + boundId = BOUNDING_SPHERE; + updateBoundsStates(); + } + /** + * Constructs and initializes a BoundingSphere with radius = 1 at 0 0 0. + */ + public BoundingSphere() { + boundId = BOUNDING_SPHERE; + center = new Point3d(); + radius = 1.0; + } + + /** + * Constructs and initializes a BoundingSphere from a bounding object. + * @param boundsObject a bounds object + */ + public BoundingSphere(Bounds boundsObject) { + int i; + + boundId = BOUNDING_SPHERE; + if (boundsObject == null) { + // Negative volume. + center = new Point3d(); + radius = -1.0; + } + else if( boundsObject.boundsIsInfinite ) { + center = new Point3d(); + radius = Double.POSITIVE_INFINITY; + + } else if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + center = new Point3d(); + center.x = (box.upper.x+box.lower.x)/2.0; + center.y = (box.upper.y+box.lower.y)/2.0; + center.z = (box.upper.z+box.lower.z)/2.0; + radius = 0.5*(Math.sqrt((box.upper.x-box.lower.x)* + (box.upper.x-box.lower.x)+ + (box.upper.y-box.lower.y)* + (box.upper.y-box.lower.y)+ + (box.upper.z-box.lower.z)* + (box.upper.z-box.lower.z))); + + } else if (boundsObject.boundId == BOUNDING_SPHERE) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + center = new Point3d(sphere.center); + radius = sphere.radius; + + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + double t,dis,dis_sq,rad_sq,oldc_to_new_c; + center = new Point3d(); + center.x = polytope.centroid.x; + center.y = polytope.centroid.y; + center.z = polytope.centroid.z; + radius = Math.sqrt( (polytope.verts[0].x - center.x)* + (polytope.verts[0].x - center.x) + + (polytope.verts[0].y - center.y)* + (polytope.verts[0].y - center.y) + + (polytope.verts[0].z - center.z)* + (polytope.verts[0].z - center.z)); + + for(i=1;i<polytope.nVerts;i++) { + rad_sq = radius * radius; + + dis_sq = (polytope.verts[i].x - center.x)* + (polytope.verts[i].x - center.x) + + (polytope.verts[i].y - center.y)* + (polytope.verts[i].y - center.y) + + (polytope.verts[i].z - center.z)* + (polytope.verts[i].z - center.z); + + // change sphere so one side passes through the point + // and other passes through the old sphere + if( dis_sq > rad_sq) { + dis = Math.sqrt( dis_sq); + radius = (radius + dis)*.5; + oldc_to_new_c = dis - radius; + t = oldc_to_new_c/dis; + center.x = center.x + (polytope.verts[i].x - center.x)*t; + center.y = center.y + (polytope.verts[i].y - center.y)*t; + center.z = center.z + (polytope.verts[i].z - center.z)*t; + } + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere0")); + } + + updateBoundsStates(); + } + + /** + * Constructs and initializes a BoundingSphere from an array of bounding objects. + * @param boundsObjects an array of bounds objects + */ + public BoundingSphere(Bounds[] boundsObjects) { + int i=0; + double dis,t,d1; + + boundId = BOUNDING_SPHERE; + center = new Point3d(); + + if( boundsObjects == null || boundsObjects.length <= 0 ) { + // Negative volume. + radius = -1.0; + updateBoundsStates(); + return; + } + + // find first non empty bounds object + while( boundsObjects[i] == null && i < boundsObjects.length) { + i++; + } + + if( i >= boundsObjects.length ) { // all bounds objects were empty + // Negative volume. + radius = -1.0; + updateBoundsStates(); + return; + } + + this.set(boundsObjects[i++]); + if(boundsIsInfinite) + return; + + for(;i<boundsObjects.length;i++) { + if( boundsObjects[i] == null ); // do nothing + else if( boundsObjects[i].boundsIsEmpty); // do nothing + else if( boundsObjects[i].boundsIsInfinite ) { + radius = Double.POSITIVE_INFINITY; + break; // We're done. + } + else if( boundsObjects[i].boundId == BOUNDING_BOX){ + BoundingBox b = (BoundingBox)boundsObjects[i]; + if( !allocBoxVerts){ + boxVerts = new Point3d[8]; + for(int j=0;j<8;j++)boxVerts[j] = new Point3d(); + allocBoxVerts = true; + } + boxVerts[0].set(b.lower.x, b.lower.y, b.lower.z ); + boxVerts[1].set(b.lower.x, b.upper.y, b.lower.z ); + boxVerts[2].set(b.upper.x, b.lower.y, b.lower.z ); + boxVerts[3].set(b.upper.x, b.upper.y, b.lower.z ); + boxVerts[4].set(b.lower.x, b.lower.y, b.upper.z ); + boxVerts[5].set(b.lower.x, b.upper.y, b.upper.z ); + boxVerts[6].set(b.upper.x, b.lower.y, b.upper.z ); + boxVerts[7].set(b.upper.x, b.upper.y, b.upper.z ); + this.combine(boxVerts); + } + else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + dis = Math.sqrt( (center.x - sphere.center.x)* + (center.x - sphere.center.x) + + (center.y - sphere.center.y)* + (center.y - sphere.center.y) + + (center.z - sphere.center.z)* + (center.z - sphere.center.z) ); + if( radius > sphere.radius) { + if( (dis+sphere.radius) > radius) { + d1 = .5*(radius-sphere.radius+dis); + t = d1/dis; + radius = d1+sphere.radius; + center.x = sphere.center.x + (center.x-sphere.center.x)*t; + center.y = sphere.center.y + (center.y-sphere.center.y)*t; + center.z = sphere.center.z + (center.z-sphere.center.z)*t; + } + }else { + if( (dis+radius) <= sphere.radius) { + center.x = sphere.center.x; + center.y = sphere.center.y; + center.z = sphere.center.z; + radius = sphere.radius; + }else { + d1 = .5*(sphere.radius-radius+dis); + t = d1/dis; + radius = d1+radius; + center.x = center.x + (sphere.center.x-center.x)*t; + center.y = center.y + (sphere.center.y-center.y)*t; + center.z = center.z + (sphere.center.z-center.z)*t; + } + } + } + else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i]; + this.combine(polytope.verts); + + } + else { + if( boundsObjects[i] != null ) + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere0")); + } + } + updateBoundsStates(); + } + + /** + * Returns the radius of this bounding sphere as a double. + * @return the radius of the bounding sphere + */ + public double getRadius() { + return radius; + } + + /** + * Sets the radius of this bounding sphere from a double. + * @param r the new radius for the bounding sphere + */ + public void setRadius(double r) { + radius = r; + updateBoundsStates(); + } + + /** + * Returns the position of this bounding sphere as a point. + * @param center a Point to receive the center of the bounding sphere + + */ + public void getCenter(Point3d center) { + center.x = this.center.x; + center.y = this.center.y; + center.z = this.center.z; + } + + /** + * Sets the position of this bounding sphere from a point. + * @param center a Point defining the new center of the bounding sphere + */ + public void setCenter(Point3d center) { + this.center.x = center.x; + this.center.y = center.y; + this.center.z = center.z; + checkBoundsIsNaN(); + } + + /** + * Sets the value of this BoundingSphere. + * @param boundsObject another bounds object + */ + public void set(Bounds boundsObject){ + int i; + + if ((boundsObject == null) || boundsObject.boundsIsEmpty) { + center.x = 0.0; + center.y = 0.0; + center.z = 0.0; + radius = -1.0; + } else if( boundsObject.boundsIsInfinite ) { + center.x = 0.0; + center.y = 0.0; + center.z = 0.0; + radius = Double.POSITIVE_INFINITY; + } else if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + center.x = (box.upper.x + box.lower.x )/2.0; + center.y = (box.upper.y + box.lower.y )/2.0; + center.z = (box.upper.z + box.lower.z )/2.0; + radius = 0.5*Math.sqrt((box.upper.x-box.lower.x)* + (box.upper.x-box.lower.x)+ + (box.upper.y-box.lower.y)* + (box.upper.y-box.lower.y)+ + (box.upper.z-box.lower.z)* + (box.upper.z-box.lower.z)); + } else if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + radius = sphere.radius; + center.x = sphere.center.x; + center.y = sphere.center.y; + center.z = sphere.center.z; + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + double t,dis,dis_sq,rad_sq,oldc_to_new_c; + center.x = polytope.centroid.x; + center.y = polytope.centroid.y; + center.z = polytope.centroid.z; + radius = Math.sqrt((polytope.verts[0].x - center.x)* + (polytope.verts[0].x - center.x) + + (polytope.verts[0].y - center.y)* + (polytope.verts[0].y - center.y) + + (polytope.verts[0].z - center.z)* + (polytope.verts[0].z - center.z)); + + for(i=1;i<polytope.nVerts;i++) { + rad_sq = radius * radius; + + dis_sq = (polytope.verts[i].x - center.x)* + (polytope.verts[i].x - center.x) + + (polytope.verts[i].y - center.y)* + (polytope.verts[i].y - center.y) + + (polytope.verts[i].z - center.z)* + (polytope.verts[i].z - center.z); + + // change sphere so one side passes through the point + // and other passes through the old sphere + if( dis_sq > rad_sq) { // point is outside sphere + dis = Math.sqrt( dis_sq); + radius = (radius + dis)*.5; + oldc_to_new_c = dis - radius; + t = oldc_to_new_c/dis; + center.x = center.x + (polytope.verts[i].x - center.x)*t; + center.y = center.y + (polytope.verts[i].y - center.y)*t; + center.z = center.z + (polytope.verts[i].z - center.z)*t; + } + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere2")); + } + updateBoundsStates(); + } + + /** + * Creates a copy of the bounding sphere. + * @return a BoundingSphere + */ + public Object clone() { + return new BoundingSphere(this.center, this.radius); + } + + + /** + * Indicates whether the specified <code>bounds</code> object is + * equal to this BoundingSphere object. They are equal if the + * specified <code>bounds</code> object is an instance of + * BoundingSphere and all of the data + * members of <code>bounds</code> are equal to the corresponding + * data members in this BoundingSphere. + * @param bounds the object with which the comparison is made. + * @return true if this BoundingSphere is equal to <code>bounds</code>; + * otherwise false + * + * @since Java 3D 1.2 + */ + public boolean equals(Object bounds) { + try { + BoundingSphere sphere = (BoundingSphere)bounds; + return (center.equals(sphere.center) && + radius == sphere.radius); + } + catch (NullPointerException e) { + return false; + } + catch (ClassCastException e) { + return false; + } + } + + + /** + * Returns a hash code value for this BoundingSphere object + * based on the data values in this object. Two different + * BoundingSphere objects with identical data values (i.e., + * BoundingSphere.equals returns true) will return the same hash + * code value. Two BoundingSphere objects with different data + * members may return the same hash code value, although this is + * not likely. + * @return a hash code value for this BoundingSphere object. + * + * @since Java 3D 1.2 + */ + public int hashCode() { + long bits = 1L; + bits = 31L * bits + Double.doubleToLongBits(radius); + bits = 31L * bits + Double.doubleToLongBits(center.x); + bits = 31L * bits + Double.doubleToLongBits(center.y); + bits = 31L * bits + Double.doubleToLongBits(center.z); + return (int) (bits ^ (bits >> 32)); + } + + + /** + * Combines this bounding sphere with a bounding object so that the + * resulting bounding sphere encloses the original bounding sphere and the + * given bounds object. + * @param boundsObject another bounds object + */ + public void combine(Bounds boundsObject) { + double t,dis,d1,u,l,x,y,z,oldc_to_new_c; + BoundingSphere sphere; + + if((boundsObject == null) || (boundsObject.boundsIsEmpty) + || (boundsIsInfinite)) + return; + + if((boundsIsEmpty) || (boundsObject.boundsIsInfinite)) { + this.set(boundsObject); + return; + } + + + if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox b = (BoundingBox)boundsObject; + + // start with point furthest from sphere + u = b.upper.x-center.x; + l = b.lower.x-center.x; + if( u*u > l*l) + x = b.upper.x; + else + x = b.lower.x; + + u = b.upper.y-center.y; + l = b.lower.y-center.y; + if( u*u > l*l) + y = b.upper.y; + else + y = b.lower.y; + + u = b.upper.z-center.z; + l = b.lower.z-center.z; + if( u*u > l*l) + z = b.upper.z; + else + z = b.lower.z; + + dis = Math.sqrt( (x - center.x)*(x - center.x) + + (y - center.y)*(y - center.y) + + (z - center.z)*(z - center.z) ); + + if( dis > radius) { + radius = (dis + radius)*.5; + oldc_to_new_c = dis - radius; + center.x = (radius*center.x + oldc_to_new_c*x)/dis; + center.y = (radius*center.y + oldc_to_new_c*y)/dis; + center.z = (radius*center.z + oldc_to_new_c*z)/dis; + combinePoint( b.upper.x, b.upper.y, b.upper.z); + combinePoint( b.upper.x, b.upper.y, b.lower.z); + combinePoint( b.upper.x, b.lower.y, b.upper.z); + combinePoint( b.upper.x, b.lower.y, b.lower.z); + combinePoint( b.lower.x, b.upper.y, b.upper.z); + combinePoint( b.lower.x, b.upper.y, b.lower.z); + combinePoint( b.lower.x, b.lower.y, b.upper.z); + combinePoint( b.lower.x, b.lower.y, b.lower.z); + } + } else if( boundsObject.boundId == BOUNDING_SPHERE ) { + sphere = (BoundingSphere)boundsObject; + dis = Math.sqrt( (center.x - sphere.center.x)* + (center.x - sphere.center.x) + + (center.y - sphere.center.y)* + (center.y - sphere.center.y) + + (center.z - sphere.center.z)* + (center.z - sphere.center.z) ); + if( radius > sphere.radius) { + if( (dis+sphere.radius) > radius) { + d1 = .5*(radius-sphere.radius+dis); + t = d1/dis; + radius = d1+sphere.radius; + center.x = sphere.center.x + (center.x-sphere.center.x)*t; + center.y = sphere.center.y + (center.y-sphere.center.y)*t; + center.z = sphere.center.z + (center.z-sphere.center.z)*t; + } + }else { + if( (dis+radius) <= sphere.radius) { + center.x = sphere.center.x; + center.y = sphere.center.y; + center.z = sphere.center.z; + radius = sphere.radius; + }else { + d1 = .5*(sphere.radius-radius+dis); + t = d1/dis; + radius = d1+radius; + center.x = center.x + (sphere.center.x-center.x)*t; + center.y = center.y + (sphere.center.y-center.y)*t; + center.z = center.z + (sphere.center.z-center.z)*t; + } + } + + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + this.combine(polytope.verts); + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere3")); + } + updateBoundsStates(); + } + + private void combinePoint( double x, double y, double z) { + double dis,oldc_to_new_c; + dis = Math.sqrt( (x - center.x)*(x - center.x) + + (y - center.y)*(y - center.y) + + (z - center.z)*(z - center.z) ); + + if( dis > radius) { + radius = (dis + radius)*.5; + oldc_to_new_c = dis - radius; + center.x = (radius*center.x + oldc_to_new_c*x)/dis; + center.y = (radius*center.y + oldc_to_new_c*y)/dis; + center.z = (radius*center.z + oldc_to_new_c*z)/dis; + } + } + + /** + * Combines this bounding sphere with an array of bounding objects so that the + * resulting bounding sphere encloses the original bounding sphere and the + * given array of bounds object. + * @param boundsObjects an array of bounds objects + */ + public void combine(Bounds[] boundsObjects) { + BoundingSphere sphere; + BoundingBox b; + BoundingPolytope polytope; + double t,dis,d1,u,l,x,y,z,oldc_to_new_c; + int i=0; + + + if((boundsObjects == null) || (boundsObjects.length <= 0) + || (boundsIsInfinite)) + return; + + // find first non empty bounds object + while((i<boundsObjects.length) && + ((boundsObjects[i] == null) || boundsObjects[i].boundsIsEmpty)) { + i++; + } + if( i >= boundsObjects.length) + return; // no non empty bounds so do not modify current bounds + + if( boundsIsEmpty) + this.set(boundsObjects[i++]); + + if(boundsIsInfinite) + return; + + for(;i<boundsObjects.length;i++) { + if( boundsObjects[i] == null ); // do nothing + else if( boundsObjects[i].boundsIsEmpty); // do nothing + else if( boundsObjects[i].boundsIsInfinite ) { + center.x = 0.0; + center.y = 0.0; + center.z = 0.0; + radius = Double.POSITIVE_INFINITY; + break; // We're done. + } else if( boundsObjects[i].boundId == BOUNDING_BOX){ + b = (BoundingBox)boundsObjects[i]; + + // start with point furthest from sphere + u = b.upper.x-center.x; + l = b.lower.x-center.x; + if( u*u > l*l) + x = b.upper.x; + else + x = b.lower.x; + + u = b.upper.y-center.y; + l = b.lower.y-center.y; + if( u*u > l*l) + y = b.upper.y; + else + y = b.lower.y; + + u = b.upper.z-center.z; + l = b.lower.z-center.z; + if( u*u > l*l) + z = b.upper.z; + else + z = b.lower.z; + + dis = Math.sqrt( (x - center.x)*(x - center.x) + + (y - center.y)*(y - center.y) + + (z - center.z)*(z - center.z) ); + + if( dis > radius) { + radius = (dis + radius)*.5; + oldc_to_new_c = dis - radius; + center.x = (radius*center.x + oldc_to_new_c*x)/dis; + center.y = (radius*center.y + oldc_to_new_c*y)/dis; + center.z = (radius*center.z + oldc_to_new_c*z)/dis; + combinePoint( b.upper.x, b.upper.y, b.upper.z); + combinePoint( b.upper.x, b.upper.y, b.lower.z); + combinePoint( b.upper.x, b.lower.y, b.upper.z); + combinePoint( b.upper.x, b.lower.y, b.lower.z); + combinePoint( b.lower.x, b.upper.y, b.upper.z); + combinePoint( b.lower.x, b.upper.y, b.lower.z); + combinePoint( b.lower.x, b.lower.y, b.upper.z); + combinePoint( b.lower.x, b.lower.y, b.lower.z); + } + } else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + sphere = (BoundingSphere)boundsObjects[i]; + dis = Math.sqrt( (center.x - sphere.center.x)* + (center.x - sphere.center.x) + + (center.y - sphere.center.y)* + (center.y - sphere.center.y) + + (center.z - sphere.center.z)* + (center.z - sphere.center.z) ); + if( radius > sphere.radius) { + if( (dis+sphere.radius) > radius) { + d1 = .5*(radius-sphere.radius+dis); + t = d1/dis; + radius = d1+sphere.radius; + center.x = sphere.center.x + (center.x-sphere.center.x)*t; + center.y = sphere.center.y + (center.y-sphere.center.y)*t; + center.z = sphere.center.z + (center.z-sphere.center.z)*t; + } + }else { + if( (dis+radius) <= sphere.radius) { + center.x = sphere.center.x; + center.y = sphere.center.y; + center.z = sphere.center.z; + radius = sphere.radius; + }else { + d1 = .5*(sphere.radius-radius+dis); + t = d1/dis; + radius = d1+radius; + center.x = center.x + (sphere.center.x-center.x)*t; + center.y = center.y + (sphere.center.y-center.y)*t; + center.z = center.z + (sphere.center.z-center.z)*t; + } + } + } else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + polytope = (BoundingPolytope)boundsObjects[i]; + this.combine(polytope.verts); + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere4")); + } + } + + updateBoundsStates(); + } + + /** + * Combines this bounding sphere with a point. + * @param point a 3D point in space + */ + public void combine(Point3d point) { + double t,dis,oldc_to_new_c; + + if( boundsIsInfinite) { + return; + } + + if( boundsIsEmpty) { + radius = 0.0; + center.x = point.x; + center.y = point.y; + center.z = point.z; + } else { + dis = Math.sqrt( (point.x - center.x)*(point.x - center.x) + + (point.y - center.y)*(point.y - center.y) + + (point.z - center.z)*(point.z - center.z) ); + + if( dis > radius) { + radius = (dis + radius)*.5; + oldc_to_new_c = dis - radius; + center.x = (radius*center.x + oldc_to_new_c*point.x)/dis; + center.y = (radius*center.y + oldc_to_new_c*point.y)/dis; + center.z = (radius*center.z + oldc_to_new_c*point.z)/dis; + } + } + + updateBoundsStates(); + } + + /** + * Combines this bounding sphere with an array of points. + * @param points an array of 3D points in space + */ + public void combine(Point3d[] points) { + int i; + double dis,dis_sq,rad_sq,oldc_to_new_c; + + if( boundsIsInfinite) { + return; + } + + if( boundsIsEmpty ) { + center.x = points[0].x; + center.y = points[0].y; + center.z = points[0].z; + radius = 0.0; + } + + for(i=0;i<points.length;i++) { + rad_sq = radius * radius; + dis_sq = (points[i].x - center.x)*(points[i].x - center.x) + + (points[i].y - center.y)*(points[i].y - center.y) + + (points[i].z - center.z)*(points[i].z - center.z); + + // change sphere so one side passes through the point and + // other passes through the old sphere + if( dis_sq > rad_sq) { + dis = Math.sqrt( dis_sq); + radius = (radius + dis)*.5; + oldc_to_new_c = dis - radius; + center.x = (radius*center.x + oldc_to_new_c*points[i].x)/dis; + center.y = (radius*center.y + oldc_to_new_c*points[i].y)/dis; + center.z = (radius*center.z + oldc_to_new_c*points[i].z)/dis; + } + } + + updateBoundsStates(); + } + + + /** + * Modifies the bounding sphere so that it bounds the volume + * generated by transforming the given bounding object. + * @param boundsObject the bounding object to be transformed + * @param matrix a transformation matrix + */ + public void transform( Bounds boundsObject, Transform3D matrix) { + double scale; + + if( boundsObject == null || boundsObject.boundsIsEmpty) { + // Negative volume. + center.x = center.y = center.z = 0.0; + radius = -1.0; + updateBoundsStates(); + return; + } + + if(boundsObject.boundsIsInfinite) { + center.x = center.y = center.z = 0.0; + radius = Double.POSITIVE_INFINITY; + updateBoundsStates(); + return; + } + + if( boundsObject.boundId == BOUNDING_BOX){ + if (tmpBox == null) { + tmpBox = new BoundingBox( (BoundingBox)boundsObject); + } else { + tmpBox.set((BoundingBox)boundsObject); + } + tmpBox.transform(matrix); + this.set(tmpBox); + }else if( boundsObject.boundId == BOUNDING_SPHERE ) { + matrix.transform(((BoundingSphere)boundsObject).center, this.center); + // A very simple radius scale. + scale = matrix.getDistanceScale(); + this.radius = ((BoundingSphere)boundsObject).radius * scale; + if (Double.isNaN(radius)) { + // Negative volume. + center.x = center.y = center.z = 0.0; + radius = -1.0; + updateBoundsStates(); + return; + } + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + if (tmpPolytope == null) { + tmpPolytope = new BoundingPolytope((BoundingPolytope)boundsObject); + } else { + tmpPolytope.set((BoundingPolytope)boundsObject); + } + tmpPolytope.transform(matrix); + this.set(tmpPolytope); + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere5")); + } + } + + /** + * Transforms this bounding sphere by the given matrix. + */ + public void transform( Transform3D trans) { + double scale; + + if(boundsIsInfinite) + return; + + trans.transform(center); + scale = trans.getDistanceScale(); + radius = radius * scale; + if (Double.isNaN(radius)) { + // Negative volume. + center.x = center.y = center.z = 0.0; + radius = -1.0; + updateBoundsStates(); + return; + } + } + + /** + * Test for intersection with a ray + * @param origin the starting point of the ray + * @param direction the direction of the ray + * @param position3 a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect(Point3d origin, Vector3d direction, Point4d position ) { + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = origin.x; + position.y = origin.y; + position.z = origin.z; + position.w = 0.0; + return true; + } + + double l2oc,rad2,tca,t2hc,mag,t,invMag; + Vector3d dir = new Vector3d(); // normalized direction of ray + Point3d oc = new Point3d(); // vector from sphere center to ray origin + + oc.x = center.x - origin.x; + oc.y = center.y - origin.y; + oc.z = center.z - origin.z; + + l2oc = oc.x*oc.x + oc.y*oc.y + oc.z*oc.z; // center to origin squared + + rad2 = radius*radius; + if( l2oc < rad2 ){ + // System.out.println("ray origin inside sphere" ); + return true; // ray origin inside sphere + } + + invMag = 1.0/Math.sqrt(direction.x*direction.x + + direction.y*direction.y + + direction.z*direction.z); + dir.x = direction.x*invMag; + dir.y = direction.y*invMag; + dir.z = direction.z*invMag; + tca = oc.x*dir.x + oc.y*dir.y + oc.z*dir.z; + + if( tca <= 0.0 ) { + // System.out.println("ray points away from sphere" ); + return false; // ray points away from sphere + } + + t2hc = rad2 - l2oc + tca*tca; + + if( t2hc > 0.0 ){ + t = tca - Math.sqrt(t2hc); + // System.out.println("ray hits sphere:"+this.toString()+" t="+t+" direction="+dir ); + position.x = origin.x + dir.x*t; + position.y = origin.y + dir.y*t; + position.z = origin.z + dir.z*t; + position.w = t; + return true; // ray hits sphere + }else { + // System.out.println("ray does not hit sphere" ); + return false; + } + + } + + /** + * Test for intersection with a point + * @param point the pick point + * @param position a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect(Point3d point, Point4d position ) { + double x,y,z,dist; + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = point.x; + position.y = point.y; + position.z = point.z; + position.w = 0.0; + return true; + } + + x = point.x - center.x; + y = point.y - center.y; + z = point.z - center.z; + + dist = x*x + y*y + z*z; + if( dist > radius*radius) + return false; + else { + position.x = point.x; + position.y = point.y; + position.z = point.z; + position.w = Math.sqrt(dist); + return true; + } + + } + + /** + * Test for intersection with a segment + * @param start a point defining the start of the line segment + * @param end a point defining the end of the line segment + * @param position a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + boolean intersect( Point3d start, Point3d end, Point4d position ) { + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + position.x = start.x; + position.y = start.y; + position.z = start.z; + position.w = 0.0; + return true; + } + + double l2oc,rad2,tca,t2hc,mag,invMag,t; + Vector3d dir = new Vector3d(); // normalized direction of ray + Point3d oc = new Point3d(); // vector from sphere center to ray origin + Vector3d direction = new Vector3d(); + + oc.x = center.x - start.x; + oc.y = center.y - start.y; + oc.z = center.z - start.z; + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + invMag = 1.0/Math.sqrt( direction.x*direction.x + + direction.y*direction.y + + direction.z*direction.z); + dir.x = direction.x*invMag; + dir.y = direction.y*invMag; + dir.z = direction.z*invMag; + + + l2oc = oc.x*oc.x + oc.y*oc.y + oc.z*oc.z; // center to origin squared + + rad2 = radius*radius; + if( l2oc < rad2 ){ + // System.out.println("ray origin inside sphere" ); + return true; // ray origin inside sphere + } + + tca = oc.x*dir.x + oc.y*dir.y + oc.z*dir.z; + + if( tca <= 0.0 ) { + // System.out.println("ray points away from sphere" ); + return false; // ray points away from sphere + } + + t2hc = rad2 - l2oc + tca*tca; + + if( t2hc > 0.0 ){ + t = tca - Math.sqrt(t2hc); + if( t*t <= ((end.x-start.x)*(end.x-start.x)+ + (end.y-start.y)*(end.y-start.y)+ + (end.z-start.z)*(end.z-start.z))){ + + position.x = start.x + dir.x*t; + position.y = start.y + dir.x*t; + position.z = start.z + dir.x*t; + position.w = t; + return true; // segment hits sphere + } + } + return false; + } + + /** + * Test for intersection with a ray. + * @param origin the starting point of the ray + * @param direction the direction of the ray + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Point3d origin, Vector3d direction ) { + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + return true; + } + + double l2oc,rad2,tca,t2hc,mag; + Vector3d dir = new Vector3d(); // normalized direction of ray + Point3d oc = new Point3d(); // vector from sphere center to ray origin + + oc.x = center.x - origin.x; + oc.y = center.y - origin.y; + oc.z = center.z - origin.z; + + l2oc = oc.x*oc.x + oc.y*oc.y + oc.z*oc.z; // center to origin squared + + rad2 = radius*radius; + if( l2oc < rad2 ){ + // System.out.println("ray origin inside sphere" ); + return true; // ray origin inside sphere + } + + mag = Math.sqrt(direction.x*direction.x + + direction.y*direction.y + + direction.z*direction.z); + dir.x = direction.x/mag; + dir.y = direction.y/mag; + dir.z = direction.z/mag; + tca = oc.x*dir.x + oc.y*dir.y + oc.z*dir.z; + + if( tca <= 0.0 ) { + // System.out.println("ray points away from sphere" ); + return false; // ray points away from sphere + } + + t2hc = rad2 - l2oc + tca*tca; + + if( t2hc > 0.0 ){ + // System.out.println("ray hits sphere" ); + return true; // ray hits sphere + }else { + // System.out.println("ray does not hit sphere" ); + return false; + } + } + + + /** + * Returns the position of the intersect point if the ray intersects with + * the sphere. + * + */ + boolean intersect(Point3d origin, Vector3d direction, Point3d intersectPoint ) { + + if( boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite ) { + intersectPoint.x = origin.x; + intersectPoint.y = origin.y; + intersectPoint.z = origin.z; + return true; + } + + double l2oc,rad2,tca,t2hc,mag,t; + Point3d dir = new Point3d(); // normalized direction of ray + Point3d oc = new Point3d(); // vector from sphere center to ray origin + + oc.x = center.x - origin.x; // TODO check if this method is still needed + oc.y = center.y - origin.y; + oc.z = center.z - origin.z; + + l2oc = oc.x*oc.x + oc.y*oc.y + oc.z*oc.z; // center to origin squared + + rad2 = radius*radius; + if( l2oc < rad2 ){ + // System.out.println("ray origin inside sphere" ); + return true; // ray origin inside sphere + } + + mag = Math.sqrt(direction.x*direction.x + + direction.y*direction.y + + direction.z*direction.z); + dir.x = direction.x/mag; + dir.y = direction.y/mag; + dir.z = direction.z/mag; + tca = oc.x*dir.x + oc.y*dir.y + oc.z*dir.z; + + if( tca <= 0.0 ) { + // System.out.println("ray points away from sphere" ); + return false; // ray points away from sphere + } + + t2hc = rad2 - l2oc + tca*tca; + + if( t2hc > 0.0 ){ + t = tca - Math.sqrt(t2hc); + intersectPoint.x = origin.x + direction.x*t; + intersectPoint.y = origin.y + direction.y*t; + intersectPoint.z = origin.z + direction.z*t; + // System.out.println("ray hits sphere" ); + return true; // ray hits sphere + }else { + // System.out.println("ray does not hit sphere" ); + return false; + } + } + + + /** + * Test for intersection with a point. + * @param point a point defining a position in 3-space + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Point3d point ) { + double x,y,z,dist; + + if( boundsIsEmpty ) { + return false; + } + if( boundsIsInfinite ) { + return true; + } + + x = point.x - center.x; + y = point.y - center.y; + z = point.z - center.z; + + dist = x*x + y*y + z*z; + if( dist > radius*radius) + return false; + else + return true; + + } + + /** + * Tests whether the bounding sphere is empty. A bounding sphere is + * empty if it is null (either by construction or as the result of + * a null intersection) or if its volume is negative. A bounding sphere + * with a volume of zero is <i>not</i> empty. + * @return true if the bounding sphere is empty; + * otherwise, it returns false + */ + public boolean isEmpty() { + return boundsIsEmpty; + } + + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @return true or false indicating if an intersection occured + */ + boolean intersect(Bounds boundsObject, Point4d position) { + return intersect(boundsObject); + } + + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds boundsObject) { + double distsq, radsq; + BoundingSphere sphere; + boolean intersect; + + if( boundsObject == null ) { + return false; + } + + if( boundsIsEmpty || boundsObject.boundsIsEmpty ) { + return false; + } + + if( boundsIsInfinite || boundsObject.boundsIsInfinite ) { + return true; + } + + if( boundsObject.boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObject; + double dis = 0.0; + double rad_sq = radius*radius; + + // find the corner closest to the center of sphere + + if( center.x < box.lower.x ) + dis = (center.x-box.lower.x)*(center.x-box.lower.x); + else + if( center.x > box.upper.x ) + dis = (center.x-box.upper.x)*(center.x-box.upper.x); + + if( center.y < box.lower.y ) + dis += (center.y-box.lower.y)*(center.y-box.lower.y); + else + if( center.y > box.upper.y ) + dis += (center.y-box.upper.y)*(center.y-box.upper.y); + + if( center.z < box.lower.z ) + dis += (center.z-box.lower.z)*(center.z-box.lower.z); + else + if( center.z > box.upper.z ) + dis += (center.z-box.upper.z)*(center.z-box.upper.z); + + return ( dis <= rad_sq ); + } else if( boundsObject.boundId == BOUNDING_SPHERE ) { + sphere = (BoundingSphere)boundsObject; + radsq = radius + sphere.radius; + radsq *= radsq; + distsq = center.distanceSquared(sphere.center); + return (distsq <= radsq); + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + return intersect_ptope_sphere( (BoundingPolytope)boundsObject, this); + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere6")); + } + } + + /** + * Test for intersection with another bounds object. + * @param boundsObjects an array of bounding objects + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds[] boundsObjects) { + double distsq, radsq; + BoundingSphere sphere; + int i; + + if( boundsObjects == null || boundsObjects.length <= 0 ) { + return false; + } + + if( boundsIsEmpty ) { + return false; + } + + for(i = 0; i < boundsObjects.length; i++){ + if( boundsObjects[i] == null || boundsObjects[i].boundsIsEmpty); + else if( boundsIsInfinite || boundsObjects[i].boundsIsInfinite ) { + return true; // We're done here. + } else if( boundsObjects[i].boundId == BOUNDING_BOX){ + if( this.intersect( boundsObjects[i])) return true; + } else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + sphere = (BoundingSphere)boundsObjects[i]; + radsq = radius + sphere.radius; + radsq *= radsq; + distsq = center.distanceSquared(sphere.center); + if (distsq <= radsq) { + return true; + } + } else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + if( this.intersect( boundsObjects[i])) return true; + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere7")); + } + } + + return false; + + } + + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @param newBoundSphere the new bounding sphere which is the intersection of + * the boundsObject and this BoundingSphere + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds boundsObject, BoundingSphere newBoundSphere) { + + if((boundsObject == null ) || boundsIsEmpty || boundsObject.boundsIsEmpty) { + // Negative volume. + newBoundSphere.center.x = newBoundSphere.center.y = + newBoundSphere.center.z = 0.0; + newBoundSphere.radius = -1.0; + newBoundSphere.updateBoundsStates(); + return false; + } + + if(boundsIsInfinite && (!boundsObject.boundsIsInfinite)) { + newBoundSphere.set(boundsObject); + return true; + } + else if((!boundsIsInfinite) && boundsObject.boundsIsInfinite) { + newBoundSphere.set(this); + return true; + } + else if(boundsIsInfinite && boundsObject.boundsIsInfinite) { + newBoundSphere.set(this); + return true; + } else if(boundsObject.boundId == BOUNDING_BOX){ + BoundingBox tbox = new BoundingBox(); + BoundingBox box = (BoundingBox)boundsObject; + if( this.intersect( box) ){ + BoundingBox sbox = new BoundingBox( this ); // convert sphere to box + sbox.intersect(box, tbox); // insersect two boxes + newBoundSphere.set( tbox ); // set sphere to the intersection of 2 boxes + return true; + } else { + // Negative volume. + newBoundSphere.center.x = newBoundSphere.center.y = + newBoundSphere.center.z = 0.0; + newBoundSphere.radius = -1.0; + newBoundSphere.updateBoundsStates(); + return false; + } + } else if( boundsObject.boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObject; + double dis,t,d2; + boolean status; + dis = Math.sqrt( (center.x-sphere.center.x)*(center.x-sphere.center.x) + + (center.y-sphere.center.y)*(center.y-sphere.center.y) + + (center.z-sphere.center.z)*(center.z-sphere.center.z) ); + if ( dis > radius+sphere.radius) { + // Negative volume. + newBoundSphere.center.x = newBoundSphere.center.y = + newBoundSphere.center.z = 0.0; + newBoundSphere.radius = -1.0; + status = false; + } else if( dis+radius <= sphere.radius ) { // this sphere is contained within boundsObject + newBoundSphere.center.x = center.x; + newBoundSphere.center.y = center.y; + newBoundSphere.center.z = center.z; + newBoundSphere.radius = radius; + status = true; + } else if( dis+sphere.radius <= radius ) { // boundsObject is containted within this sphere + newBoundSphere.center.x = sphere.center.x; + newBoundSphere.center.y = sphere.center.y; + newBoundSphere.center.z = sphere.center.z; + newBoundSphere.radius = sphere.radius; + status = true; + } else { + // distance from this center to center of overlapped volume + d2 = (dis*dis + radius*radius - sphere.radius*sphere.radius)/(2.0*dis); + newBoundSphere.radius = Math.sqrt( radius*radius - d2*d2); + t = d2/dis; + newBoundSphere.center.x = center.x + (sphere.center.x - center.x)*t; + newBoundSphere.center.y = center.y + (sphere.center.y - center.y)*t; + newBoundSphere.center.z = center.z + (sphere.center.z - center.z)*t; + status = true; + } + + newBoundSphere.updateBoundsStates(); + return status; + + } else if(boundsObject.boundId == BOUNDING_POLYTOPE) { + BoundingBox tbox = new BoundingBox(); + + BoundingPolytope polytope = (BoundingPolytope)boundsObject; + if( this.intersect( polytope) ){ + BoundingBox sbox = new BoundingBox( this ); // convert sphere to box + BoundingBox pbox = new BoundingBox( polytope ); // convert polytope to box + sbox.intersect(pbox,tbox); // insersect two boxes + newBoundSphere.set( tbox ); // set sphere to the intersection of 2 boxesf + return true; + } else { + // Negative volume. + newBoundSphere.center.x = newBoundSphere.center.y = + newBoundSphere.center.z = 0.0; + newBoundSphere.radius = -1.0; + newBoundSphere.updateBoundsStates(); + return false; + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere8")); + } + } + + /** + * Test for intersection with an array of bounds objects. + * @param boundsObjects an array of bounds objects + * @param newBoundSphere the new bounding sphere which is the intersection of + * the boundsObject and this BoundingSphere + * @return true or false indicating if an intersection occured + */ + public boolean intersect(Bounds[] boundsObjects, BoundingSphere newBoundSphere) { + + if( boundsObjects == null || boundsObjects.length <= 0 || boundsIsEmpty ) { + // Negative volume. + newBoundSphere.center.x = newBoundSphere.center.y = + newBoundSphere.center.z = 0.0; + newBoundSphere.radius = -1.0; + newBoundSphere.updateBoundsStates(); + return false; + } + + int i=0; + + // find first non null bounds object + while( boundsObjects[i] == null && i < boundsObjects.length) { + i++; + } + + if( i >= boundsObjects.length ) { // all bounds objects were empty + // Negative volume. + newBoundSphere.center.x = newBoundSphere.center.y = + newBoundSphere.center.z = 0.0; + newBoundSphere.radius = -1.0; + newBoundSphere.updateBoundsStates(); + return false; + } + + boolean status = false; + double newRadius; + Point3d newCenter = new Point3d(); + BoundingBox tbox = new BoundingBox(); + + for(i=0;i<boundsObjects.length;i++) { + if( boundsObjects[i] == null || boundsObjects[i].boundsIsEmpty) ; + else if( boundsObjects[i].boundId == BOUNDING_BOX) { + BoundingBox box = (BoundingBox)boundsObjects[i]; + if( this.intersect( box) ){ + BoundingBox sbox = new BoundingBox( this ); // convert sphere to box + sbox.intersect(box,tbox); // insersect two boxes + if( status ) { + newBoundSphere.combine( tbox ); + } else { + newBoundSphere.set( tbox ); // set sphere to the intersection of 2 boxesf + status = true; + } + } + } else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + double dis,t,d2; + dis = Math.sqrt( (center.x-sphere.center.x)*(center.x-sphere.center.x) + + (center.y-sphere.center.y)*(center.y-sphere.center.y) + + (center.z-sphere.center.z)*(center.z-sphere.center.z) ); + if ( dis > radius+sphere.radius) { + } else if( dis+radius <= sphere.radius ) { // this sphere is contained within boundsObject + if( status ) { + newBoundSphere.combine( this ); + } else { + newBoundSphere.center.x = center.x; + newBoundSphere.center.y = center.y; + newBoundSphere.center.z = center.z; + newBoundSphere.radius = radius; + status = true; + newBoundSphere.updateBoundsStates(); + } + } else if( dis+sphere.radius <= radius ) { // boundsObject is containted within this sphere + if( status ) { + newBoundSphere.combine( sphere ); + } else { + newBoundSphere.center.x = center.x; + newBoundSphere.center.y = center.y; + newBoundSphere.center.z = center.z; + newBoundSphere.radius = sphere.radius; + status = true; + newBoundSphere.updateBoundsStates(); + } + } else { + // distance from this center to center of overlapped volume + d2 = (dis*dis + radius*radius - sphere.radius*sphere.radius)/(2.0*dis); + newRadius = Math.sqrt( radius*radius - d2*d2); + t = d2/dis; + newCenter.x = center.x + (sphere.center.x - center.x)*t; + newCenter.y = center.y + (sphere.center.y - center.y)*t; + newCenter.z = center.z + (sphere.center.z - center.z)*t; + if( status ) { + BoundingSphere newSphere = new BoundingSphere( newCenter, + newRadius ); + newBoundSphere.combine( newSphere ); + } else { + newBoundSphere.setRadius( newRadius ); + newBoundSphere.setCenter( newCenter ); + status = true; + } + } + + } else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i]; + if( this.intersect( polytope) ){ + BoundingBox sbox = new BoundingBox( this ); // convert sphere to box + BoundingBox pbox = new BoundingBox( polytope ); // convert polytope to box + sbox.intersect(pbox, tbox); // insersect two boxes + if( status ) { + newBoundSphere.combine( tbox ); + } else { + newBoundSphere.set( tbox ); // set sphere to the intersection of 2 boxesf + status = true; + } + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere9")); + } + } + if( status == false) { + // Negative volume. + newBoundSphere.center.x = newBoundSphere.center.y = + newBoundSphere.center.z = 0.0; + newBoundSphere.radius = -1.0; + newBoundSphere.updateBoundsStates(); + } + return status; + } + + /** + * Finds closest bounding object that intersects this bounding sphere. + * @param boundsObjects an array of bounds objects + * @return closest bounding object + */ + public Bounds closestIntersection( Bounds[] boundsObjects) { + + if( boundsObjects == null || boundsObjects.length <= 0 ) { + return null; + } + + if( boundsIsEmpty ) { + return null; + } + + double dis,far_dis,pdist,x,y,z,rad_sq; + double cenX = 0.0, cenY = 0.0, cenZ = 0.0; + boolean contains = false; + boolean inside; + boolean intersect = false; + double smallest_distance = Double.MAX_VALUE; + int i,j,index=0; + + + for(i = 0; i < boundsObjects.length; i++){ + if( boundsObjects[i] == null ) ; + + else if( this.intersect( boundsObjects[i])) { + intersect = true; + if(boundsObjects[i].boundId == BOUNDING_BOX){ + BoundingBox box = (BoundingBox)boundsObjects[i]; + cenX = (box.upper.x+box.lower.x)/2.0; + cenY = (box.upper.y+box.lower.y)/2.0; + cenZ = (box.upper.z+box.lower.z)/2.0; + dis = Math.sqrt( (center.x-cenX)*(center.x-cenX) + + (center.y-cenY)*(center.y-cenY) + + (center.z-cenZ)*(center.z-cenZ) ); + if( (center.x-box.lower.x)*(center.x-box.lower.x) > + (center.x-box.upper.x)*(center.x-box.upper.x) ) + far_dis = (center.x-box.lower.x)*(center.x-box.lower.x); + else + far_dis = (center.x-box.upper.x)*(center.x-box.upper.x); + + if( (center.y-box.lower.y)*(center.y-box.lower.y) > + (center.y-box.upper.y)*(center.y-box.upper.y) ) + far_dis += (center.y-box.lower.y)*(center.y-box.lower.y); + else + far_dis += (center.y-box.upper.y)*(center.y-box.upper.y); + + if( (center.z-box.lower.z)*(center.z-box.lower.z) > + (center.z-box.upper.z)*(center.z-box.upper.z) ) + far_dis += (center.z-box.lower.z)*(center.z-box.lower.z); + else + far_dis += (center.z-box.upper.z)*(center.z-box.upper.z); + + rad_sq = radius * radius; + if( far_dis <= rad_sq ) { // contains box + if( !contains ){ // initialize smallest_distance for the first containment + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if( boundsObjects[i].boundId == BOUNDING_SPHERE ) { + BoundingSphere sphere = (BoundingSphere)boundsObjects[i]; + dis = Math.sqrt( (center.x-sphere.center.x)*(center.x-sphere.center.x) + + (center.y-sphere.center.y)*(center.y-sphere.center.y) + + (center.z-sphere.center.z)*(center.z-sphere.center.z) ); + if( (dis+sphere.radius) <= radius) { // contains the sphere + if( !contains ){ // initialize smallest_distance for the first containment + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + + } else if(boundsObjects[i].boundId == BOUNDING_POLYTOPE) { + BoundingPolytope polytope = (BoundingPolytope)boundsObjects[i]; + dis = Math.sqrt( (center.x-polytope.centroid.x)*(center.x-polytope.centroid.x) + + (center.y-polytope.centroid.y)*(center.y-polytope.centroid.y) + + (center.z-polytope.centroid.z)*(center.z-polytope.centroid.z) ); + inside = true; + for(j=0;j<polytope.nVerts;j++) { + x = polytope.verts[j].x - center.x; + y = polytope.verts[j].y - center.y; + z = polytope.verts[j].z - center.z; + + pdist = x*x + y*y + z*z; + if( pdist > radius*radius) + inside=false; + } + if( inside ) { + if( !contains ){ // initialize smallest_distance for the first containment + index = i; + smallest_distance = dis; + contains = true; + } else{ + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + } else if (!contains) { + if( dis < smallest_distance){ + index = i; + smallest_distance = dis; + } + } + + } else { + throw new IllegalArgumentException(J3dI18N.getString("BoundingSphere10")); + } + } + } + + if ( intersect ) + return boundsObjects[index]; + else + return null; + + } + + + /** + * Intersects this bounding sphere with preprocessed frustum. + * @return true if the bounding sphere and frustum intersect. + */ + boolean intersect(CachedFrustum frustum) { + int i; + double dist; + + if( boundsIsEmpty ) { + return false; + } + + if(boundsIsInfinite) + return true; + + for (i=0; i<6; i++) { + dist = frustum.clipPlanes[i].x*center.x + frustum.clipPlanes[i].y*center.y + + frustum.clipPlanes[i].z*center.z + frustum.clipPlanes[i].w; + if (dist < 0.0 && (dist + radius) < 0.0) { + return(false); + } + } + return true; + } + + /** + * This intersects this bounding sphere with 6 frustum plane equations + * @return returns true if the bounding sphere and frustum intersect. + */ + boolean intersect(Vector4d[] planes) { + int i; + double dist; + + if( boundsIsEmpty ) { + return false; + } + + if(boundsIsInfinite) + return true; + + for (i=0; i<6; i++) { + dist = planes[i].x*center.x + planes[i].y*center.y + + planes[i].z*center.z + planes[i].w; + if (dist < 0.0 && (dist + radius) < 0.0) { + //System.out.println("Tossing " + i + " " + dist + " " + radius); + return(false); + } + } + return true; + } + + /** + * Returns a string representation of this class. + */ + public String toString() { + return new String( "Center="+center+" Radius="+radius); + } + + private void updateBoundsStates() { + + if (checkBoundsIsNaN()) { + boundsIsEmpty = true; + boundsIsInfinite = false; + return; + } + + if(radius == Double.POSITIVE_INFINITY) { + boundsIsEmpty = false; + boundsIsInfinite = true; + } + else { + boundsIsInfinite = false; + if( radius < 0.0 ) { + boundsIsEmpty = true; + } else { + boundsIsEmpty = false; + } + } + } + + Point3d getCenter() { + return center; + } + + /** + * if the passed the "region" is same type as this object + * then do a copy, otherwise clone the Bounds and + * return + */ + Bounds copy(Bounds r) { + if (r != null && this.boundId == r.boundId) { + BoundingSphere region = (BoundingSphere)r; + region.radius = radius; + region.center.x = center.x; + region.center.y = center.y; + region.center.z = center.z; + region.boundsIsEmpty = boundsIsEmpty; + region.boundsIsInfinite = boundsIsInfinite; + return region; + } + else { + return (Bounds) this.clone(); + } + } + + boolean checkBoundsIsNaN() { + if (Double.isNaN(radius+center.x+center.y+center.z)) { + return true; + } + return false; + } + + int getPickType() { + return PickShape.PICKBOUNDINGSPHERE; + } +} + + + + diff --git a/src/classes/share/javax/media/j3d/Bounds.java b/src/classes/share/javax/media/j3d/Bounds.java new file mode 100644 index 0000000..79e9e17 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Bounds.java @@ -0,0 +1,647 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The abstract base class for bounds objects. Bounds objects define + * a convex, closed volume that is used for various intersection and + * culling operations. + */ + +public abstract class Bounds extends Object implements Cloneable { + static final double EPSILON = .000001; + static final boolean debug = false; + + static final int BOUNDING_BOX = 0x1; + static final int BOUNDING_SPHERE = 0x2; + static final int BOUNDING_POLYTOPE = 0x4; + + boolean boundsIsEmpty = false; + boolean boundsIsInfinite = false; + int boundId = 0; + + /** + * Constructs a new Bounds object. + */ + public Bounds() { + } + + + /** + * Makes a copy of a bounds object. + */ + public abstract Object clone(); + + + /** + * Indicates whether the specified <code>bounds</code> object is + * equal to this Bounds object. They are equal if both the + * specified <code>bounds</code> object and this Bounds are + * instances of the same Bounds subclass and all of the data + * members of <code>bounds</code> are equal to the corresponding + * data members in this Bounds. + * @param bounds the object with which the comparison is made. + * @return true if this Bounds object is equal to <code>bounds</code>; + * otherwise false + * + * @since Java 3D 1.2 + */ + public abstract boolean equals(Object bounds); + + + /** + * Returns a hash code for this Bounds object based on the + * data values in this object. Two different Bounds objects of + * the same type with identical data values (i.e., Bounds.equals + * returns true) will return the same hash code. Two Bounds + * objects with different data members may return the same hash code + * value, although this is not likely. + * @return a hash code for this Bounds object. + * + * @since Java 3D 1.2 + */ + public abstract int hashCode(); + + + /** + * Test for intersection with a ray. + * @param origin the starting point of the ray + * @param direction the direction of the ray + * @return true or false indicating if an intersection occured + */ + public abstract boolean intersect( Point3d origin, Vector3d direction ); + + /** + * Test for intersection with a point. + * @param point a point defining a position in 3-space + * @return true or false indicating if an intersection occured + */ + public abstract boolean intersect( Point3d point ); + + /** + * Test for intersection with a ray + * @param origin is a the starting point of the ray + * @param direction is the direction of the ray + * @param position is a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + abstract boolean intersect( Point3d origin, Vector3d direction, Point4d position ); + + /** + * Test for intersection with a point + * @param point is a point defining a position in 3-space + * @param position is a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + abstract boolean intersect( Point3d point, Point4d position); + + /** + * Test for intersection with a segment + * @param start is a point defining the start of the line segment + * @param end is a point defining the end of the line segment + * @param position is a point defining the location of the pick w= distance to pick + * @return true or false indicating if an intersection occured + */ + abstract boolean intersect( Point3d start, Point3d end, Point4d position ); + + /** + * Test for intersection with another bounds object + * + * Test for intersection with another bounds object + * @param boundsObject is another bounds object + * @return true or false indicating if an intersection occured + */ + abstract boolean intersect( Bounds boundsObject, Point4d position ); + + /** + * Test for intersection with another bounds object. + * @param boundsObject another bounds object + * @return true or false indicating if an intersection occurred + */ + public abstract boolean intersect( Bounds boundsObject ); + + /** + * Test for intersection with another bounds object. + * @param boundsObjects an array of bounding objects + * @return true or false indicating if an intersection occured + */ + public abstract boolean intersect( Bounds[] boundsObjects ); + + + /** + * Finds closest bounding object that intersects this bounding object. + * @param boundsObjects an array of bounds objects + * @return closest bounding object + */ + public abstract Bounds closestIntersection( Bounds[] boundsObjects); + + /** + * Returns the center of the bounds + * @return bounds center + */ + abstract Point3d getCenter(); + + /** + * Combines this bounding object with a bounding object so that the + * resulting bounding object encloses the original bounding object and the + * given bounds object. + * @param boundsObject another bounds object + */ + public abstract void combine( Bounds boundsObject ); + + + /** + * Combines this bounding object with an array of bounding objects so that the + * resulting bounding object encloses the original bounding object and the + * given array of bounds object. + * @param boundsObjects an array of bounds objects + */ + public abstract void combine( Bounds[] boundsObjects); + + /** + * Combines this bounding object with a point. + * @param point a 3d point in space + */ + public abstract void combine( Point3d point); + + /** + * Combines this bounding object with an array of points. + * @param points an array of 3d points in space + */ + public abstract void combine( Point3d[] points); + + /** + * Transforms this bounding object by the given matrix. + * @param trans the transformation matrix + */ + public abstract void transform(Transform3D trans); + + /** + * Modifies the bounding object so that it bounds the volume + * generated by transforming the given bounding object. + * @param bounds the bounding object to be transformed + * @param trans the transformation matrix + */ + public abstract void transform( Bounds bounds, Transform3D trans); + + /** + * Tests whether the bounds is empty. A bounds is + * empty if it is null (either by construction or as the result of + * a null intersection) or if its volume is negative. A bounds + * with a volume of zero is <i>not</i> empty. + * @return true if the bounds is empty; otherwise, it returns false + */ + public abstract boolean isEmpty(); + + /** + * Sets the value of this Bounds object. + * @param boundsObject another bounds object. + */ + public abstract void set( Bounds boundsObject); + + + abstract Bounds copy(Bounds region); + + + private void test_point(Vector4d[] planes, Point3d new_point) { + for (int i = 0; i < planes.length; i++){ + double dist = (new_point.x*planes[i].x + new_point.y*planes[i].y + + new_point.z*planes[i].z + planes[i].w ) ; + if (dist > EPSILON ){ + System.out.println("new point is outside of" + + " plane["+i+"] dist = " + dist); + } + } + } + + /** + * computes the closest point from the given point to a set of planes + * (polytope) + * @param g the point + * @param planes array of bounding planes + * @param new_point point on planes closest g + */ + boolean closest_point( Point3d g, Vector4d[] planes, Point3d new_point ) { + + double t,s,dist,w; + boolean converged, inside, firstPoint, firstInside; + int i,count; + double ab,ac,bc,ad,bd,cd,aa,bb,cc; + double b1,b2,b3,d1,d2,d3,y1,y2,y3; + double h11,h12,h13,h22,h23,h33; + double l12,l13,l23; + Point3d n = new Point3d(); + Point3d p = new Point3d(); + Vector3d delta = null; + + // These are temporary until the solve code is working + Matrix3d hMatrix = new Matrix3d(); + + /* + * The algorithm: + * We want to find the point "n", closest to "g", while still within + * the the polytope defined by "planes". We find the solution by + * minimizing the value for a "penalty function"; + * + * f = distance(n,g)^2 + sum for each i: w(distance(n, planes[i])) + * + * Where "w" is a weighting which indicates how much more important + * it is to be close to the planes than it is to be close to "g". + * + * We minimize this function by taking it's derivitive, and then + * solving for the value of n when the derivitive equals 0. + * + * For the 1D case with a single plane (a,b,c,d), x = n.x and g = g.x, + * this looks like: + * + * f(x) = (x - g) ^ 2 + w(ax + d)^2 + * f'(x) = 2x -2g + 2waax + 2wad + * + * (note aa = a^2) setting f'(x) = 0 gives: + * + * (1 + waa)x = g - wad + * + * Note that the solution is just outside the plane [a, d]. With the + * correct choice of w, this should be inside of the EPSILON tolerance + * outside the planes. + * + * Extending to 3D gives the matrix solution: + * + * | (1 + waa) wab wac | + * H = | wab (1 + wbb) wbc | + * | wac wbc (1 + wcc) | + * + * b = [g.x - wad, g.y - wbd, g.z - wcd] + * + * H * n = b + * + * n = b * H.inverse() + * + * The implementation speeds this process up by recognizing that + * H is symmetric, so that it can be decomposed into three matrices: + * + * H = L * D * L.transpose() + * + * 1.0 0.0 0.0 d1 0.0 0.0 + * L = l12 1.0 0.0 D = 0.0 d2 0.0 + * l13 l23 1.0 0.0 0.0 d3 + * + * n can then be derived by back-substitution, where the original + * problem is decomposed as: + * + * H * n = b + * L * D * L.transpose() * n = b + * L * D * y = b; L.transpose() * n = y + * + * We can then multiply out the terms of L * D and solve for y, and + * then use y to solve for n. + */ + + w=100.0 / EPSILON; // must be large enough to ensure that solution + // is within EPSILON of planes + + count = 0; + p.set(g); + + if (debug) { + System.out.println("closest_point():\nincoming g="+" "+g.x+" "+g.y+ + " "+g.z); + } + + converged = false; + firstPoint = true; + firstInside = false; + + Vector4d pln; + + while( !converged ) { + if (debug) { + System.out.println("start: p="+" "+p.x+" "+p.y+" "+p.z); + } + + // test the current point against the planes, for each + // plane that is violated, add it's contribution to the + // penalty function + inside = true; + aa=0.0; bb=0.0; cc=0.0; + ab=0.0; ac=0.0; bc=0.0; ad=0.0; bd=0.0; cd=0.0; + for(i = 0; i < planes.length; i++){ + pln = planes[i]; + dist = (p.x*pln.x + p.y*pln.y + + p.z*pln.z + pln.w ) ; + // if point is outside or within EPSILON of the boundary, add + // the plane to the penalty matrix. We do this even if the + // point is already inside the polytope to prevent numerical + // instablity in cases where the point is just outside the + // boundary of several planes of the polytope + if (dist > -EPSILON ){ + aa = aa + pln.x * pln.x; + bb = bb + pln.y * pln.y; + cc = cc + pln.z * pln.z; + ab = ab + pln.x * pln.y; + ac = ac + pln.x * pln.z; + bc = bc + pln.y * pln.z; + ad = ad + pln.x * pln.w; + bd = bd + pln.y * pln.w; + cd = cd + pln.z * pln.w; + } + // If the point is inside if dist is <= EPSILON + if (dist > EPSILON ){ + inside = false; + if (debug) { + System.out.println("point outside plane["+i+"]=("+ + pln.x+ ","+pln.y+",\n\t"+pln.z+ + ","+ pln.w+")\ndist = " + dist); + } + } + } + // see if we are done + if (inside) { + if (debug) { + System.out.println("p is inside"); + } + if (firstPoint) { + firstInside = true; + } + new_point.set(p); + converged = true; + } else { // solve for a closer point + firstPoint = false; + + // this is the upper right corner of H, which is all we + // need to do the decomposition since the matrix is symetric + h11 = 1.0 + aa * w; + h12 = ab * w; + h13 = ac * w; + h22 = 1.0 + bb * w; + h23 = bc * w; + h33 = 1.0 + cc * w; + + if (debug) { + System.out.println(" hessin= "); + System.out.println(h11+" "+h12+" "+h13); + System.out.println(" "+h22+" "+h23); + System.out.println(" "+h33); + } + + // these are the constant terms + b1 = g.x - w * ad; + b2 = g.y - w * bd; + b3 = g.z - w * cd; + + if (debug) { + System.out.println(" b1,b2,b3 = "+b1+" "+b2+" " +b3); + } + + // solve, d1, d2, d3 actually 1/dx, which is more useful + d1 = 1/h11; + l12 = d1 * h12; + l13 = d1 * h13; + s = h22-l12*h12; + d2 = 1/s; + t = h23-h12*l13; + l23 = d2 * t; + d3 = 1/(h33 - h13*l13 - t*l23); + + if (debug) { + System.out.println(" l12,l13,l23 "+l12+" "+l13+" "+l23); + System.out.println(" d1,d2,d3 "+ d1+" "+d2+" "+d3); + } + + // we have L and D, now solve for y + y1 = d1 * b1; + y2 = d2 * (b2 - h12*y1); + y3 = d3 * (b3 - h13*y1 - t*y2); + + if (debug) { + System.out.println(" y1,y2,y3 = "+y1+" "+y2+" "+y3); + } + + // we have y, solve for n + n.z = y3; + n.y = (y2 - l23*n.z); + n.x = (y1 - l13*n.z - l12*n.y); + + if (debug) { + System.out.println("new point = " + n.x+" " + n.y+" " + + n.z); + test_point(planes, n); + + if (delta == null) delta = new Vector3d(); + delta.sub(n, p); + delta.normalize(); + System.out.println("p->n direction: " + delta); + + // check using the the javax.vecmath routine + hMatrix.m00 = h11; + hMatrix.m01 = h12; + hMatrix.m02 = h13; + hMatrix.m10 = h12; // h21 = h12 + hMatrix.m11 = h22; + hMatrix.m12 = h23; + hMatrix.m20 = h13; // h31 = h13 + hMatrix.m21 = h23; // h32 = h22 + hMatrix.m22 = h33; + hMatrix.invert(); + Point3d check = new Point3d(b1, b2, b3); + hMatrix.transform(check); + + System.out.println("check point = " + check.x+" " + + check.y+" " + check.z); + } + + // see if we have converged yet + dist = (p.x-n.x)*(p.x-n.x) + (p.y-n.y)*(p.y-n.y) + + (p.z-n.z)*(p.z-n.z); + + if (debug) { + System.out.println("p->n distance =" + dist ); + } + + if( dist < EPSILON) { // close enough + converged = true; + new_point.set(n); + } else { + p.set(n); + count++; + if(count > 4 ){ // watch for cycling between two minimums + new_point.set(n); + converged = true; + } + } + } + } + if (debug) { + System.out.println("returning pnt ("+new_point.x+" "+ + new_point.y+" "+new_point.z+")"); + + if(firstInside) System.out.println("input point inside polytope "); + } + return firstInside; + } + + boolean intersect_ptope_sphere( BoundingPolytope polyTope, + BoundingSphere sphere) { + Point3d p = new Point3d(); + boolean inside; + + + if (debug) { + System.out.println("ptope_sphere intersect sphere ="+sphere); + } + inside = closest_point( sphere.center, polyTope.planes, p ); + if (debug) { + System.out.println("ptope sphere intersect point ="+p); + } + if (!inside){ + // if distance between polytope and sphere center is greater than + // radius then no intersection + if (p.distanceSquared( sphere.center) > + sphere.radius*sphere.radius){ + if (debug) { + System.out.println("ptope_sphere returns false"); + } + return false; + } else { + if (debug) { + System.out.println("ptope_sphere returns true"); + } + return true; + } + } else { + if (debug) { + System.out.println("ptope_sphere returns true"); + } + return true; + } + } + + boolean intersect_ptope_abox( BoundingPolytope polyTope, BoundingBox box) { + Vector4d planes[] = new Vector4d[6]; + + if (debug) { + System.out.println("ptope_abox, box = " + box); + } + planes[0] = new Vector4d( -1.0, 0.0, 0.0, box.lower.x); + planes[1] = new Vector4d( 1.0, 0.0, 0.0,-box.upper.x); + planes[2] = new Vector4d( 0.0,-1.0, 0.0, box.lower.y); + planes[3] = new Vector4d( 0.0, 1.0, 0.0,-box.upper.y); + planes[4] = new Vector4d( 0.0, 0.0,-1.0, box.lower.z); + planes[5] = new Vector4d( 0.0, 0.0, 1.0,-box.upper.z); + + + BoundingPolytope pbox = new BoundingPolytope( planes); + + boolean result = intersect_ptope_ptope( polyTope, pbox ); + if (debug) { + System.out.println("ptope_abox returns " + result); + } + return(result); + } + + + boolean intersect_ptope_ptope( BoundingPolytope poly1, + BoundingPolytope poly2) { + boolean intersect; + Point3d p = new Point3d(); + Point3d g = new Point3d(); + Point3d gnew = new Point3d(); + Point3d pnew = new Point3d(); + + intersect = false; + + p.x = 0.0; + p.y = 0.0; + p.z = 0.0; + + // start from an arbitrary point on poly1 + closest_point( p, poly1.planes, g); + + // get the closest points on each polytope + if (debug) { + System.out.println("ptope_ptope: first g = "+g); + } + intersect = closest_point( g, poly2.planes, p); + + if (intersect) { + return true; + } + + if (debug) { + System.out.println("first p = "+p+"\n"); + } + intersect = closest_point( p, poly1.planes, gnew); + if (debug) { + System.out.println("gnew = "+gnew+" intersect="+intersect); + } + + // loop until the closest points on the two polytopes are not changing + + double prevDist = p.distanceSquared(g); + double dist; + + while( !intersect ) { + + dist = p.distanceSquared(gnew); + + if (dist < prevDist) { + g.set(gnew); + intersect = closest_point( g, poly2.planes, pnew ); + if (debug) { + System.out.println("pnew = "+pnew+" intersect="+intersect); + } + } else { + g.set(gnew); + break; + } + prevDist = dist; + dist = pnew.distanceSquared(g); + + if (dist < prevDist) { + p.set(pnew); + if( !intersect ) { + intersect = closest_point( p, poly1.planes, gnew ); + if (debug) { + System.out.println("gnew = "+gnew+" intersect="+ + intersect); + } + } + } else { + p.set(pnew); + break; + } + prevDist = dist; + } + + if (debug) { + System.out.println("gnew="+" "+gnew.x+" "+gnew.y+" "+gnew.z); + System.out.println("pnew="+" "+pnew.x+" "+pnew.y+" "+pnew.z); + } + return intersect; + } + + + synchronized void setWithLock(Bounds b) { + this.set(b); + } + + synchronized void getWithLock(Bounds b) { + b.set(this); + } + + // Return one of Pick Bounds type define in PickShape + abstract int getPickType(); +} diff --git a/src/classes/share/javax/media/j3d/BranchGroup.java b/src/classes/share/javax/media/j3d/BranchGroup.java new file mode 100644 index 0000000..5083a10 --- /dev/null +++ b/src/classes/share/javax/media/j3d/BranchGroup.java @@ -0,0 +1,207 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The BranchGroup serves as a pointer to the root of a + * scene graph branch; BranchGroup objects are the only objects that + * can be inserted into a Locale's set of objects. A subgraph, rooted + * by a BranchGroup node can be thought of as a compile unit. The + * following things may be done with BranchGroup: + * <P><UL> + * <LI>A BranchGroup may be compiled by calling its compile method. This causes the + * entire subgraph to be compiled. If any BranchGroup nodes are contained within the + * subgraph, they are compiled as well (along with their descendants).</LI> + * <p> + * <LI>A BranchGroup may be inserted into a virtual universe by attaching it to a + * Locale. The entire subgraph is then said to be live.</LI> + * <p> + * <LI>A BranchGroup that is contained within another subgraph may be reparented or + * detached at run time if the appropriate capabilities are set.</LI> + * </UL> + * Note that that if a BranchGroup is included in another subgraph, as a child of + * some other group node, it may not be attached to a Locale. + */ + +public class BranchGroup extends Group { + + /** + * For BranchGroup nodes, specifies that this BranchGroup allows detaching + * from its parent. + */ + public static final int + ALLOW_DETACH = CapabilityBits.BRANCH_GROUP_ALLOW_DETACH; + + /** + * Constructs and initializes a new BranchGroup node object. + */ + public BranchGroup() { + } + + /** + * Creates the retained mode BranchGroupRetained object that this + * BranchGroup component object will point to. + */ + void createRetained() { + this.retained = new BranchGroupRetained(); + this.retained.setSource(this); + } + + + /** + * Compiles the source BranchGroup associated with this object and + * creates and caches a compiled scene graph. + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + * @exception RestrictedAccessException if the method is called + * when this object is part of a live scene graph. + */ + public void compile() { + if (isLive()) { + throw new RestrictedAccessException( + J3dI18N.getString("BranchGroup0")); + } + + if (isCompiled() == false) { + // will throw SceneGraphCycleException if there is a cycle + // in the scene graph + checkForCycle(); + + ((BranchGroupRetained)this.retained).compile(); + } + } + + /** + * Detaches this BranchGroup from its parent. + */ + public void detach() { + Group parent; + + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_DETACH)) + throw new CapabilityNotSetException(J3dI18N.getString("BranchGroup1")); + + if (((BranchGroupRetained)this.retained).parent != null) { + parent = (Group)((BranchGroupRetained)this.retained).parent.source; + if(!parent.getCapability(Group.ALLOW_CHILDREN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("BranchGroup2")); + } + } + + ((BranchGroupRetained)this.retained).detach(); + } + + /** + * Returns an array referencing all the items that are pickable below this + * <code>BranchGroup</code> that intersect with PickShape. + * The resultant array is unordered. + * + * @param pickShape the PickShape object + * + * @see SceneGraphPath + * @see Locale#pickAll + * @see PickShape + * @exception IllegalStateException if BranchGroup is not live. + * + */ + public SceneGraphPath[] pickAll( PickShape pickShape ) { + if(isLive()==false) + throw new IllegalStateException(J3dI18N.getString("BranchGroup3")); + + return Picking.pickAll( this, pickShape ); + } + + /** + * Returns a sorted array of references to all the Pickable items that + * intersect with the pickShape. Element [0] references the item closest + * to <i>origin</i> of PickShape successive array elements are further + * from the <i>origin</i> + * + * Note: If pickShape is of type PickBounds, the resulting array + * is unordered. + * @param pickShape the PickShape object + * + * @see SceneGraphPath + * @see Locale#pickAllSorted + * @see PickShape + * @exception IllegalStateException if BranchGroup is not live. + * + */ + public SceneGraphPath[] pickAllSorted( PickShape pickShape ) { + if(isLive()==false) + throw new IllegalStateException(J3dI18N.getString("BranchGroup3")); + + return Picking.pickAllSorted( this, pickShape ); + } + + /** + * Returns a SceneGraphPath that references the pickable item + * closest to the origin of <code>pickShape</code>. + * + * Note: If pickShape is of type PickBounds, the return is any pickable node + * below this BranchGroup. + * @param pickShape the PickShape object + * + * @see SceneGraphPath + * @see Locale#pickClosest + * @see PickShape + * @exception IllegalStateException if BranchGroup is not live. + * + */ + public SceneGraphPath pickClosest( PickShape pickShape ) { + if(isLive()==false) + throw new IllegalStateException(J3dI18N.getString("BranchGroup3")); + + return Picking.pickClosest( this, pickShape ); + } + + /** + * Returns a reference to any item that is Pickable below this BranchGroup that + * intersects with <code>pickShape</code>. + * + * @param pickShape the PickShape object + * @see SceneGraphPath + * @see Locale#pickAny + * @see PickShape + * @exception IllegalStateException if BranchGroup is not live. + * + */ + public SceneGraphPath pickAny( PickShape pickShape ) { + if(isLive()==false) + throw new IllegalStateException(J3dI18N.getString("BranchGroup3")); + + return Picking.pickAny( this, pickShape ); + } + + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + BranchGroup bg = new BranchGroup(); + bg.duplicateNode(this, forceDuplicate); + return bg; + } +} diff --git a/src/classes/share/javax/media/j3d/BranchGroupRetained.java b/src/classes/share/javax/media/j3d/BranchGroupRetained.java new file mode 100644 index 0000000..b73438c --- /dev/null +++ b/src/classes/share/javax/media/j3d/BranchGroupRetained.java @@ -0,0 +1,209 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +/** + * The BranchGroup node provides the ability to insert a branch of + * a scene graph into the virtual world. + */ + +class BranchGroupRetained extends GroupRetained { + + // This boolean is used in a moveTo operation to get transforms to update + boolean isDirty = false; + + // This boolean is used when the BG is inserted into the scene + boolean isNew = false; + + /** + * True, if this branchGroup is directly attached to the locale + */ + boolean attachedToLocale = false; + + + BranchGroupRetained() { + this.nodeType = NodeRetained.BRANCHGROUP; + } + + /** + * This sets the current locale. + */ + void setLocale(Locale loc) { + locale = loc; + } + + /** + * This gets the current locale + */ + Locale getLocale() { + return locale; + } + + /** + * Detaches this BranchGroup from its parent. + */ + void detach() { + if (universe != null) { + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + if (source.isLive()) { + notifySceneGraphChanged(true); + } + do_detach(); + universe.setLiveState.clear(); + } + universe.waitForMC(); + } else { // Not live yet, just do it. + this.do_detach(); + if (universe != null) { + synchronized (universe.sceneGraphLock) { + universe.setLiveState.clear(); + } + } + } + } + + // The method that does the work once the lock is acquired. + void do_detach() { + if (attachedToLocale) { + locale.doRemoveBranchGraph((BranchGroup)this.source, null, 0); + } else if (parent != null) { + GroupRetained g = (GroupRetained)parent; + g.doRemoveChild(g.children.indexOf(this), null, 0); + } + } + + void setNodeData(SetLiveState s) { + // super.setNodeData will set branchGroupPaths + // based on s.parentBranchGroupPaths, we need to + // set it earlier in order to undo the effect. + s.parentBranchGroupPaths = branchGroupPaths; + + super.setNodeData(s); + + if (!inSharedGroup) { + setAuxData(s, 0, 0); + } else { + // For inSharedGroup case. + int j, hkIndex; + for(j=0; j<s.keys.length; j++) { + hkIndex = s.keys[j].equals(localToVworldKeys, 0, + localToVworldKeys.length); + + if(hkIndex >= 0) { + setAuxData(s, j, hkIndex); + + } else { + // TODO: change this to an assertion exception + System.out.println("Can't Find matching hashKey in setNodeData."); + System.out.println("We're in TROUBLE!!!"); + } + } + } + } + + + void setAuxData(SetLiveState s, int index, int hkIndex) { + super.setAuxData(s, index, hkIndex); + + BranchGroupRetained path[] = (BranchGroupRetained[]) + s.branchGroupPaths.get(index); + BranchGroupRetained clonePath[] = + new BranchGroupRetained[path.length+1]; + System.arraycopy(path, 0, clonePath, 0, path.length); + clonePath[path.length] = this; + s.branchGroupPaths.set(index, clonePath); + branchGroupPaths.add(hkIndex, clonePath); + } + + + /** + * remove the localToVworld transform for this node. + */ + void removeNodeData(SetLiveState s) { + + if((!inSharedGroup) || (s.keys.length == localToVworld.length)) { + // restore to default and avoid calling clear() + // that may clear parent reference branchGroupPaths + branchGroupPaths = new ArrayList(1); + } + else { + int i, index; + // Must be in reverse, to preserve right indexing. + for (i = s.keys.length-1; i >= 0; i--) { + index = s.keys[i].equals(localToVworldKeys, 0, localToVworldKeys.length); + if(index >= 0) { + branchGroupPaths.remove(index); + } + } + // Set it back to its parent localToVworld data. This is b/c the parent has + // changed it localToVworld data arrays. + } + super.removeNodeData(s); + } + + void setLive(SetLiveState s) { + // recursively call child + super.doSetLive(s); + super.markAsLive(); + } + + + // Top level compile call + void compile() { + + if (source.isCompiled() || VirtualUniverse.mc.disableCompile) + return; + + if (J3dDebug.devPhase && J3dDebug.debug) { + J3dDebug.doDebug(J3dDebug.compileState, J3dDebug.LEVEL_3, + "BranchGroupRetained.compile()....\n"); + } + + CompileState compState = new CompileState(); + + isRoot = true; + + compile(compState); + merge(compState); + + if (J3dDebug.devPhase && J3dDebug.debug) { + if (J3dDebug.doDebug(J3dDebug.compileState, J3dDebug.LEVEL_3)) { + compState.printStats(); + } + if (J3dDebug.doDebug(J3dDebug.compileState, J3dDebug.LEVEL_5)) { + this.traverse(false, 1); + System.out.println(); + } + } + } + + void compile(CompileState compState) { + // if this branch group is previously compiled, don't + // go any further. Mark the keepTG flag for now. Static + // transform doesn't go beyond previously compiled group. + + if (mergeFlag == SceneGraphObjectRetained.MERGE_DONE) { + compState.keepTG = true; + return; + } + + super.compile(compState); + + // don't remove this node because user can do pickAll on this node + // without any capabilities set + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + } +} diff --git a/src/classes/share/javax/media/j3d/CachedFrustum.java b/src/classes/share/javax/media/j3d/CachedFrustum.java new file mode 100644 index 0000000..a034e7d --- /dev/null +++ b/src/classes/share/javax/media/j3d/CachedFrustum.java @@ -0,0 +1,571 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * The CachedFrustum class is used to pre compute data for a set of view + * frustum planes which allows more efficient intersection testing. + * + * The CachedFrustum caches data for frustums for effecient intersection + * testing. + */ + +// this class assumes the planes are in the following order: + // left plane = frustumPlanes[0] + // right plane = frustumPlanes[1] + // top plane = frustumPlanes[2] + // bottom plane = frustumPlanes[3] + // front plane = frustumPlanes[4] + // back plane = frustumPlanes[5] + +class CachedFrustum { + + static final double EPSILON = .000001; + Vector4d[] clipPlanes; + Point3d[] verts; + Point4d[] xEdges; // silhouette edges in yz plane + Point4d[] yEdges; // silhouette edges in xz plane + Point4d[] zEdges; // silhouette edges in xy plane + int nxEdges, nyEdges, nzEdges; + int[] xEdgeList; + int[] yEdgeList; + int[] zEdgeList; + Point3d upper,lower; // bounding box of frustum + Point3d center; // center of frustum + + // re-used temporary from computeEdges + Point3d edge = new Point3d(); + + /** + * Constructs and initializes a new CachedFrustum using the values + * provided in the argument. + * @param planes array specifying the frustum's clip plane position + */ + CachedFrustum(Vector4d[] planes) { + int i; + if( planes.length < 6 ) { + throw new IllegalArgumentException(J3dI18N.getString("CachedFrustum0")); + } + clipPlanes = new Vector4d[6]; + xEdges = new Point4d[12]; + yEdges = new Point4d[12]; + zEdges = new Point4d[12]; + verts = new Point3d[8]; + upper = new Point3d(); + lower = new Point3d(); + xEdgeList = new int[12]; + yEdgeList = new int[12]; + zEdgeList = new int[12]; + center = new Point3d(); + + for(i=0;i<8;i++){ + verts[i] = new Point3d(); + } + + for(i=0;i<6;i++){ + clipPlanes[i] = new Vector4d( planes[i] ); + } + for(i=0;i<12;i++){ + xEdges[i] = new Point4d(); + yEdges[i] = new Point4d(); + zEdges[i] = new Point4d(); + } + computeValues(clipPlanes); + } + + /** + * Constructs and initializes a new default CachedFrustum + * @param planes array specifying the frustum's clip planes + */ + CachedFrustum() { + int i; + clipPlanes = new Vector4d[6]; + upper = new Point3d(); + lower = new Point3d(); + xEdges = new Point4d[12]; + yEdges = new Point4d[12]; + zEdges = new Point4d[12]; + verts = new Point3d[8]; + xEdgeList = new int[12]; + yEdgeList = new int[12]; + zEdgeList = new int[12]; + center = new Point3d(); + + for(i=0;i<8;i++){ + verts[i] = new Point3d(); + } + for(i=0;i<6;i++){ + clipPlanes[i] = new Vector4d(); + } + for(i=0;i<12;i++){ + xEdges[i] = new Point4d(); + yEdges[i] = new Point4d(); + zEdges[i] = new Point4d(); + } + + } + + /** + * Returns a string containing the values of the CachedFrustum. + */ + public String toString() { + return( clipPlanes[0].toString()+"\n"+ + clipPlanes[1].toString()+"\n"+ + clipPlanes[2].toString()+"\n"+ + clipPlanes[3].toString()+"\n"+ + clipPlanes[4].toString()+"\n"+ + clipPlanes[5].toString()+"\n"+ + "corners="+"\n"+ + verts[0].toString()+"\n"+ + verts[1].toString()+"\n"+ + verts[2].toString()+"\n"+ + verts[3].toString()+"\n"+ + verts[4].toString()+"\n"+ + verts[5].toString()+"\n"+ + verts[6].toString()+"\n"+ + verts[7].toString() + ); + } + + + /** + * Sets the values of the CachedFrustum based on a new set of frustum planes. + * @param planes array specifying the frustum's clip planes + */ + void set(Vector4d[] planes) { + int i; + + if( planes.length != 6 ) { + throw new IllegalArgumentException(J3dI18N.getString("CachedFrustum1")); + } + + for(i=0;i<6;i++){ + clipPlanes[i].set( planes[i] ); + } + + computeValues(clipPlanes); + } + + /** + * Computes cached values. + * @param planes array specifying the frustum's clip planes + */ + private void computeValues(Vector4d[] planes) { + + int i; + + // compute verts + + computeVertex( 0, 3, 4, verts[0]); // front-bottom-left + computeVertex( 0, 2, 4, verts[1]); // front-top-left + computeVertex( 1, 2, 4, verts[2]); // front-top-right + computeVertex( 1, 3, 4, verts[3]); // front-bottom-right + computeVertex( 0, 3, 5, verts[4]); // back-bottom-left + computeVertex( 0, 2, 5, verts[5]); // back-top-left + computeVertex( 1, 2, 5, verts[6]); // back-top-right + computeVertex( 1, 3, 5, verts[7]); // back-bottom-right + + // compute bounding box + + upper.x = verts[0].x; + upper.y = verts[0].y; + upper.z = verts[0].z; + lower.x = verts[0].x; + lower.y = verts[0].y; + lower.z = verts[0].z; + + center.x = verts[0].x; + center.y = verts[0].y; + center.z = verts[0].z; + + // find min and max in x-y-z directions + + for(i=1;i<8;i++) { + if( verts[i].x > upper.x) upper.x = verts[i].x; // xmax + if( verts[i].x < lower.x) lower.x = verts[i].x; // xmin + + if( verts[i].y > upper.y) upper.y = verts[i].y; // ymay + if( verts[i].y < lower.y) lower.y = verts[i].y; // ymin + + if( verts[i].z > upper.z) upper.z = verts[i].z; // zmaz + if( verts[i].z < lower.z) lower.z = verts[i].z; // zmin + + center.x += verts[i].x; + center.y += verts[i].y; + center.z += verts[i].z; + } + + center.x = center.x*0.125; + center.y = center.y*0.125; + center.z = center.z*0.125; + + // to find the sil. edges in the xz plane check the sign y component of the normals + // from the plane equation and see if they are opposite + // xz plane + i = 0; + if( (clipPlanes[0].y * clipPlanes[4].y) <= 0.0 ) yEdgeList[i++] = 0; // front-left + if( (clipPlanes[2].y * clipPlanes[4].y) <= 0.0 ) yEdgeList[i++] = 1; // front-top + if( (clipPlanes[1].y * clipPlanes[4].y) <= 0.0 ) yEdgeList[i++] = 2; // front-right + if( (clipPlanes[3].y * clipPlanes[4].y) <= 0.0 ) yEdgeList[i++] = 3; // front-bottom + if( (clipPlanes[0].y * clipPlanes[3].y) <= 0.0 ) yEdgeList[i++] = 4; // middle-left + if( (clipPlanes[0].y * clipPlanes[2].y) <= 0.0 ) yEdgeList[i++] = 5; // middle-top + if( (clipPlanes[1].y * clipPlanes[2].y) <= 0.0 ) yEdgeList[i++] = 6; // middle-right + if( (clipPlanes[1].y * clipPlanes[3].y) <= 0.0 ) yEdgeList[i++] = 7; // middle-bottom + if( (clipPlanes[0].y * clipPlanes[5].y) <= 0.0 ) yEdgeList[i++] = 8; // back-left + if( (clipPlanes[2].y * clipPlanes[5].y) <= 0.0 ) yEdgeList[i++] = 9; // back-top + if( (clipPlanes[1].y * clipPlanes[5].y) <= 0.0 ) yEdgeList[i++] =10; // back-right + if( (clipPlanes[3].y * clipPlanes[5].y) <= 0.0 ) yEdgeList[i++] =11; // back-bottom + nyEdges = i; + + // yz plane + i = 0; + if( (clipPlanes[0].x * clipPlanes[4].x) <= 0.0 ) xEdgeList[i++] = 0; // front-left + if( (clipPlanes[2].x * clipPlanes[4].x) <= 0.0 ) xEdgeList[i++] = 1; // front-top + if( (clipPlanes[1].x * clipPlanes[4].x) <= 0.0 ) xEdgeList[i++] = 2; // front-right + if( (clipPlanes[3].x * clipPlanes[4].x) <= 0.0 ) xEdgeList[i++] = 3; // front-bottom + if( (clipPlanes[0].x * clipPlanes[3].x) <= 0.0 ) xEdgeList[i++] = 4; // middle-left + if( (clipPlanes[0].x * clipPlanes[2].x) <= 0.0 ) xEdgeList[i++] = 5; // middle-top + if( (clipPlanes[1].x * clipPlanes[2].x) <= 0.0 ) xEdgeList[i++] = 6; // middle-right + if( (clipPlanes[1].x * clipPlanes[3].x) <= 0.0 ) xEdgeList[i++] = 7; // middle-bottom + if( (clipPlanes[0].x * clipPlanes[5].x) <= 0.0 ) xEdgeList[i++] = 8; // back-left + if( (clipPlanes[2].x * clipPlanes[5].x) <= 0.0 ) xEdgeList[i++] = 9; // back-top + if( (clipPlanes[1].x * clipPlanes[5].x) <= 0.0 ) xEdgeList[i++] =10; // back-right + if( (clipPlanes[3].x * clipPlanes[5].x) <= 0.0 ) xEdgeList[i++] =11; // back-bottom + nxEdges = i; + + // xy plane + i = 0; + if( (clipPlanes[0].z * clipPlanes[4].z) <= 0.0 ) zEdgeList[i++] = 0; // front-left + if( (clipPlanes[2].z * clipPlanes[4].z) <= 0.0 ) zEdgeList[i++] = 1; // front-top + if( (clipPlanes[1].z * clipPlanes[4].z) <= 0.0 ) zEdgeList[i++] = 2; // front-right + if( (clipPlanes[3].z * clipPlanes[4].z) <= 0.0 ) zEdgeList[i++] = 3; // front-bottom + if( (clipPlanes[0].z * clipPlanes[3].z) <= 0.0 ) zEdgeList[i++] = 4; // middle-left + if( (clipPlanes[0].z * clipPlanes[2].z) <= 0.0 ) zEdgeList[i++] = 5; // middle-top + if( (clipPlanes[1].z * clipPlanes[2].z) <= 0.0 ) zEdgeList[i++] = 6; // middle-right + if( (clipPlanes[1].z * clipPlanes[3].z) <= 0.0 ) zEdgeList[i++] = 7; // middle-bottom + if( (clipPlanes[0].z * clipPlanes[5].z) <= 0.0 ) zEdgeList[i++] = 8; // back-left + if( (clipPlanes[2].z * clipPlanes[5].z) <= 0.0 ) zEdgeList[i++] = 9; // back-top + if( (clipPlanes[1].z * clipPlanes[5].z) <= 0.0 ) zEdgeList[i++] =10; // back-right + if( (clipPlanes[3].z * clipPlanes[5].z) <= 0.0 ) zEdgeList[i++] =11; // back-bottom + nzEdges = i; + + // compute each edge + computeEdges( clipPlanes, 0, 4, xEdges[0], yEdges[0], zEdges[0]); // front-left + computeEdges( clipPlanes, 2, 4, xEdges[1], yEdges[1], zEdges[1]); // front-top + computeEdges( clipPlanes, 1, 4, xEdges[2], yEdges[2], zEdges[2]); + computeEdges( clipPlanes, 3, 4, xEdges[3], yEdges[3], zEdges[3]); + computeEdges( clipPlanes, 0, 3, xEdges[4], yEdges[4], zEdges[4]); + computeEdges( clipPlanes, 0, 2, xEdges[5], yEdges[5], zEdges[5]); + computeEdges( clipPlanes, 1, 2, xEdges[6], yEdges[6], zEdges[6]); + computeEdges( clipPlanes, 1, 3, xEdges[7], yEdges[7], zEdges[7]); + computeEdges( clipPlanes, 0, 5, xEdges[8], yEdges[8], zEdges[8]); + computeEdges( clipPlanes, 2, 5, xEdges[9], yEdges[9], zEdges[9]); + computeEdges( clipPlanes, 1, 5, xEdges[10], yEdges[10], zEdges[10]); + computeEdges( clipPlanes, 3, 5, xEdges[11], yEdges[11], zEdges[11]); + + /* + int k; + System.out.println("clipPlanes="); + for( k=0;k<6;k++){ + System.out.println(clipPlanes[k].toString()); + } + System.out.println("corners="+"\n"+ + verts[0].toString()+"\n"+ + verts[1].toString()+"\n"+ + verts[2].toString()+"\n"+ + verts[3].toString()+"\n"+ + verts[4].toString()+"\n"+ + verts[5].toString()+"\n"+ + verts[6].toString()+"\n"+ + verts[7].toString()); + System.out.println("\nxEdges="); + for(k=0;k<nxEdges;k++){ + System.out.println(xEdges[xEdgeList[k]].toString()); + } + System.out.println("\nyEdges="); + for(k=0;k<nxEdges;k++){ + System.out.println(yEdges[xEdgeList[k]].toString()); + } + System.out.println("\nzEdges="); + for(k=0;k<nxEdges;k++){ + System.out.println(zEdges[xEdgeList[k]].toString()); + } + */ + + } + private void computeEdges( Vector4d[] planes, int i, int j, Point4d xEdge, + Point4d yEdge, Point4d zEdge) { + + double mag,x,y,z,xm,ym,zm,w; + + // compute vector that is intersection of two planes + + edge.x = planes[i].y*planes[j].z - planes[j].y*planes[i].z; + edge.y = planes[j].x*planes[i].z - planes[i].x*planes[j].z; + edge.z = planes[i].x*planes[j].y - planes[j].x*planes[i].y; + + mag = 1.0/Math.sqrt( edge.x*edge.x + edge.y*edge.y + edge.z*edge.z); + + edge.x = mag*edge.x; + edge.y = mag*edge.y; + edge.z = mag*edge.z; + + xm = Math.abs(edge.x); + ym = Math.abs(edge.y); + zm = Math.abs(edge.z); + + // compute point on the intersection vector + // see Graphics Gems III pg. 233 + + if( zm >= xm && zm >= ym ){ // z greatest magnitude + w = (planes[i].x*planes[j].y + planes[i].z*planes[j].y); + if( w == 0.0) + w = 1.0; + else + w = 1.0/w; + x = (planes[i].y*planes[j].w - planes[j].y*planes[i].w) * w; + y = (planes[j].x*planes[i].w - planes[i].x*planes[j].w) * w; + z = 0.0; + } else if( xm >= ym && xm >= zm){ // x greatest magnitude + w = (planes[i].y*planes[j].z + planes[i].z*planes[j].y); + if( w == 0.0) + w = 1.0; + else + w = 1.0/w; + x = 0.0; + y = (planes[i].z*planes[j].w - planes[j].z*planes[i].w) * w; + z = (planes[j].y*planes[i].w - planes[i].y*planes[j].w) * w; + } else { // y greatest magnitude + w = (planes[i].x*planes[j].z + planes[i].z*planes[j].x); + if( w == 0.0) + w = 1.0; + else + w = 1.0/w; + x = (planes[i].z*planes[j].w - planes[j].z*planes[i].w) * w; + y = 0.0; + z = (planes[j].x*planes[i].w - planes[i].x*planes[j].w) * w; + } + + // compute the noramls to the edges in for silhouette testing + /* + System.out.println("\nplane1="+planes[i].toString()); + System.out.println("plane2="+planes[j].toString()); + System.out.println("point="+x+" "+y+" "+z); + System.out.println("edge="+edge.x+" "+edge.y+" "+edge.z); + */ + // x=0 edges + + xEdge.x = 0.0; + xEdge.y = -edge.z; + xEdge.z = edge.y; + xEdge.w = -(xEdge.y*y + xEdge.z*z); + + if( (center.y*xEdge.y + center.z*xEdge.z + xEdge.w) > 0.0 ){ + xEdge.y = edge.z; + xEdge.z = -edge.y; + xEdge.w = -(xEdge.y*y + xEdge.z*z); + } + + // y=0 edges + yEdge.x = -edge.z; + yEdge.y = 0.0; + yEdge.z = edge.x; + yEdge.w = -(yEdge.x*x + yEdge.z*z); + if( (center.y*yEdge.y + center.z*yEdge.z + yEdge.w) > 0.0 ){ + yEdge.x = edge.z; + yEdge.z = -edge.x; + yEdge.w = -(yEdge.x*x + yEdge.z*z); + } + + // z=0 edges + zEdge.x = -edge.y; + zEdge.y = edge.x; + zEdge.z = 0.0; + zEdge.w = -(zEdge.y*y + zEdge.x*x); + + if( (center.x*zEdge.x + center.y*zEdge.y + zEdge.w) > 0.0 ){ + zEdge.x = edge.y; + zEdge.y = -edge.x; + zEdge.w = -(zEdge.y*y + zEdge.x*x); + } + /* + System.out.println("xedge="+xEdge.x+" "+xEdge.y+" "+xEdge.z+" "+xEdge.w); + System.out.println("yedge="+yEdge.y+" "+yEdge.y+" "+yEdge.z+" "+yEdge.w); + System.out.println("zedge="+zEdge.z+" "+zEdge.y+" "+zEdge.z+" "+zEdge.w); + */ + } + private void computeVertex( int a, int b, int c, Point3d vert) { + double det,x,y,z; + + det = clipPlanes[a].x*clipPlanes[b].y*clipPlanes[c].z + clipPlanes[a].y*clipPlanes[b].z*clipPlanes[c].x + + clipPlanes[a].z*clipPlanes[b].x*clipPlanes[c].y - clipPlanes[a].z*clipPlanes[b].y*clipPlanes[c].x - + clipPlanes[a].y*clipPlanes[b].x*clipPlanes[c].z - clipPlanes[a].x*clipPlanes[b].z*clipPlanes[c].y; + + if( det*det < EPSILON ){ + return; // two planes are parallel + } + + det = 1.0/det; + + vert.x = (clipPlanes[b].y*clipPlanes[c].z - clipPlanes[b].z*clipPlanes[c].y) * -clipPlanes[a].w; + vert.y = (clipPlanes[b].z*clipPlanes[c].x - clipPlanes[b].x*clipPlanes[c].z) * -clipPlanes[a].w; + vert.z = (clipPlanes[b].x*clipPlanes[c].y - clipPlanes[b].y*clipPlanes[c].x) * -clipPlanes[a].w; + + vert.x += (clipPlanes[c].y*clipPlanes[a].z - clipPlanes[c].z*clipPlanes[a].y) * -clipPlanes[b].w; + vert.y += (clipPlanes[c].z*clipPlanes[a].x - clipPlanes[c].x*clipPlanes[a].z) * -clipPlanes[b].w; + vert.z += (clipPlanes[c].x*clipPlanes[a].y - clipPlanes[c].y*clipPlanes[a].x) * -clipPlanes[b].w; + + vert.x += (clipPlanes[a].y*clipPlanes[b].z - clipPlanes[a].z*clipPlanes[b].y) * -clipPlanes[c].w; + vert.y += (clipPlanes[a].z*clipPlanes[b].x - clipPlanes[a].x*clipPlanes[b].z) * -clipPlanes[c].w; + vert.z += (clipPlanes[a].x*clipPlanes[b].y - clipPlanes[a].y*clipPlanes[b].x) * -clipPlanes[c].w; + + vert.x = vert.x*det; + vert.y = vert.y*det; + vert.z = vert.z*det; + + } + + + /** + * Tests for intersection of six sided hull and the frustum + * @param six sided bounding box ( lower (x,y,z), upper (x,y,z)) + * @return true if they intersect + */ + boolean intersect( double lx, double ly, double lz, + double ux, double uy, double uz) { + int i,index; + + // System.out.println("intersect frustum with box : lower ( " + lx + ", " + ly + ", " + lz + + // ") upper( " + ux + ", " + uy + ", " + uz + ")"); + // System.out.println("frustum "+this.toString()); + // check if box and bounding box of frustum intersect + if( ux > this.lower.x && + lx < this.upper.x && + uy > this.lower.y && + ly < this.upper.y && + uz > this.lower.z && + lz < this.upper.z ) { + } else { + // System.out.println("false box and bounding box of frustum do not intersect"); + return false; + } + + // check if all box points out any frustum plane + for(i=0;i<6;i++){ + if(( ux*this.clipPlanes[i].x + + uy*this.clipPlanes[i].y + + uz*this.clipPlanes[i].z + this.clipPlanes[i].w ) > -EPSILON ) { + continue; // corner inside plane + } + if(( ux*this.clipPlanes[i].x + + ly*this.clipPlanes[i].y + + uz*this.clipPlanes[i].z + this.clipPlanes[i].w ) > -EPSILON ){ + continue; + } + if(( ux*this.clipPlanes[i].x + + ly*this.clipPlanes[i].y + + lz*this.clipPlanes[i].z + this.clipPlanes[i].w ) > -EPSILON ){ + continue; + } + if(( ux*this.clipPlanes[i].x + + uy*this.clipPlanes[i].y + + lz*this.clipPlanes[i].z + this.clipPlanes[i].w ) > -EPSILON ) { + continue; + } + if(( lx*this.clipPlanes[i].x + + uy*this.clipPlanes[i].y + + uz*this.clipPlanes[i].z + this.clipPlanes[i].w ) > -EPSILON ) { + continue; + } + if(( lx*this.clipPlanes[i].x + + ly*this.clipPlanes[i].y + + uz*this.clipPlanes[i].z + this.clipPlanes[i].w ) > -EPSILON ) { + continue; + } + if(( lx*this.clipPlanes[i].x + + ly*this.clipPlanes[i].y + + lz*this.clipPlanes[i].z + this.clipPlanes[i].w ) > -EPSILON ) { + continue; + } + if(( lx*this.clipPlanes[i].x + + uy*this.clipPlanes[i].y + + lz*this.clipPlanes[i].z + this.clipPlanes[i].w ) > -EPSILON ) { + continue; + } + // System.out.println("false all corners outside this frustum plane"+frustum.clipPlanes[i].toString()); + return false; // all corners outside this frustum plane + } + + // check if any box corner is inside of the frustum silhoette edges in the 3 views + // y-z + for(i=0;i<this.nxEdges;i++){ + index = this.xEdgeList[i]; + if(( uy*this.xEdges[index].y + + uz*this.xEdges[index].z + this.xEdges[index].w ) < EPSILON ) break; // corner inside ege + if(( uy*this.xEdges[index].y + + lz*this.xEdges[index].z + this.xEdges[index].w ) < EPSILON ) break; + if(( ly*this.xEdges[index].y + + uz*this.xEdges[index].z + this.xEdges[index].w ) < EPSILON ) break; + if(( ly*this.xEdges[index].y + + lz*this.xEdges[index].z + this.xEdges[index].w ) < EPSILON ) break; + if( i == this.nxEdges-1) { + // System.out.println("false all box corners outside yz silhouette edges "); + return false; // all box corners outside yz silhouette edges + } + } + // x-z + for(i=0;i<this.nyEdges;i++){ + index = this.yEdgeList[i]; + if(( ux*this.yEdges[index].x + + uz*this.yEdges[index].z + this.yEdges[index].w ) < EPSILON ) break; + if(( ux*this.yEdges[index].x + + lz*this.yEdges[index].z + this.yEdges[index].w ) < EPSILON ) break; + if(( lx*this.yEdges[index].x + + uz*this.yEdges[index].z + this.yEdges[index].w ) < EPSILON ) break; + if(( lx*this.yEdges[index].x + + lz*this.yEdges[index].z + this.yEdges[index].w ) < EPSILON ) break; + if( i == this.nyEdges-1) { + // System.out.println("false all box corners outside xz silhouette edges"); + return false; // all box corners outside xz silhouette edges + } + } + // x-y + for(i=0;i<this.nzEdges;i++){ + index = this.zEdgeList[i]; + if(( uy*this.zEdges[index].y + + uz*this.zEdges[index].z + this.zEdges[index].w ) < EPSILON ) break; + if(( uy*this.zEdges[index].y + + lz*this.zEdges[index].z + this.zEdges[index].w ) < EPSILON ) break; + if(( ly*this.zEdges[index].y + + uz*this.zEdges[index].z + this.zEdges[index].w ) < EPSILON ) break; + if(( ly*this.zEdges[index].y + + lz*this.zEdges[index].z + this.zEdges[index].w ) < EPSILON ) break; + if( i == this.nzEdges-1) { + /* + System.out.println("false all box corners outside xy silhouette edges"); + System.out.println("xy silhouette edges="); + for(int j=0;j<this.nzEdges;j++){ + System.out.println(this.zEdges[j].toString()); + } + */ + return false; // all box corners outside xy silhouette edges + } + } + // System.out.println("true"); + return true; + } + + +} diff --git a/src/classes/share/javax/media/j3d/CachedTargets.java b/src/classes/share/javax/media/j3d/CachedTargets.java new file mode 100644 index 0000000..f10bc7f --- /dev/null +++ b/src/classes/share/javax/media/j3d/CachedTargets.java @@ -0,0 +1,109 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class CachedTargets { + // cached targets, used by J3d threads + + // 0 - Data type is GeometryAtom. + // 1 - Data type is Light, Fog, Background, ModelClip, AlternateAppearance, + // Clip + // 2 - Data type is BehaviorRetained. + // 3 - Data type is Sound or Soundscape + // 4 - Data type is ViewPlatformRetained. + // 5 - Data type is BoundingLeafRetained. + // 6 - Data type is GroupRetained. + + // Order of index is as above. + // The handling of BoundingLeaf isn't optimize. Target threads should be + // more specific. + + static String typeString[] = { + "GEO_TARGETS", + "ENV_TARGETS", + "BEH_TARGETS", + "SND_TARGETS", + "VPF_TARGETS", + "BLN_TARGETS", + "GRP_TARGETS", + }; + + static int updateTargetThreads[] = { + // GEO + J3dThread.UPDATE_TRANSFORM | J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_GEOMETRY, + + // ENV + J3dThread.UPDATE_RENDER | J3dThread.UPDATE_RENDERING_ENVIRONMENT, + + // BEH + J3dThread.UPDATE_BEHAVIOR, + + // SND + J3dThread.UPDATE_SOUND | J3dThread.SOUND_SCHEDULER, + + // VPF + J3dThread.UPDATE_RENDER | J3dThread.UPDATE_BEHAVIOR | + J3dThread.UPDATE_SOUND | J3dThread.SOUND_SCHEDULER, + + // BLN + J3dThread.UPDATE_RENDER | J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_BEHAVIOR | J3dThread.UPDATE_SOUND, + + // GRP + J3dThread.UPDATE_TRANSFORM | J3dThread.UPDATE_GEOMETRY + }; + + + NnuId targetArr[][] = new NnuId[Targets.MAX_NODELIST][]; + + int computeTargetThreads() { + int targetThreads = 0; + + for (int i=0; i < Targets.MAX_NODELIST; i++) { + if (targetArr[i] != null) { + targetThreads |= updateTargetThreads[i]; + } + } + return targetThreads; + } + + void copy( CachedTargets ct ) { + + for(int i=0; i<Targets.MAX_NODELIST; i++) { + targetArr[i] = ct.targetArr[i]; + } + } + + void replace(NnuId oldObj, NnuId newObj, int type) { + + NnuId[] newArr = new NnuId[targetArr[type].length]; + System.arraycopy(targetArr[type], 0, newArr, + 0, targetArr[type].length); + targetArr[type] = newArr; + NnuIdManager.replace((NnuId)oldObj, (NnuId)newObj, + (NnuId[])targetArr[type]); + } + + void dump() { + for(int i=0; i<Targets.MAX_NODELIST; i++) { + if (targetArr[i] != null) { + System.out.println(" " + typeString[i]); + for(int j=0; j<targetArr[i].length; j++) { + System.out.println(" " + targetArr[i][j]); + } + } + } + } + +} diff --git a/src/classes/share/javax/media/j3d/Canvas3D.java b/src/classes/share/javax/media/j3d/Canvas3D.java new file mode 100644 index 0000000..4d23891 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Canvas3D.java @@ -0,0 +1,4209 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.image.BufferedImage; +import java.util.*; + + + +/** + * The Canvas3D class provides a drawing canvas for 3D rendering. It + * is used either for on-screen rendering or off-screen rendering. + * Canvas3D is an extension of the AWT Canvas class that users may + * further subclass to implement additional functionality. + * <p> + * The Canvas3D object extends the Canvas object to include + * 3D-related information such as the size of the canvas in pixels, + * the Canvas3D's location, also in pixels, within a Screen3D object, + * and whether or not the canvas has stereo enabled. + * <p> + * Because all Canvas3D objects contain a + * reference to a Screen3D object and because Screen3D objects define + * the size of a pixel in physical units, Java 3D can convert a Canvas3D + * size in pixels to a physical world size in meters. It can also + * determine the Canvas3D's position and orientation in the + * physical world. + * <p> + * <b>On-screen Rendering vs. Off-screen Rendering</b> + * <p> + * The Canvas3D class is used either for on-screen rendering or + * off-screen rendering. + * On-screen Canvas3Ds are added to AWT or Swing Container objects + * like any other canvas. Java 3D automatically and continuously + * renders to all on-screen canvases that are attached to an active + * View object. On-screen Canvas3Ds can be either single or double + * buffered and they can be either stereo or monoscopic. + * <p> + * Off-screen Canvas3Ds must not be added to any Container. Java 3D + * renders to off-screen canvases in response to the + * <code>renderOffScreenBuffer</code> method. Off-screen Canvas3Ds + * are single buffered. However, on many systems, the actual + * rendering is done to an off-screen hardware buffer or to a 3D + * library-specific buffer and only copied to the off-screen buffer of + * the Canvas when the rendering is complete, at "buffer swap" time. + * Off-screen Canvas3Ds are monoscopic. + * <p> + * The setOffScreenBuffer method sets the off-screen buffer for this + * Canvas3D. The specified image is written into by the Java 3D renderer. + * The size of the specified ImageComponent determines the size, in + * pixels, of this Canvas3D - the size inherited from Component is + * ignored. Note that the size, physical width, and physical height of the + * associated Screen3D must be set + * explicitly prior to rendering. Failure to do so will result in an + * exception. + * <p> + * The getOffScreenBuffer method retrieves the off-screen + * buffer for this Canvas3D. + * <p> + * The renderOffScreenBuffer method schedules the rendering of a frame + * into this Canvas3D's off-screen buffer. The rendering is done from + * the point of view of the View object to which this Canvas3D has been + * added. No rendering is performed if this Canvas3D object has not been + * added to an active View. This method does not wait for the rendering + * to actually happen. An application that wishes to know when the + * rendering is complete must either subclass Canvas3D and + * override the postSwap method, or call waitForOffScreenRendering. + * <p> + * The setOfScreenLocation methods set the location of this off-screen + * Canvas3D. The location is the upper-left corner of the Canvas3D + * relative to the upper-left corner of the corresponding off-screen + * Screen3D. The function of these methods is similar to that of + * Component.setLocation for on-screen Canvas3D objects. The default + * location is (0,0). + * <p> + * <b>Accessing and Modifying an Eye's Image Plate Position</b> + * <p> + * A Canvas3D object provides sophisticated applications with access + * to the eye's position information in head-tracked, room-mounted + * runtime environments. It also allows applications to manipulate + * the position of an eye relative to an image plate in non-head-tracked + * runtime environments. + * <p> + * The setLeftManualEyeInImagePlate and setRightManualEyeInImagePlate + * methods set the position of the manual left and right eyes in image + * plate coordinates. These values determine eye placement when a head + * tracker is not in use and the application is directly controlling the + * eye position in image plate coordinates. In head-tracked mode or + * when the windowEyepointPolicy is RELATIVE_TO_FIELD_OF_VIEW or + * RELATIVE_TO_COEXISTENCE, this + * value is ignored. When the windowEyepointPolicy is RELATIVE_TO_WINDOW, + * only the Z value is used. + * <p> + * The getLeftEyeInImagePlate, getRightEyeInImagePlate, and + * getCenterEyeInImagePlate methods retrieve the actual position of the + * left eye, right eye, and center eye in image plate coordinates and + * copy that value into the object provided. The center eye is the + * fictional eye half-way between the left and right eye. These three + * values are a function of the windowEyepointPolicy, the tracking + * enable flag, and the manual left, right, and center eye positions. + * <p> + * <b>Monoscopic View Policy</b> + * <p> + * The setMonoscopicViewPolicy and getMonoscopicViewPolicy methods + * set and retrieve the policy regarding how Java 3D generates monoscopic + * view. If the policy is set to View.LEFT_EYE_VIEW, the view generated + * corresponds to the view as seen from the left eye. If set to + * View.RIGHT_EYE_VIEW, the view generated corresponds to the view as + * seen from the right eye. If set to View.CYCLOPEAN_EYE_VIEW, the view + * generated corresponds to the view as seen from the "center eye," the + * fictional eye half-way between the left and right eye. The default + * monoscopic view policy is View.CYCLOPEAN_EYE_VIEW. + * <p> + * <b>Immediate Mode Rendering</b> + * <p> + * Pure immediate-mode rendering provides for those applications and + * applets that do not want Java 3D to do any automatic rendering of + * the scene graph. Such applications may not even wish to build a + * scene graph to represent their graphical data. However, they use + * Java 3D's attribute objects to set graphics state and Java 3D's + * geometric objects to render geometry. + * <p> + * A pure immediate mode application must create a minimal set of + * Java 3D objects before rendering. In addition to a Canvas3D object, + * the application must create a View object, with its associated + * PhysicalBody and PhysicalEnvironment objects, and the following + * scene graph elements: a VirtualUniverse object, a high-resolution + * Locale object, a BranchGroup node object, a TransformGroup node + * object with associated transform, and a ViewPlatform + * leaf node object that defines the position and orientation within + * the virtual universe that generates the view. + * <p> + * In immediate mode, all rendering is done completely under user + * control. It is necessary for the user to clear the 3D canvas, + * render all geometry, and swap the buffers. Additionally, + * rendering the right and left eye for stereo viewing becomes the + * sole responsibility of the application. In pure immediate mode, + * the user must stop the Java 3D renderer, via the + * Canvas3D object <code>stopRenderer</code> method, prior to adding the + * Canvas3D object to an active View object (that is, one that is + * attached to a live ViewPlatform object). + * <p> + * Other Canvas3D methods related to immediate mode rendering are: + * <p> + * <ul> + * <code>getGraphicsContext3D</code> retrieves the immediate-mode + * 3D graphics context associated with this Canvas3D. It creates a + * new graphics context if one does not already exist. + * <p> + * <code>getGraphics2D</code> retrieves the + * 2D graphics object associated with this Canvas3D. It creates a + * new 2D graphics object if one does not already exist. + * <p> + * <code>swap</code> synchronizes and swaps buffers on a + * double-buffered canvas for this Canvas3D object. This method + * should only be called if the Java 3D renderer has been stopped. + * In the normal case, the renderer automatically swaps + * the buffer. + * </ul> + * + * <p> + * <b>Mixed Mode Rendering</b> + * <p> + * Mixing immediate mode and retained or compiled-retained mode + * requires more structure than pure immediate mode. In mixed mode, + * the Java 3D renderer is running continuously, rendering the scene + * graph into the canvas. + * + * <p> + * Canvas3D methods related to mixed mode rendering are: + * + * <p> + * <ul> + * <code>preRender</code> called by the Java 3D rendering loop after + * clearing the canvas and before any rendering has been done for + * this frame. + * <p> + * <code>postRender</code> called by the Java 3D rendering loop after + * completing all rendering to the canvas for this frame and before + * the buffer swap. + * <p> + * <code>postSwap</code> called by the Java 3D rendering loop after + * completing all rendering to the canvas, and all other canvases + * associated with this view, for this frame following the + * buffer swap. + * <p> + * <code>renderField</code> called by the Java 3D rendering loop + * during the execution of the rendering loop. It is called once + * for each field (i.e., once per frame on a mono system or once + * each for the right eye and left eye on a two-pass stereo system. + * </ul> + * <p> + * The above callback methods are called by the Java 3D rendering system + * and should <i>not</i> be called by an application directly. + * + * <p> + * The basic Java 3D <i>stereo</i> rendering loop, + * executed for each Canvas3D, is as follows: + * <ul><pre> + * clear canvas (both eyes) + * call preRender() // user-supplied method + * set left eye view + * render opaque scene graph objects + * call renderField(FIELD_LEFT) // user-supplied method + * render transparent scene graph objects + * set right eye view + * render opaque scene graph objects again + * call renderField(FIELD_RIGHT) // user-supplied method + * render transparent scene graph objects again + * call postRender() // user-supplied method + * synchronize and swap buffers + * call postSwap() // user-supplied method + * </pre></ul> + * <p> + * The basic Java 3D <i>monoscopic</i> rendering loop is as follows: + * <ul><pre> + * clear canvas + * call preRender() // user-supplied method + * set view + * render opaque scene graph objects + * call renderField(FIELD_ALL) // user-supplied method + * render transparent scene graph objects + * call postRender() // user-supplied method + * synchronize and swap buffers + * call postSwap() // user-supplied method + * </pre></ul> + * <p> + * In both cases, the entire loop, beginning with clearing the canvas + * and ending with swapping the buffers, defines a frame. The application + * is given the opportunity to render immediate-mode geometry at any of + * the clearly identified spots in the rendering loop. A user specifies + * his or her own rendering methods by extending the Canvas3D class and + * overriding the preRender, postRender, postSwap, and/or renderField + * methods. + * Updates to live Geometry, Texture, and ImageComponent objects + * in the scene graph are not allowed from any of these callback + * methods. + * + * <p> + * <b>Serialization</b> + * <p> + * Canvas3D does <i>not</i> support serialization. An attempt to + * serialize a Canvas3D object will result in an + * UnsupportedOperationException being thrown. + * + * @see Screen3D + * @see View + * @see GraphicsContext3D + */ +public class Canvas3D extends Canvas { + /** + * Specifies the left field of a field-sequential stereo rendering loop. + * A left field always precedes a right field. + */ + public static final int FIELD_LEFT = 0; + + /** + * Specifies the right field of a field-sequential stereo rendering loop. + * A right field always follows a left field. + */ + public static final int FIELD_RIGHT = 1; + + /** + * Specifies a single-field rendering loop. + */ + public static final int FIELD_ALL = 2; + + // + // The following constants are bit masks to specify which of the node + // components are dirty and need updates. + // + // Values for the geometryType field. + static final int POLYGONATTRS_DIRTY = 0x01; + static final int LINEATTRS_DIRTY = 0x02; + static final int POINTATTRS_DIRTY = 0x04; + static final int MATERIAL_DIRTY = 0x08; + static final int TRANSPARENCYATTRS_DIRTY = 0x10; + static final int COLORINGATTRS_DIRTY = 0x20; + + // Values for lightbin, env set, texture, texture setting etc. + static final int LIGHTBIN_DIRTY = 0x40; + static final int LIGHTENABLES_DIRTY = 0x80; + static final int AMBIENTLIGHT_DIRTY = 0x100; + static final int ATTRIBUTEBIN_DIRTY = 0x200; + static final int TEXTUREBIN_DIRTY = 0x400; + static final int TEXTUREATTRIBUTES_DIRTY = 0x800; + static final int RENDERMOLECULE_DIRTY = 0x1000; + static final int FOG_DIRTY = 0x2000; + static final int MODELCLIP_DIRTY = 0x4000; + static final int VWORLD_SCALE_DIRTY = 0x8000; + + // Use to notify D3D Canvas when window change + static final int RESIZE = 1; + static final int TOGGLEFULLSCREEN = 2; + static final int NOCHANGE = 0; + static final int RESETSURFACE = 1; + static final int RECREATEDDRAW = 2; + + // + // Flag that indicates whether this Canvas3D is an off-screen Canvas3D + // + boolean offScreen = false; + + // user specified offScreen Canvas location + Point offScreenCanvasLoc; + + // user specified offScreen Canvas dimension + Dimension offScreenCanvasSize; + + // clipped offscreen canvas + Point offScreenCanvasClippedLoc; + Dimension offScreenCanvasClippedSize; + + // + // Flag that indicates whether off-screen rendering is in progress or not + // + volatile boolean offScreenRendering = false; + + // Flag that indicates whether canvas is waiting for off-screen + // rendering to be completed + boolean waitingForOffScreenRendering = false; + + // + // ImageComponent used for off-screen rendering + // + ImageComponent2D offScreenBuffer = null; + + // read buffer for reading off screen buffer + byte[] byteBuffer = new byte[1]; + + // flag that indicates whether this canvas will use shared context + boolean useSharedCtx = true; + + // + // Read-only flag that indicates whether stereo is supported for this + // canvas. This is always false for off-screen canvases. + // + boolean stereoAvailable; + + // + // Flag to enable stereo rendering, if allowed by the + // stereoAvailable flag. + // + boolean stereoEnable = true; + + // + // This flag is set when stereo mode is both enabled and + // available. Code that looks at stereo mode should use this + // flag. + // + boolean useStereo; + + // Indicate whether it is left or right stereo pass currently + boolean rightStereoPass = false; + + // + // Specifies how Java 3D generates monoscopic view + // (LEFT_EYE_VIEW, RIGHT_EYE_VIEW, or CYCLOPEAN_EYE_VIEW). + // + int monoscopicViewPolicy = View.CYCLOPEAN_EYE_VIEW; + + // + // Read-only flag that indicates whether double buffering is supported + // for this canvas. This is always false for off-screen canvases. + // + boolean doubleBufferAvailable; + + // + // Flag to enable double buffered rendering, if allowed by the + // doubleBufferAvailable flag. + // + boolean doubleBufferEnable = true; + + // + // This flag is set when doubleBuffering is both enabled and + // available Code that enables or disables double buffering should + // use this flag. + // + boolean useDoubleBuffer; + + // + // Read-only flag that indicates whether scene antialiasing + // is supported for this canvas. + // + boolean sceneAntialiasingAvailable; + boolean sceneAntialiasingMultiSamplesAvailable; + + // Use to see whether antialiasing is already set + boolean antialiasingSet = false; + + // + // Read-only flag that indicates the size of the texture color + // table for this canvas. A value of 0 indicates that the texture + // color table is not supported. + // + int textureColorTableSize; + + + boolean multiTexAccelerated = false; + + // number of simultaneous Texture unit support for this canvas. + int numTexUnitSupported = 1; + + // number of texture coords unit support for multi-texture. + int numTexCoordSupported = 1; + + // a mapping between underlying graphics library texture unit and + // texture unit state in j3d + int[] texUnitStateMap = null; + + // number of active/enabled texture unit + int numActiveTexUnit = 0; + + // index iof last enabled texture unit + int lastActiveTexUnit = -1; + + // Query properties + J3dQueryProps queryProps; + + // + // The positions of the manual left and right eyes in image-plate + // coordinates. + // By default, we will use the center of the screen for X and Y values + // (X values are adjusted for default eye separation), and + // 0.4572 meters (18 inches) for the Z value. + // These match defaults elsewhere in the system. + // + Point3d leftManualEyeInImagePlate = new Point3d(0.142, 0.135, 0.4572); + Point3d rightManualEyeInImagePlate = new Point3d(0.208, 0.135, 0.4572); + + // + // View that is attached to this Canvas3D. + // + View view = null; + + // View waiting to be set + View pendingView; + + // + // View cache for this canvas and its associated view. + // + CanvasViewCache canvasViewCache = null; + + // Since multiple renderAtomListInfo, share the same vecBounds + // we want to do the intersection test only once per renderAtom + // this flag is set to true after the first intersect and set to + // false during checkForCompaction in renderBin + boolean raIsVisible = false; + + RenderAtom ra = null; + + // Stereo related field has changed. + static final int STEREO_DIRTY = 0x01; + // MonoscopicViewPolicy field has changed. + static final int MONOSCOPIC_VIEW_POLICY_DIRTY = 0x02; + // Left/right eye in image plate field has changed. + static final int EYE_IN_IMAGE_PLATE_DIRTY = 0x04; + // Canvas has moved/resized. + static final int MOVED_OR_RESIZED_DIRTY = 0x08; + + // Canvas Background changed (this may affect doInfinite flag) + static final int BACKGROUND_DIRTY = 0x10; + + // Canvas Background Image changed + static final int BACKGROUND_IMAGE_DIRTY = 0x20; + + + // Mask that indicates this Canvas view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + static final int VIEW_INFO_DIRTY = (STEREO_DIRTY | + MONOSCOPIC_VIEW_POLICY_DIRTY | + EYE_IN_IMAGE_PLATE_DIRTY | + MOVED_OR_RESIZED_DIRTY | + BACKGROUND_DIRTY | + BACKGROUND_IMAGE_DIRTY); + + int cvDirtyMask = VIEW_INFO_DIRTY; + + // This boolean informs the J3DGraphics2DImpl that the window is resized + boolean resizeGraphics2D = true; + // + // This boolean allows an application to start and stop the render + // loop on this canvas. + // + volatile boolean isRunning = true; + + // This is used by MasterControl only. MC relay on this in a + // single loop to set renderer thread. During this time, + // the isRunningStatus can't change by user thread. + volatile boolean isRunningStatus = true; + + // This is true when the canvas is ready to be rendered into + boolean active = false; + + // This is true when the canvas is visible + boolean visible = false; + + // This is true if context need to recreate + boolean ctxReset = true; + + // The Screen3D that corresponds to this Canvas3D + Screen3D screen = null; + + // Flag to indicate that image is render completely + // so swap is valid. + boolean imageReady = false; + + + // + // The current fog enable state + // + int fogOn = 0; + + // The 3D Graphics context used for immediate mode rendering + // into this canvas. + GraphicsContext3D graphicsContext3D = null; + boolean waiting = false; + boolean swapDone = false; + + GraphicsConfiguration graphicsConfiguration; + + // The Java 3D Graphics2D object used for Java2D/AWT rendering + // into this Canvas3D + J3DGraphics2DImpl graphics2D = null; + + // Lock used to synchronize the creation of the 2D and 3D + // graphics context objects + Object gfxCreationLock = new Object(); + + // The source of the currently loaded localToVWorld for this Canvas3D + // (used to only update the model matrix when it changes) + // Transform3D localToVWorldSrc = null; + + // The current vworldToEc Transform + Transform3D vworldToEc = new Transform3D(); + + // The view transform (VPC to EC) for the current eye. + // NOTE that this is *read-only* + Transform3D vpcToEc; + + // The window field when running Windows is the native HDC. With X11 it + // is the handle to the native X11 drawable. + int window = 0; + + // The vid field when running Windows is the pixel format. With X11 it is + // the visual id. + int vid = 0; + + // The visInfo field is only used when running X11. It contains a pointer + // to the native XVisualInfo structure, since in some cases the visual id + // alone isn't sufficient for the native OpenGL renderer (e.g., when using + // Solaris OpenGL with Xinerama mode disabled). + long visInfo = 0; + + // visInfoTable is a static hashtable which allows getBestConfiguration() + // in NativeConfigTemplate3D to map a GraphicsConfiguration to the pointer + // to the actual XVisualInfo that glXChooseVisual() returns. The Canvas3D + // doesn't exist at the time getBestConfiguration() is called, and + // X11GraphicsConfig neither maintains this pointer nor provides a public + // constructor to allow Java 3D to extend it. + static Hashtable visInfoTable = new Hashtable(); + + // The native graphics version and renderer information + String nativeGraphicsVersion = null; + + NativeWSInfo nativeWSobj = new NativeWSInfo(); + boolean firstPaintCalled = false; + + // This reflects whether or not this canvas has seen an addNotify. + boolean added = false; + + // This is the id for the underlying graphics context structure. + long ctx = 0; + + // since the ctx id can be the same as the previous one, + // we need to keep a time stamp to differentiate the contexts with the + // same id + volatile long ctxTimeStamp = 0; + + // The current context setting for local eye lighting + boolean ctxEyeLightingEnable = false; + + // This AppearanceRetained Object refelects the current state of this + // canvas. It is used to optimize setting of attributes at render time. + AppearanceRetained currentAppear = new AppearanceRetained(); + + // This MaterialRetained Object refelects the current state of this canvas. + // It is used to optimize setting of attributes at render time. + MaterialRetained currentMaterial = new MaterialRetained(); + + /** + * The object used for View Frustum Culling + */ + CachedFrustum viewFrustum = new CachedFrustum(); + + /** + * The RenderBin bundle references used to decide what the underlying + * context contains. + */ + LightBin lightBin = null; + EnvironmentSet environmentSet = null; + AttributeBin attributeBin = null; + RenderMolecule renderMolecule = null; + PolygonAttributesRetained polygonAttributes = null; + LineAttributesRetained lineAttributes = null; + PointAttributesRetained pointAttributes = null; + MaterialRetained material = null; + boolean enableLighting = false; + TransparencyAttributesRetained transparency = null; + ColoringAttributesRetained coloringAttributes = null; + Transform3D modelMatrix = null; + TextureBin textureBin = null; + + + /** + * cached RenderBin states for lazy native states update + */ + LightRetained lights[] = null; + int frameCount[] = null; + long enableMask = -1; + FogRetained fog = null; + ModelClipRetained modelClip = null; + Color3f sceneAmbient = new Color3f(); + TextureUnitStateRetained texUnitState[] = null; + + /** + * cached View states for lazy native states update + */ + // - DVR support. + float cachedDvrFactor = 1.0f; + boolean cachedDvrResizeCompensation = true; + + /** + * These cached values are only used in Pure Immediate and Mixed Mode rendering + */ + TextureRetained texture = null; + TextureAttributesRetained texAttrs = null; + TexCoordGenerationRetained texCoordGeneration = null; + RenderingAttributesRetained renderingAttrs = null; + AppearanceRetained appearance = null; + + // only used in Mixed Mode rendering + Object appHandle = null; + + /** + * Set to true when any one of texture state use + * Texture Generation linear mode. This is used for D3D + * temporary turn displayList off and do its own coordinate + * generation since D3D don't support it. + */ + boolean texLinearMode = false; + + /** + * Dirty bit to determine if the NodeComponent needs to be re-sent + * down to the underlying API + */ + int canvasDirty = 0xffff; + + // True when either one of dirtyRenderMoleculeList, + // dirtyDlistPerRinfoList, dirtyRenderAtomList size > 0 + boolean dirtyDisplayList = false; + + ArrayList dirtyRenderMoleculeList = new ArrayList(); + ArrayList dirtyRenderAtomList = new ArrayList(); + // List of (Rm, rInfo) pair of individual dlists that need to be rebuilt + ArrayList dirtyDlistPerRinfoList = new ArrayList(); + + ArrayList displayListResourceFreeList = new ArrayList(); + ArrayList textureIdResourceFreeList = new ArrayList(); + + // an unique bit to identify this canvas + int canvasBit = 0; + + // Avoid using this as lock, it cause deadlock + Object cvLock = new Object(); + Object evaluateLock = new Object(); + Object dirtyMaskLock = new Object(); + + // Use by D3D when toggle between window/fullscreen mode. + // Note that in fullscreen mode, the width and height get + // by canvas is smaller than expected. + boolean fullScreenMode = false; + int fullscreenWidth; + int fullscreenHeight; + + // For D3D, instead of using the same variable in Renderer, + // each canvas has to build its own displayList. + boolean needToRebuildDisplayList = false; + + // Use by D3D when canvas resize/toggle in pure immediate mode + int reEvaluateCanvasCmd = 0; + + // Read-only flag that indicates whether the following texture features + // are supported for this canvas. + + static final int TEXTURE_3D = 0x0001; + static final int TEXTURE_COLOR_TABLE = 0x0002; + static final int TEXTURE_MULTI_TEXTURE = 0x0004; + static final int TEXTURE_COMBINE = 0x0008; + static final int TEXTURE_COMBINE_DOT3 = 0x0010; + static final int TEXTURE_COMBINE_SUBTRACT = 0x0020; + static final int TEXTURE_REGISTER_COMBINERS = 0x0040; + static final int TEXTURE_CUBE_MAP = 0x0080; + static final int TEXTURE_SHARPEN = 0x0100; + static final int TEXTURE_DETAIL = 0x0200; + static final int TEXTURE_FILTER4 = 0x0400; + static final int TEXTURE_ANISOTROPIC_FILTER = 0x0800; + static final int TEXTURE_LOD_RANGE = 0x1000; + static final int TEXTURE_LOD_OFFSET = 0x2000; + // Use by D3D to indicate using one pass Blend mode + // if Texture interpolation mode is support. + static final int TEXTURE_LERP = 0x4000; + + int textureExtendedFeatures = 0; + + // Extensions supported by the underlying canvas + static final int SUN_GLOBAL_ALPHA = 0x1; + static final int EXT_ABGR = 0x2; + static final int EXT_BGR = 0x4; + static final int EXT_RESCALE_NORMAL = 0x8; + static final int EXT_MULTI_DRAW_ARRAYS = 0x10; + static final int SUN_MULTI_DRAW_ARRAYS = 0x20; + static final int SUN_CONSTANT_DATA = 0x40; + static final int EXT_SEPARATE_SPECULAR_COLOR = 0x80; + static final int ARB_TRANSPOSE_MATRIX = 0x100; + static final int ARB_MULTISAMPLE = 0x200; + static final int EXT_COMPILED_VERTEX_ARRAYS = 0x400; + static final int SUN_VIDEO_RESIZE = 0x800; + static final int STENCIL_BUFFER = 0x1000; + + // The following three variables are set by + // createContext()/createQueryContext() callback + // Supported Extensions + int extensionsSupported = 0; + + // Anisotropic Filter degree + float anisotropicDegreeMax = 1.0f; + + // Texture Boundary Width Max + int textureBoundaryWidthMax = 0; + + // Texture Width Max + int textureWidthMax = 0; + + // Texture Height Max + int textureHeightMax = 0; + + // Cached position & size for CanvasViewCache. + // We don't want to call canvas.getxx method in Renderer + // since it will cause deadlock as removeNotify() need to get + // component lock of Canvas also and need to wait Renderer to + // finish before continue. So we invoke the method now in + // CanvasViewEventCatcher. + Point newPosition = new Point(); + Dimension newSize = new Dimension(); + + // Remember OGL context resources to free + // before context is destroy. + // It is used when sharedCtx = false; + ArrayList textureIDResourceTable = new ArrayList(5); + + // The following variables are used by the lazy download of + // states code to keep track of the set of current to be update bins + + static final int LIGHTBIN_BIT = 0x0; + static final int ENVIRONMENTSET_BIT = 0x1; + static final int ATTRIBUTEBIN_BIT = 0x2; + static final int TEXTUREBIN_BIT = 0x3; + static final int RENDERMOLECULE_BIT = 0x4; + static final int TRANSPARENCY_BIT = 0x5; + + // bitmask to specify if the corresponding "bin" needs to be updated + int stateUpdateMask = 0; + + // the set of current "bins" that is to be updated, the stateUpdateMask + // specifies if each bin in this set is updated or not. + Object curStateToUpdate[] = new Object[6]; + + + // Native method for determining the number of texture unit supported + native int getTextureUnitCount(long ctx); + + // Native method for determining the texture color table size + // in the underlying API for this Canvas3D. + /* native int getTextureColorTableSize(long ctx); */ + + // This is the native method for creating the underlying graphics context. + native long createContext(long display, int window, int vid, long visInfo, + long shareCtx, boolean isSharedCtx, + boolean offScreen); + + native void createQueryContext(long display, int window, int vid, boolean offScreen, int width, int height); + native static void destroyContext(long display, int window, long context); + + // This is the native for creating offscreen buffer + native int createOffScreenBuffer(long ctx, long display, int vid, int width, int height); + native void destroyOffScreenBuffer(long ctx, long display, int window); + + // This is the native for reading the image from the offscreen buffer + native void readOffScreenBuffer(long ctx, int format, int width, int height); + + // This is the native method for doing accumulation. + native void accum(long ctx, float value ); + + // This is the native method for doing accumulation return. + native void accumReturn(long ctx); + + // This is the native method for clearing the accumulation buffer. + native void clearAccum(long ctx); + + // This is the native method for getting the number of lights the underlying + // native library can support. + native int getNumCtxLights(long ctx); + + // Native method for decal 1st child setup + native boolean decal1stChildSetup(long ctx); + + // Native method for decal nth child setup + native void decalNthChildSetup(long ctx); + + // Native method for decal reset + native void decalReset(long ctx, boolean depthBufferEnable); + + // Native method for decal reset + native void ctxUpdateEyeLightingEnable(long ctx, boolean localEyeLightingEnable); + + // The following three methods are used in multi-pass case + + // Native method for setting the depth func + native void setDepthFunc(long ctx, int func); + + // native method for setting blend color + native void setBlendColor(long ctx, float red, float green, + float blue, float alpha); + + // native method for setting blend func + native void setBlendFunc(long ctx, int src, int dst); + + // native method for setting fog enable flag + native void setFogEnableFlag(long ctx, boolean enableFlag); + + // Setup the full scene antialising in D3D and ogl when GL_ARB_multisamle supported + native void setFullSceneAntialiasing(long ctx, boolean enable); + + // notify D3D that Canvas is resize + native int resizeD3DCanvas(long ctx); + + // notify D3D to toggle between FullScreen and window mode + native int toggleFullScreenMode(long ctx); + + native void setGlobalAlpha(long ctx, float alpha); + native void disableGlobalAlpha(long ctx); + + // Native method to update separate specular color control + native void updateSeparateSpecularColorEnable(long ctx, boolean control); + + // Initialization for D3D when scene begin + native void beginScene(long ctx); + native void endScene(long ctx); + + // True under Solaris, + // False under windows when display mode <= 8 bit + native boolean validGraphicsMode(); + + /** + * The list of lights that are currently being represented in the native + * graphics context. + */ + LightRetained[] currentLights = null; + + /** + * Flag to override RenderAttributes.depthBufferWriteEnable + */ + boolean depthBufferWriteEnableOverride = false; + + /** + * Flag to override RenderAttributes.depthBufferEnable + */ + boolean depthBufferEnableOverride = false; + + // current state of depthBufferWriteEnable + boolean depthBufferWriteEnable = true; + + boolean vfPlanesValid = false; + + // The event catcher for this canvas. + EventCatcher eventCatcher; + + // The view event catcher for this canvas. + CanvasViewEventCatcher canvasViewEventCatcher; + + // The parent window for this canvas. + Container parent; + + // flag that indicates if light has changed + boolean lightChanged = false; + + // resource control object + DrawingSurfaceObject drawingSurfaceObject; + + // true if context is valid for rendering + boolean validCtx = false; + + // true if canvas is valid for rendering + boolean validCanvas = false; + + // true if ctx changed between render and swap. In this case + // cv.canvasDirty flag will not reset in Renderer. + // This case happen when GraphicsContext3D invoked doClear() + // and canvas removeNotify() called while Renderer is running + boolean ctxChanged = false; + + // native method for setting light enables + native void setLightEnables(long ctx, long enableMask, int maxLights); + + // native method for setting scene ambient + native void setSceneAmbient(long ctx, float red, float green, float blue); + + // native method for disabling fog + native void disableFog(long ctx); + + // native method for disabling modelClip + native void disableModelClip(long ctx); + + // native method for setting default RenderingAttributes + native void resetRenderingAttributes(long ctx, + boolean depthBufferWriteEnableOverride, + boolean depthBufferEnableOverride); + + // native method for setting default texture + native void resetTextureNative(long ctx, int texUnitIndex); + + // native method for activating a particular texture unit + native void activeTextureUnit(long ctx, int texUnitIndex); + + // native method for setting default TexCoordGeneration + native void resetTexCoordGeneration(long ctx); + + // native method for setting default TextureAttributes + native void resetTextureAttributes(long ctx); + + // native method for setting default PolygonAttributes + native void resetPolygonAttributes(long ctx); + + // native method for setting default LineAttributes + native void resetLineAttributes(long ctx); + + // native method for setting default PointAttributes + native void resetPointAttributes(long ctx); + + // native method for setting default TransparencyAttributes + native void resetTransparency(long ctx, int geometryType, + int polygonMode, boolean lineAA, + boolean pointAA); + + // native method for setting default ColoringAttributes + native void resetColoringAttributes(long ctx, + float r, float g, + float b, float a, + boolean enableLight); + + // native method for setting Material when no material is present + native void updateMaterial(long ctx, float r, float g, float b, float a); + + + // native method for updating the texture unit state map + native void updateTexUnitStateMap(long ctx, int numActiveTexUnit, + int[] texUnitStateMap); + + /** + * This native method makes sure that the rendering for this canvas + * gets done now. + */ + native void syncRender(long ctx, boolean wait); + + // Default graphics configuration + private static GraphicsConfiguration defaultGcfg = null; + + // Returns default graphics configuration if user passes null + // into the Canvas3D constructor + private static synchronized GraphicsConfiguration defaultGraphicsConfiguration() { + if (defaultGcfg == null) { + GraphicsConfigTemplate3D template = new GraphicsConfigTemplate3D(); + defaultGcfg = GraphicsEnvironment.getLocalGraphicsEnvironment(). + getDefaultScreenDevice().getBestConfiguration(template); + } + return defaultGcfg; + } + + private static synchronized GraphicsConfiguration + checkForValidGraphicsConfig(GraphicsConfiguration gconfig) { + + if (gconfig == null) { + // Print out warning if Canvas3D is called with a + // null GraphicsConfiguration + System.err.println("************************************************************************"); + System.err.println(J3dI18N.getString("Canvas3D7")); + System.err.println(J3dI18N.getString("Canvas3D18")); + System.err.println("************************************************************************"); + return defaultGraphicsConfiguration(); + } + + if (!J3dGraphicsConfig.isValidConfig(gconfig)) { + // Print out warning if Canvas3D is called with a + // GraphicsConfiguration that wasn't created from a + // GraphicsConfigTemplate3D (Solaris only). + System.err.println("************************************************************************"); + System.err.println(J3dI18N.getString("Canvas3D21")); + System.err.println(J3dI18N.getString("Canvas3D22")); + System.err.println("************************************************************************"); + return defaultGraphicsConfiguration(); + } + + return gconfig; + } + + /** + * Constructs and initializes a new Canvas3D object that Java 3D + * can render into. The following Canvas3D attributes are initialized + * to default values as shown: + * <ul> + * left manual eye in image plate : (0.142, 0.135, 0.4572)<br> + * right manual eye in image plate : (0.208, 0.135, 0.4572)<br> + * stereo enable : true<br> + * double buffer enable : true<br> + * monoscopic view policy : View.CYCLOPEAN_EYE_VIEW<br> + * off-screen mode : false<br> + * off-screen buffer : null<br> + * off-screen location : (0,0)<br> + * </ul> + * + * @param graphicsConfiguration a valid GraphicsConfiguration object that + * will be used to create the canvas. This object should not be null and + * should be created using a GraphicsConfigTemplate3D or the + * getPreferredConfiguration() method of the SimpleUniverse utility. For + * backward compatibility with earlier versions of Java 3D, a null or + * default GraphicsConfiguration will still work when used to create a + * Canvas3D on the default screen, but an error message will be printed. + * A NullPointerException or IllegalArgumentException will be thrown in a + * subsequent release. + * + * @exception IllegalArgumentException if the specified + * GraphicsConfiguration does not support 3D rendering + */ + public Canvas3D(GraphicsConfiguration graphicsConfiguration) { + this(checkForValidGraphicsConfig(graphicsConfiguration), false); + + // TODO 1.4: remove call to checkForValidGraphicsConfig. + // Call should then be: + // this(graphicsConfiguration, false); + } + + /** + * Constructs and initializes a new Canvas3D object that Java 3D + * can render into. + * + * @param graphicsConfiguration a valid GraphicsConfiguration object + * that will be used to create the canvas. This must be created either + * with a GraphicsConfigTemplate3D or by using the + * getPreferredConfiguration() method of the SimpleUniverse utility. + * + * @param offScreen a flag that indicates whether this canvas is + * an off-screen 3D rendering canvas. Note that if offScreen + * is set to true, this Canvas3D object cannot be used for normal + * rendering; it should not be added to any Container object. + * + * @exception NullPointerException if the GraphicsConfiguration + * is null. + * + * @exception IllegalArgumentException if the specified + * GraphicsConfiguration does not support 3D rendering + * + * @since Java 3D 1.2 + */ + public Canvas3D(GraphicsConfiguration graphicsConfiguration, + boolean offScreen) { + + super(graphicsConfiguration); + + if (graphicsConfiguration == null) { + throw new NullPointerException + (J3dI18N.getString("Canvas3D19")); + } + + if (!J3dGraphicsConfig.isValidConfig(graphicsConfiguration)) { + throw new IllegalArgumentException + (J3dI18N.getString("Canvas3D17")); + } + + if (!J3dGraphicsConfig.isValidPixelFormat(graphicsConfiguration)) { + throw new IllegalArgumentException + (J3dI18N.getString("Canvas3D17")); + } + + VirtualUniverse.createMC(); + + this.offScreen = offScreen; + this.graphicsConfiguration = graphicsConfiguration; + + Object visInfoObject; + vid = nativeWSobj.getCanvasVid(graphicsConfiguration); + visInfoObject = visInfoTable.get(graphicsConfiguration); + + if ((visInfoObject != null) && (visInfoObject instanceof Long)) { + visInfo = ((Long)visInfoObject).longValue(); + } + + if (offScreen) { + screen = new Screen3D(graphicsConfiguration, offScreen); + + // TODO: keep a list of off-screen Screen3D objects? + // Does this list need to be grouped by GraphicsDevice? + + + // since this canvas will not receive the addNotify + // callback from AWT, set the added flag here + added = true; + synchronized(dirtyMaskLock) { + cvDirtyMask |= Canvas3D.MOVED_OR_RESIZED_DIRTY; + } + + // this canvas will not receive the paint callback either, + // so set the necessary flags here as well + firstPaintCalled = true; + ctx = 0; + evaluateActive(); + + // create the rendererStructure object + //rendererStructure = new RendererStructure(); + offScreenCanvasLoc = new Point(0, 0); + offScreenCanvasSize = new Dimension(0, 0); + offScreenCanvasClippedLoc = new Point(0, 0); + offScreenCanvasClippedSize = new Dimension(0, 0); + + this.setLocation(offScreenCanvasLoc); + this.setSize(offScreenCanvasSize); + newSize = offScreenCanvasSize; + newPosition = offScreenCanvasLoc; + + } else { + GraphicsDevice graphicsDevice; + graphicsDevice = graphicsConfiguration.getDevice(); + + eventCatcher = new EventCatcher(this); + canvasViewEventCatcher = new CanvasViewEventCatcher(this); + + synchronized(VirtualUniverse.mc.deviceScreenMap) { + screen = (Screen3D) VirtualUniverse.mc. + deviceScreenMap.get(graphicsDevice); + + if (screen == null) { + screen = new Screen3D(graphicsConfiguration, offScreen); + VirtualUniverse.mc.deviceScreenMap.put(graphicsDevice, + screen); + } + } + + } + + drawingSurfaceObject = new DrawingSurfaceObjectAWT + (this, VirtualUniverse.mc.awt, screen.display, screen.screen, + VirtualUniverse.mc.xineramaDisabled); + + lights = new LightRetained[VirtualUniverse.mc.maxLights]; + frameCount = new int[VirtualUniverse.mc.maxLights]; + for (int i=0; i<frameCount.length;i++) { + frameCount[i] = -1; + } + + // Get double buffer, stereo available, scene antialiasing + // flags from template. + GraphicsConfigTemplate3D.getGraphicsConfigFeatures(this); + + useDoubleBuffer = doubleBufferEnable && doubleBufferAvailable; + useStereo = stereoEnable && stereoAvailable; + useSharedCtx = VirtualUniverse.mc.isSharedCtx; + + } + + /** + * This method overrides AWT's handleEvent class... + */ + void sendEventToBehaviorScheduler(AWTEvent evt) { + + ViewPlatform vp; + + + if ((view != null) && ((vp = view.getViewPlatform()) != null)) { + VirtualUniverse univ = + ((ViewPlatformRetained)(vp.retained)).universe; + if (univ != null) { + univ.behaviorStructure.handleAWTEvent(evt); + } + } + } + + /** + * This version looks for the view and notifies it. + */ + void redraw() { + if ((view != null) && active && isRunning) { + view.repaint(); + } + } + + /** + * Canvas3D uses the paint callback to track when it is possible to + * render into the canvas. Subclasses of Canvas3D that override this + * method need to call super.paint() in their paint method for Java 3D + * to function properly. + * @param g the graphics context + */ + public void paint(Graphics g) { + + if (!firstPaintCalled && added && validCanvas && + validGraphicsMode()) { + + try { + newSize = getSize(); + newPosition = getLocationOnScreen(); + } catch (IllegalComponentStateException e) { + return; + } + + synchronized (drawingSurfaceObject) { + drawingSurfaceObject.getDrawingSurfaceObjectInfo(); + } + + firstPaintCalled = true; + visible = true; + evaluateActive(); + } + redraw(); + } + + // When this canvas is added to a frame, this notification gets called. We + // can get drawing surface information at this time. Note: we cannot get + // the X11 window id yet, unless it is a reset condition. + /** + * Canvas3D uses the addNotify callback to track when it is added + * to a container. Subclasses of Canvas3D that override this + * method need to call super.addNotify() in their addNotify() method for Java 3D + * to function properly. + */ + public void addNotify() { + Renderer rdr = null; + + if (isRunning && (screen != null)) { + // If there is other Canvas3D in the same screen + // rendering, stop it before JDK create new Canvas + + rdr = screen.renderer; + if (rdr != null) { + VirtualUniverse.mc.postRequest(MasterControl.STOP_RENDERER, rdr); + while (!rdr.userStop) { + MasterControl.threadYield(); + } + } + } + + + super.addNotify(); + screen.addUser(this); + + parent = this.getParent(); + while (!(parent instanceof Window)) { + parent = parent.getParent(); + } + + ((Window)parent).addWindowListener(eventCatcher); + + if (VirtualUniverse.mc.isD3D()) { + ((Window)parent).addComponentListener(eventCatcher); + } + + if (!offScreen) { + if(canvasViewEventCatcher.parentList.size() > 0) { + Component comp; + //Release and clear. + for(int i=0; i<canvasViewEventCatcher.parentList.size(); i++) { + comp = (Component)canvasViewEventCatcher.parentList.get(i); + comp.removeComponentListener(canvasViewEventCatcher); + } + canvasViewEventCatcher.parentList.clear(); + } + + Component parent = (Component) this.getParent(); + while(parent != null) { + parent.addComponentListener(canvasViewEventCatcher); + canvasViewEventCatcher.parentList.add(parent); + parent = parent.getParent(); + } + // Need to traverse up the parent and add listener. + this.addComponentListener(canvasViewEventCatcher); + } + + synchronized(dirtyMaskLock) { + cvDirtyMask |= Canvas3D.MOVED_OR_RESIZED_DIRTY; + } + + canvasBit = VirtualUniverse.mc.getCanvasBit(); + validCanvas = true; + added = true; + + // In case the same canvas is removed and add back, + // we have to change isRunningStatus back to true; + if (isRunning) { + isRunningStatus = true; + } + + + if (rdr != null) { + rdr.userStop = false; + } + ctxTimeStamp = 0; + if ((view != null) && (view.universe != null)) { + view.universe.checkForEnableEvents(); + } + + } + + // When this canvas is removed a frame, this notification gets called. We + // need to release the native context at this time. The underlying window + // is about to go away. + /** + * Canvas3D uses the removeNotify callback to track when it is removed + * from a container. Subclasses of Canvas3D that override this + * method need to call super.removeNotify() in their removeNotify() + * method for Java 3D to function properly. + */ + + public void removeNotify() { + + Renderer rdr = null; + + if (isRunning && (screen != null)) { + // If there is other Canvas3D in the same screen + // rendering, stop it before JDK create new Canvas + + rdr = screen.renderer; + if (rdr != null) { + VirtualUniverse.mc.postRequest(MasterControl.STOP_RENDERER, rdr); + while (!rdr.userStop) { + MasterControl.threadYield(); + } + } + } + + if (!offScreen) { + firstPaintCalled = false; + } + + + // Note that although renderer userStop is true, + // MasterControl can still schedule renderer to run through + // runMonotor(RUN_RENDERER_CLEANUP) which skip userStop + // thread checking. + // For non-offscreen rendering the following call will + // block waiting until all resources is free before + // continue + + synchronized (drawingSurfaceObject) { + validCtx = false; + validCanvas = false; + } + + removeCtx(false); + + synchronized (drawingSurfaceObject) { + + DrawingSurfaceObjectAWT dso = + (DrawingSurfaceObjectAWT)drawingSurfaceObject; + // get nativeDS before it is set to 0 in invalidate() + long ds = dso.getDS(); + long ds_struct[] = {ds, dso.getDSI()}; + if (ds != 0) { + VirtualUniverse.mc.postRequest( + MasterControl.FREE_DRAWING_SURFACE, + ds_struct); + } + + drawingSurfaceObject.invalidate(); + + } + + visible = false; + + screen.removeUser(this); + evaluateActive(); + + VirtualUniverse.mc.freeCanvasBit(canvasBit); + + ra = null; + graphicsContext3D = null; + + ctx = 0; + // must be after removeCtx() because + // it will free graphics2D textureID + graphics2D = null; + + super.removeNotify(); + + // This may happen if user explicity call this before + // addNotify() + + if (eventCatcher != null) { + this.removeComponentListener(eventCatcher); + this.removeFocusListener(eventCatcher); + this.removeKeyListener(eventCatcher); + this.removeMouseListener(eventCatcher); + this.removeMouseMotionListener(eventCatcher); + eventCatcher.reset(); + } + + + if (canvasViewEventCatcher != null) { + if (canvasViewEventCatcher.parentList.size() > 0) { + //Release and clear. + for(int i=canvasViewEventCatcher.parentList.size()-1; i>=0; i--) { + Component comp = + (Component)canvasViewEventCatcher.parentList.get(i); + comp.removeComponentListener(canvasViewEventCatcher); + } + canvasViewEventCatcher.parentList.clear(); + } + + // Need to traverse up the parent and remove listener. + this.removeComponentListener(canvasViewEventCatcher); + } + + if (parent != null) { + ((Window)parent).removeWindowListener(eventCatcher); + if (VirtualUniverse.mc.isD3D()) { + ((Window)parent).removeComponentListener(eventCatcher); + } + } + + if (parent != null) { + parent.requestFocus(); + } + + if (!offScreen) { + added = false; + } + + if (rdr != null) { + rdr.userStop = false; + } + } + + // This decides if the canvas is active + void evaluateActive() { + // Note that no need to check for isRunning, we want + // view register in order to create scheduler in pure immedite mode + // Also we can't use this as lock, otherwise there is a + // deadlock where updateViewCache get a lock of this and + // get a lock of this component. But Container + // remove will get a lock of this componet follows by evaluateActive. + + synchronized (evaluateLock) { + if ((visible || offScreen) && firstPaintCalled) { + + if (!active) { + active = true; + if (pendingView != null) { + pendingView.evaluateActive(); + } + } else { + if ((pendingView != null) && + !pendingView.activeStatus) { + pendingView.evaluateActive(); + } + } + } else { + if (active) { + active = false; + if (view != null) { + view.evaluateActive(); + } + } + } + } + + if ((view != null) && (!active)) { + VirtualUniverse u = view.universe; + if ((u != null) && !u.isSceneGraphLock) { + u.waitForMC(); + } + } + } + + void setFrustumPlanes(Vector4d[] planes) { + viewFrustum.set(planes); + } + + + /** + * Retrieve the Screen3D object that this Canvas3D is attached to. + * If this Canvas3D is an off-screen buffer, a new Screen3D object + * is created corresponding to the off-screen buffer. + * @return the 3D screen object that this Canvas3D is attached to + */ + public Screen3D getScreen3D() { + return screen; + } + + /** + * Get the immediate mode 3D graphics context associated with + * this Canvas3D. A new graphics context object is created if one does + * not already exist. + * @return a GraphicsContext3D object that can be used for immediate + * mode rendering to this Canvas3D. + */ + public GraphicsContext3D getGraphicsContext3D() { + + synchronized(gfxCreationLock) { + if (graphicsContext3D == null) + graphicsContext3D = new GraphicsContext3D(this); + } + + return graphicsContext3D; + } + + /** + * Get the 2D graphics object associated with + * this Canvas3D. A new 2D graphics object is created if one does + * not already exist. + * + * @return a Graphics2D object that can be used for Java 2D + * rendering into this Canvas3D. + * + * @since Java 3D 1.2 + */ + public J3DGraphics2D getGraphics2D() { + synchronized(gfxCreationLock) { + if (graphics2D == null) + graphics2D = new J3DGraphics2DImpl(this); + } + + return graphics2D; + } + + /** + * This routine is called by the Java 3D rendering loop after clearing + * the canvas and before any rendering has been done for this frame. + * Applications that wish to perform operations in the rendering loop, + * prior to any actual rendering may override this function. + * + * <p> + * Updates to live Geometry, Texture, and ImageComponent objects + * in the scene graph are not allowed from this method. + * + * <p> + * NOTE: Applications should <i>not</i> call this method. + */ + public void preRender() { + // Do nothing; the user overrides this to cause some action + } + + /** + * This routine is called by the Java 3D rendering loop after completing + * all rendering to the canvas for this frame and before the buffer swap. + * Applications that wish to perform operations in the rendering loop, + * following any actual rendering may override this function. + * + * <p> + * Updates to live Geometry, Texture, and ImageComponent objects + * in the scene graph are not allowed from this method. + * + * <p> + * NOTE: Applications should <i>not</i> call this method. + */ + public void postRender() { + // Do nothing; the user overrides this to cause some action + } + + /** + * This routine is called by the Java 3D rendering loop after completing + * all rendering to the canvas, and all other canvases associated with + * this view, for this frame following the buffer swap. + * Applications that wish to perform operations at the very + * end of the rendering loop may override this function. + * In off-screen mode, all rendering is copied to the off-screen + * buffer before this method is called. + * + * <p> + * Updates to live Geometry, Texture, and ImageComponent objects + * in the scene graph are not allowed from this method. + * + * <p> + * NOTE: Applications should <i>not</i> call this method. + */ + public void postSwap() { + // Do nothing; the user overrides this to cause some action + } + + /** + * This routine is called by the Java 3D rendering loop during the + * execution of the rendering loop. It is called once for each + * field (i.e., once per frame on + * a mono system or once each for the right eye and left eye on a + * two-pass stereo system. This is intended for use by applications that + * want to mix retained/compiled-retained mode rendering with some + * immediate mode rendering. Applications that wish to perform + * operations during the rendering loop, may override this + * function. + * + * <p> + * Updates to live Geometry, Texture, and ImageComponent objects + * in the scene graph are not allowed from this method. + * + * <p> + * NOTE: Applications should <i>not</i> call this method. + * <p> + * + * @param fieldDesc field description, one of: FIELD_LEFT, FIELD_RIGHT or + * FIELD_ALL. Applications that wish to work correctly in stereo mode + * should render the same image for both FIELD_LEFT and FIELD_RIGHT calls. + * If Java 3D calls the renderer with FIELD_ALL then the immediate mode + * rendering only needs to be done once. + */ + public void renderField(int fieldDesc) { + // Do nothing; the user overrides this to cause some action + } + + /** + * Stop the Java 3D renderer on this Canvas3D object. If the + * Java 3D renderer is currently running, the rendering will be + * synchronized before being stopped. No further rendering will be done + * to this canvas by Java 3D until the renderer is started again. + * In pure immediate mode this method should be called prior to adding + * this canvas to an active View object. + * + * @exception IllegalStateException if this Canvas3D is in + * off-screen mode. + */ + public final void stopRenderer() { + if (offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D14")); + + if (isRunning) { + VirtualUniverse.mc.postRequest(MasterControl.STOP_RENDERER, this); + isRunning = false; + } + } + + + /** + * Start the Java 3D renderer on this Canvas3D object. If the + * Java 3D renderer is not currently running, any rendering to other + * Canvas3D objects sharing the same View will be synchronized before this + * Canvas3D's renderer is (re)started. When a Canvas3D is created, it is + * initially marked as being started. This means that as soon as the + * Canvas3D is added to an active View object, the rendering loop will + * render the scene graph to the canvas. + */ + public final void startRenderer() { + if (!isRunning) { + VirtualUniverse.mc.postRequest(MasterControl.START_RENDERER, this); + isRunning = true; + redraw(); + } + } + + /** + * Retrieves the state of the renderer for this Canvas3D object. + * @return the state of the renderer + * + * @since Java 3D 1.2 + */ + public final boolean isRendererRunning() { + return isRunning; + } + + + /** + * Retrieves a flag indicating whether this Canvas3D is an + * off-screen canvas. + * + * @return <code>true</code> if this Canvas3D is an off-screen canvas; + * <code>false</code> if this is an on-screen canvas. + * + * @since Java 3D 1.2 + */ + public boolean isOffScreen() { + return offScreen; + } + + + /** + * Sets the off-screen buffer for this Canvas3D. The specified + * image is written into by the Java 3D renderer. The size of the + * specified ImageComponent determines the size, in pixels, of + * this Canvas3D--the size inherited from Component is ignored. + * <p> + * NOTE: the size, physical width, and physical height of the associated + * Screen3D must be set explicitly prior to rendering. + * Failure to do so will result in an exception. + * <p> + * + * @param buffer the image component that will be rendered into by + * subsequent calls to renderOffScreenBuffer. + * + * @exception IllegalStateException if this Canvas3D is not in + * off-screen mode. + * @exception RestrictedAccessException if an off-screen rendering + * is in process for this Canvas3D. + * @exception IllegalSharingException if the specified + * ImageComponent2D is used by more than one Canvas3D. + * @exception IllegalArgumentException if the specified + * ImageComponent2D is in by-reference mode and its + * RenderedImage is not an instance of a BufferedImage or + * if the ImageComponent2D format is FORMAT_CHANNEL8. + * + * @see #renderOffScreenBuffer + * @see Screen3D#setSize(int, int) + * @see Screen3D#setSize(Dimension) + * @see Screen3D#setPhysicalScreenWidth + * @see Screen3D#setPhysicalScreenHeight + * + * @since Java 3D 1.2 + */ + public void setOffScreenBuffer(ImageComponent2D buffer) { + ImageComponent2DRetained bufferRetained = + (ImageComponent2DRetained)buffer.retained; + + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D1")); + + if (offScreenRendering) + throw new RestrictedAccessException(J3dI18N.getString("Canvas3D2")); + + if (bufferRetained.byReference && + !(bufferRetained.bImage[0] instanceof BufferedImage)) { + + throw new IllegalArgumentException(J3dI18N.getString("Canvas3D15")); + } + + if (bufferRetained.format == ImageComponent.FORMAT_CHANNEL8) { + throw new IllegalArgumentException(J3dI18N.getString("Canvas3D16")); + } + + // TODO: illegalSharing + + if ((offScreenCanvasSize.width != bufferRetained.width) || + (offScreenCanvasSize.height != bufferRetained.height)) { + + if (window != 0) { + destroyOffScreenBuffer(ctx, screen.display, window); + removeCtx(true); + window = 0; + } + + // set the canvas dimension according to the buffer dimension + offScreenCanvasSize.setSize(bufferRetained.width, + bufferRetained.height); + this.setSize(offScreenCanvasSize); + + // create off screen buffer + window = createOffScreenBuffer(ctx, screen.display, vid, + offScreenCanvasSize.width, offScreenCanvasSize.height); + ctx = 0; + } + if (ctx != 0) { + removeCtx(true); + } + + offScreenBuffer = buffer; + + synchronized(dirtyMaskLock) { + cvDirtyMask |= Canvas3D.MOVED_OR_RESIZED_DIRTY; + } + + } + + + /** + * Retrieves the off-screen buffer for this Canvas3D. + * + * @return the current off-screen buffer for this Canvas3D. + * + * @exception IllegalStateException if this Canvas3D is not in + * off-screen mode. + * + * @since Java 3D 1.2 + */ + public ImageComponent2D getOffScreenBuffer() { + + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D1")); + + return (offScreenBuffer); + } + + + /** + * Schedules the rendering of a frame into this Canvas3D's + * off-screen buffer. The rendering is done from the point of + * view of the View object to which this Canvas3D has been added. + * No rendering is performed if this Canvas3D object has not been + * added to an active View. This method does not wait for the rendering + * to actually happen. An application that wishes to know when + * the rendering is complete must either subclass Canvas3D and + * override the <code>postSwap</code> method, or call + * <code>waitForOffScreenRendering</code>. + * + * @exception NullPointerException if the off-screen buffer is null. + * @exception IllegalStateException if this Canvas3D is not in + * off-screen mode, or if either the width or the height of + * the associated Screen3D's size is <= 0, or if the associated + * Screen3D's physical width or height is <= 0. + * @exception RestrictedAccessException if an off-screen rendering + * is already in process for this Canvas3D or if the Java 3D renderer + * is stopped. + * + * @see #setOffScreenBuffer + * @see Screen3D#setSize(int, int) + * @see Screen3D#setSize(Dimension) + * @see Screen3D#setPhysicalScreenWidth + * @see Screen3D#setPhysicalScreenHeight + * @see #waitForOffScreenRendering + * @see #postSwap + * + * @since Java 3D 1.2 + */ + public void renderOffScreenBuffer() { + + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D1")); + + if (offScreenBuffer == null) + throw new NullPointerException(J3dI18N.getString("Canvas3D10")); + + Dimension screenSize = screen.getSize(); + + if (screenSize.width <= 0) + throw new IllegalStateException(J3dI18N.getString("Canvas3D8")); + + if (screenSize.height <= 0) + throw new IllegalStateException(J3dI18N.getString("Canvas3D9")); + + if (screen.getPhysicalScreenWidth() <= 0.0) + throw new IllegalStateException(J3dI18N.getString("Canvas3D12")); + + if (screen.getPhysicalScreenHeight() <= 0.0) + throw new IllegalStateException(J3dI18N.getString("Canvas3D13")); + + if (offScreenRendering) + throw new RestrictedAccessException(J3dI18N.getString("Canvas3D2")); + + if (!isRunning) + throw new RestrictedAccessException(J3dI18N.getString("Canvas3D11")); + + if (!active) + return; + + // determine the offScreen boundary + // do the boundary determination here because setCanvasLocation can + // be done at any time. + + if ((offScreenCanvasLoc.x >= screenSize.width) || + (offScreenCanvasLoc.y >= screenSize.height)) + return; + + if (offScreenCanvasLoc.x < 0) { + offScreenCanvasClippedLoc.x = 0 - offScreenCanvasLoc.x; + offScreenCanvasClippedSize.width = + offScreenCanvasSize.width - offScreenCanvasClippedLoc.x; + if (offScreenCanvasClippedSize.width > screenSize.width) + offScreenCanvasClippedSize.width = screenSize.width; + } else { + offScreenCanvasClippedLoc.x = 0; + offScreenCanvasClippedSize.width = offScreenCanvasSize.width; + if ((offScreenCanvasLoc.x + offScreenCanvasClippedSize.width) + > screenSize.width) + offScreenCanvasClippedSize.width = + screenSize.width - offScreenCanvasLoc.x; + } + + + int lly = offScreenCanvasLoc.y + offScreenCanvasSize.height; + if (lly < 0) { + return; + } else if (lly <= screenSize.height) { + offScreenCanvasClippedLoc.y = 0; + if (offScreenCanvasLoc.y < 0) + offScreenCanvasClippedSize.height = lly; + else + offScreenCanvasClippedSize.height = offScreenCanvasSize.height; + } else if (lly > screenSize.height) { + offScreenCanvasClippedSize.height = + screenSize.height - offScreenCanvasLoc.y; + offScreenCanvasClippedLoc.y = lly - screenSize.height; + } + + offScreenRendering = true; + + if (view.inCanvasCallback) { + if (screen.renderer == null) { + // It is possible that screen.renderer = null when this View + // is shared by another onScreen Canvas and this callback + // is from that Canvas. In this case it need one more + // round before the renderer. + screen.renderer = (Renderer) screen.deviceRendererMap.get( + screen.graphicsDevice); + // screen.renderer may equal to null when multiple + // screen is used and this Canvas3D is in different + // screen sharing the same View not yet initialize. + } + + // if called from render call back, send a message directly to + // the renderer message queue, and call renderer doWork + // to do the offscreen rendering now + if (Thread.currentThread() == screen.renderer) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.RENDER_THREAD; + createMessage.type = J3dMessage.RENDER_OFFSCREEN; + createMessage.universe = this.view.universe; + createMessage.view = this.view; + createMessage.args[0] = this; + + screen.renderer.rendererStructure.addMessage(createMessage); + + // modify the args to reflect offScreen rendering + screen.renderer.args = new Object[4]; + ((Object[])screen.renderer.args)[0] = + new Integer(Renderer.REQUESTRENDER); + ((Object[])screen.renderer.args)[1] = this; + ((Object[])screen.renderer.args)[2] = view; + // This extra argument 3 is needed in MasterControl to + // test whether offscreen Rendering is used or not + ((Object[])screen.renderer.args)[3] = null; + + // call renderer doWork directly since we are already in + // the renderer thread + screen.renderer.doWork(0); + } else { + // TODO: + // Now we are in trouble, this will cause deadlock if + // waitForOffScreenRendering() is invoked + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.RENDER_THREAD; + createMessage.type = J3dMessage.RENDER_OFFSCREEN; + createMessage.universe = this.view.universe; + createMessage.view = this.view; + createMessage.args[0] = this; + screen.renderer.rendererStructure.addMessage(createMessage); + VirtualUniverse.mc.setWorkForRequestRenderer(); + } + + } else if (Thread.currentThread() instanceof BehaviorScheduler) { + // If called from behavior scheduler, send a message directly to + // the renderer message queue. + // Note that we didn't use + // currentThread() == view.universe.behaviorScheduler + // since the caller may be another universe Behavior + // scheduler. + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.RENDER_THREAD; + createMessage.type = J3dMessage.RENDER_OFFSCREEN; + createMessage.universe = this.view.universe; + createMessage.view = this.view; + createMessage.args[0] = this; + screen.renderer.rendererStructure.addMessage(createMessage); + VirtualUniverse.mc.setWorkForRequestRenderer(); + + } else { + // send a message to renderBin + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.RENDER_OFFSCREEN; + createMessage.universe = this.view.universe; + createMessage.view = this.view; + createMessage.args[0] = this; + createMessage.args[1] = offScreenBuffer; + VirtualUniverse.mc.processMessage(createMessage); + } + } + + + /** + * Waits for this Canvas3D's off-screen rendering to be done. + * This method will wait until the <code>postSwap</code> method of this + * off-screen Canvas3D has completed. If this Canvas3D has not + * been added to an active view or if the renderer is stopped for this + * Canvas3D, then this method will return + * immediately. This method must not be called from a render + * callback method of an off-screen Canvas3D. + * + * @exception IllegalStateException if this Canvas3D is not in + * off-screen mode, or if this method is called from a render + * callback method of an off-screen Canvas3D. + * + * @see #renderOffScreenBuffer + * @see #postSwap + * + * @since Java 3D 1.2 + */ + public void waitForOffScreenRendering() { + while (offScreenRendering) { + MasterControl.threadYield(); + } + } + + + /** + * Sets the location of this off-screen Canvas3D. The location is + * the upper-left corner of the Canvas3D relative to the + * upper-left corner of the corresponding off-screen Screen3D. + * The function of this method is similar to that of + * <code>Component.setLocation</code> for on-screen Canvas3D + * objects. The default location is (0,0). + * + * @param x the <i>x</i> coordinate of the upper-left corner of + * the new location. + * @param y the <i>y</i> coordinate of the upper-left corner of + * the new location. + * + * @exception IllegalStateException if this Canvas3D is not in + * off-screen mode. + * + * @since Java 3D 1.2 + */ + public void setOffScreenLocation(int x, int y) { + + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D1")); + + synchronized(cvLock) { + offScreenCanvasLoc.setLocation(x, y); + } + } + + + /** + * Sets the location of this off-screen Canvas3D. The location is + * the upper-left corner of the Canvas3D relative to the + * upper-left corner of the corresponding off-screen Screen3D. + * The function of this method is similar to that of + * <code>Component.setLocation</code> for on-screen Canvas3D + * objects. The default location is (0,0). + * + * @param p the point defining the upper-left corner of the new + * location. + * + * @exception IllegalStateException if this Canvas3D is not in + * off-screen mode. + * + * @since Java 3D 1.2 + */ + public void setOffScreenLocation(Point p) { + + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D1")); + + synchronized(cvLock) { + offScreenCanvasLoc.setLocation(p); + } + } + + + /** + * Retrieves the location of this off-screen Canvas3D. The + * location is the upper-left corner of the Canvas3D relative to + * the upper-left corner of the corresponding off-screen Screen3D. + * The function of this method is similar to that of + * <code>Component.getLocation</code> for on-screen Canvas3D + * objects. + * + * @return a new point representing the upper-left corner of the + * location of this off-screen Canvas3D. + * + * @exception IllegalStateException if this Canvas3D is not in + * off-screen mode. + * + * @since Java 3D 1.2 + */ + public Point getOffScreenLocation() { + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D1")); + + return (new Point(offScreenCanvasLoc)); + } + + + /** + * Retrieves the location of this off-screen Canvas3D and stores + * it in the specified Point object. The location is the + * upper-left corner of the Canvas3D relative to the upper-left + * corner of the corresponding off-screen Screen3D. The function + * of this method is similar to that of + * <code>Component.getLocation</code> for on-screen Canvas3D + * objects. This version of <code>getOffScreenLocation</code> is + * useful if the caller wants to avoid allocating a new Point + * object on the heap. + * + * @param rv Point object into which the upper-left corner of the + * location of this off-screen Canvas3D is copied. + * If <code>rv</code> is null, a new Point is allocated. + * + * @return <code>rv</code> + * + * @exception IllegalStateException if this Canvas3D is not in + * off-screen mode. + * + * @since Java 3D 1.2 + */ + public Point getOffScreenLocation(Point rv) { + + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D1")); + + if (rv == null) + return (new Point(offScreenCanvasLoc)); + + else { + rv.setLocation(offScreenCanvasLoc); + return rv; + } + } + + void endOffScreenRendering() { + + + + + // Evaluate what the stored format is before reading to offscreen buffer + if (((ImageComponent2DRetained)offScreenBuffer.retained).isByReference()) { + ((ImageComponent2DRetained)offScreenBuffer.retained).geomLock.getLock(); + ((ImageComponent2DRetained)offScreenBuffer.retained).evaluateExtensions(extensionsSupported); + ((ImageComponent2DRetained)offScreenBuffer.retained).geomLock.unLock(); + } + + + + int bytesPerPixel = ((ImageComponent2DRetained)offScreenBuffer.retained).getEffectiveBytesPerPixel(); + int format = ((ImageComponent2DRetained)offScreenBuffer.retained).getEffectiveFormat(); + + + // allocate read buffer space + + int size = + offScreenCanvasSize.width * offScreenCanvasSize.height * bytesPerPixel; + if (byteBuffer.length < size) + byteBuffer = new byte[size]; + + + // read the image from the offscreen buffer + + readOffScreenBuffer(ctx, + format, + offScreenCanvasSize.width, offScreenCanvasSize.height); + + + // copy it into the ImageComponent + + ((ImageComponent2DRetained)offScreenBuffer.retained).retrieveImage( + byteBuffer, offScreenCanvasClippedLoc.x, offScreenCanvasClippedLoc.y, + offScreenCanvasClippedSize.width, offScreenCanvasClippedSize.height); + } + + + /** + * Synchronize and swap buffers on a double buffered canvas for + * this Canvas3D object. This method should only be called if the + * Java 3D renderer has been stopped. In the normal case, the renderer + * automatically swaps the buffer. + * This method calls the <code>flush(true)</code> methods of the + * associated 2D and 3D graphics contexts, if they have been allocated. + * + * @exception RestrictedAccessException if the Java 3D renderer is + * running. + * @exception IllegalStateException if this Canvas3D is in + * off-screen mode. + * + * @see #stopRenderer + * @see GraphicsContext3D#flush + * @see J3DGraphics2D#flush + */ + public void swap() { + if (offScreen) + throw new IllegalStateException(J3dI18N.getString("Canvas3D14")); + + if (isRunning) + throw new RestrictedAccessException(J3dI18N.getString("Canvas3D0")); + + if (!firstPaintCalled) { + return; + } + + if (view != null && graphicsContext3D != null) { + if ((view.universe != null) && + (Thread.currentThread() == view.universe.behaviorScheduler)) { + graphicsContext3D.sendRenderMessage(false, GraphicsContext3D.SWAP, null, null); + } else { + graphicsContext3D.sendRenderMessage(true, GraphicsContext3D.SWAP, null, null); + } + graphicsContext3D.runMonitor(J3dThread.WAIT); + } + } + + void doSwap() { + if (firstPaintCalled && useDoubleBuffer) { + try { + if (validCtx && (ctx != 0) && (view != null)) { + synchronized (drawingSurfaceObject) { + if (validCtx) { + if (!drawingSurfaceObject.renderLock()) { + graphicsContext3D.runMonitor(J3dThread.NOTIFY); + return; + } + this.syncRender(ctx, true); + int status = swapBuffers(ctx, screen.display, window); + if (status != NOCHANGE) { + resetImmediateRendering(status); + } + drawingSurfaceObject.unLock(); + } + } + } + } catch (NullPointerException ne) { + drawingSurfaceObject.unLock(); + } + } + // Increment the elapsedFrame for the behavior structure + // to trigger any interpolators + view.universe.behaviorStructure.incElapsedFrames(); + // waitup user thread in PureImmediate mode to continue + if (reEvaluateCanvasCmd != 0) { + int status; + + antialiasingSet = false; + if (reEvaluateCanvasCmd == RESIZE) { + status = resizeD3DCanvas(ctx); + } else { + status = toggleFullScreenMode(ctx); + } + // reset everything + if (status != NOCHANGE) { + resetImmediateRendering(status); + } + reEvaluateCanvasCmd = 0; + } + graphicsContext3D.runMonitor(J3dThread.NOTIFY); + } + + /** + * Make the context associated with the specified canvas current. + */ + final void makeCtxCurrent() { + makeCtxCurrent(ctx, screen.display, window); + } + + /** + * Make the specified context current. + */ + final void makeCtxCurrent(long ctx) { + makeCtxCurrent(ctx, screen.display, window); + } + + final void makeCtxCurrent(long ctx, long dpy, int win) { + if (ctx != screen.renderer.currentCtx) { + if (!drawingSurfaceObject.isLocked()) { + drawingSurfaceObject.renderLock(); + useCtx(ctx, dpy, win); + drawingSurfaceObject.unLock(); + } else { + useCtx(ctx, dpy, win); + } + screen.renderer.currentCtx = ctx; + } + } + + + // The native method that sets this ctx to be the current one + static native void useCtx(long ctx, long display, int window); + + native void clear(long ctx, float r, float g, float b, int winWidth, int winHeight, + ImageComponent2DRetained image, int imageScaleMode, byte[] imageYdown); + native void textureclear(long ctx, int maxX, int maxY, + float r, float g, float b, + int winWidth, int winHeight, + int objectId, int scalemode, + ImageComponent2DRetained image, + boolean update); + + + // The native method for swapBuffers + native int swapBuffers(long ctx, long dpy, int win); + + // The native method for videoResize. -- Support DVR. + native void videoResize(long ctx, long dpy, int win, float dvrFactor); + + // The native method for videoResizeCompensation. -- Support DVR. + native void videoResizeCompensation( long ctx, boolean enable); + + // The native method for setting the ModelView matrix. + native void setModelViewMatrix(long ctx, double[] viewMatrix, double[] modelMatrix); + + // The native method for setting the Projection matrix. + native void setProjectionMatrix(long ctx, double[] projMatrix); + + // The native method for setting the Viewport. + native void setViewport(long ctx, int x, int y, int width, int height); + + // used for display Lists + native void newDisplayList(long ctx, int displayListId); + native void endDisplayList(long ctx); + native void callDisplayList(long ctx, int id, boolean isNonUniformScale); + + native static void freeDisplayList(long ctx, int id); + native static void freeTexture(long ctx, int id); + + native void composite(long ctx, int px, int py, + int xmin, int ymin, int xmax, int ymax, + int rasWidth, byte[] image, + int winWidth, int winHeight); + + native void texturemapping(long ctx, + int px, int py, + int xmin, int ymin, int xmax, int ymax, + int texWidth, int texHeight, + int rasWidth, + int format, int objectId, + byte[] image, + int winWidth, int winHeight); + + native boolean initTexturemapping(long ctx, int texWidth, + int texHeight, int objectId); + + /** + * Sets the position of the manual left eye in image-plate + * coordinates. This value determines eye placement when a head + * tracker is not in use and the application is directly controlling + * the eye position in image-plate coordinates. + * In head-tracked mode or when the windowEyePointPolicy is + * RELATIVE_TO_FIELD_OF_VIEW or RELATIVE_TO_COEXISTENCE, this value + * is ignored. When the + * windowEyepointPolicy is RELATIVE_TO_WINDOW only the Z value is + * used. + * @param position the new manual left eye position + */ + public void setLeftManualEyeInImagePlate(Point3d position) { + + this.leftManualEyeInImagePlate.set(position); + synchronized(dirtyMaskLock) { + cvDirtyMask |= Canvas3D.EYE_IN_IMAGE_PLATE_DIRTY; + } + redraw(); + } + + /** + * Sets the position of the manual right eye in image-plate + * coordinates. This value determines eye placement when a head + * tracker is not in use and the application is directly controlling + * the eye position in image-plate coordinates. + * In head-tracked mode or when the windowEyePointPolicy is + * RELATIVE_TO_FIELD_OF_VIEW or RELATIVE_TO_COEXISTENCE, this value + * is ignored. When the + * windowEyepointPolicy is RELATIVE_TO_WINDOW only the Z value is + * used. + * @param position the new manual right eye position + */ + public void setRightManualEyeInImagePlate(Point3d position) { + + this.rightManualEyeInImagePlate.set(position); + synchronized(dirtyMaskLock) { + cvDirtyMask |= Canvas3D.EYE_IN_IMAGE_PLATE_DIRTY; + } + redraw(); + } + + /** + * Retrieves the position of the user-specified, manual left eye + * in image-plate + * coordinates and copies that value into the object provided. + * @param position the object that will receive the position + */ + public void getLeftManualEyeInImagePlate(Point3d position) { + position.set(this.leftManualEyeInImagePlate); + } + + /** + * Retrieves the position of the user-specified, manual right eye + * in image-plate + * coordinates and copies that value into the object provided. + * @param position the object that will receive the position + */ + public void getRightManualEyeInImagePlate(Point3d position) { + position.set(this.rightManualEyeInImagePlate); + } + + /** + * Retrieves the actual position of the left eye + * in image-plate + * coordinates and copies that value into the object provided. + * This value is a function of the windowEyepointPolicy, the tracking + * enable flag, and the manual left eye position. + * @param position the object that will receive the position + */ + public void getLeftEyeInImagePlate(Point3d position) { + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + position.set(canvasViewCache.getLeftEyeInImagePlate()); + } + } + else { + position.set(leftManualEyeInImagePlate); + } + } + + /** + * Retrieves the actual position of the right eye + * in image-plate + * coordinates and copies that value into the object provided. + * This value is a function of the windowEyepointPolicy, the tracking + * enable flag, and the manual right eye position. + * @param position the object that will receive the position + */ + public void getRightEyeInImagePlate(Point3d position) { + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + position.set(canvasViewCache.getRightEyeInImagePlate()); + } + } + else { + position.set(rightManualEyeInImagePlate); + } + } + + /** + * Retrieves the actual position of the center eye + * in image-plate + * coordinates and copies that value into the object provided. + * The center eye is the fictional eye half-way between the left and + * right eye. + * This value is a function of the windowEyepointPolicy, the tracking + * enable flag, and the manual right and left eye positions. + * @param position the object that will receive the position + * @see #setMonoscopicViewPolicy + */ + // TODO: This might not make sense for field-sequential HMD. + public void getCenterEyeInImagePlate(Point3d position) { + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + position.set(canvasViewCache.getCenterEyeInImagePlate()); + } + } + else { + Point3d cenEye = new Point3d(); + cenEye.add(leftManualEyeInImagePlate, rightManualEyeInImagePlate); + cenEye.scale(0.5); + position.set(cenEye); + } + } + + /** + * Retrieves the current ImagePlate coordinates to Virtual World + * coordinates transform and places it into the specified object. + * @param t the Transform3D object that will receive the + * transform + */ + // TODO: Document -- This will return the transform of left plate. + public void getImagePlateToVworld(Transform3D t) { + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + t.set(canvasViewCache.getImagePlateToVworld()); + } + } + else { + t.setIdentity(); + } + } + + /** + * Computes the position of the specified AWT pixel value + * in image-plate + * coordinates and copies that value into the object provided. + * @param x the X coordinate of the pixel relative to the upper-left + * hand corner of the window. + * @param y the Y coordinate of the pixel relative to the upper-left + * hand corner of the window. + * @param imagePlatePoint the object that will receive the position in + * physical image plate coordinates (relative to the lower-left + * corner of the screen). + */ + // TODO: Document -- This transform the pixel location to the left image plate. + public void getPixelLocationInImagePlate(int x, int y, + Point3d imagePlatePoint) { + + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + imagePlatePoint.x = + canvasViewCache.getWindowXInImagePlate((double)x); + imagePlatePoint.y = + canvasViewCache.getWindowYInImagePlate((double)y); + imagePlatePoint.z = 0.0; + } + } else { + imagePlatePoint.set(0.0, 0.0, 0.0); + } + } + + + void getPixelLocationInImagePlate(double x, double y, double z, + Point3d imagePlatePoint) { + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + canvasViewCache.getPixelLocationInImagePlate( + x, y, z, imagePlatePoint); + } + } else { + imagePlatePoint.set(0.0, 0.0, 0.0); + } + } + + + /** + * Computes the position of the specified AWT pixel value + * in image-plate + * coordinates and copies that value into the object provided. + * @param pixelLocation the coordinates of the pixel relative to + * the upper-left hand corner of the window. + * @param imagePlatePoint the object that will receive the position in + * physical image plate coordinates (relative to the lower-left + * corner of the screen). + * + * @since Java 3D 1.2 + */ + // TODO: Document -- This transform the pixel location to the left image plate. + public void getPixelLocationInImagePlate(Point2d pixelLocation, + Point3d imagePlatePoint) { + + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + imagePlatePoint.x = + canvasViewCache.getWindowXInImagePlate(pixelLocation.x); + imagePlatePoint.y = + canvasViewCache.getWindowYInImagePlate(pixelLocation.y); + imagePlatePoint.z = 0.0; + } + } + else { + imagePlatePoint.set(0.0, 0.0, 0.0); + } + } + + + /** + * Projects the specified point from image plate coordinates + * into AWT pixel coordinates. The AWT pixel coordinates are + * copied into the object provided. + * @param imagePlatePoint the position in + * physical image plate coordinates (relative to the lower-left + * corner of the screen). + * @param pixelLocation the object that will receive the coordinates + * of the pixel relative to the upper-left hand corner of the window. + * + * @since Java 3D 1.2 + */ + // TODO: Document -- This transform the pixel location from the left image plate. + public void getPixelLocationFromImagePlate(Point3d imagePlatePoint, + Point2d pixelLocation) { + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + canvasViewCache.getPixelLocationFromImagePlate( + imagePlatePoint, pixelLocation); + } + } + else { + pixelLocation.set(0.0, 0.0); + } + } + + /** + * Copies the current Vworld projection transform for each eye + * into the specified Transform3D objects. This transform takes + * points in virtual world coordinates and projects them into + * clipping coordinates, which are in the range [-1,1] in + * <i>X</i>, <i>Y</i>, and <i>Z</i> after clipping and perspective + * division. + * In monoscopic mode, the same projection transform will be + * copied into both the right and left eye Transform3D objects. + * + * @param leftProjection the Transform3D object that will receive + * a copy of the current projection transform for the left eye. + * + * @param rightProjection the Transform3D object that will receive + * a copy of the current projection transform for the right eye. + * + * @since Java 3D 1.3 + */ + public void getVworldProjection(Transform3D leftProjection, + Transform3D rightProjection) { + if (canvasViewCache != null) { + ViewPlatformRetained viewPlatformRetained = + (ViewPlatformRetained)view.getViewPlatform().retained; + + synchronized(canvasViewCache) { + leftProjection.mul(canvasViewCache.getLeftProjection(), + canvasViewCache.getLeftVpcToEc()); + leftProjection.mul(viewPlatformRetained.getVworldToVpc()); + + // caluclate right eye if in stereo, otherwise + // this is the same as the left eye. + if (useStereo) { + rightProjection.mul(canvasViewCache.getRightProjection(), + canvasViewCache.getRightVpcToEc()); + rightProjection.mul(viewPlatformRetained.getVworldToVpc()); + } + else { + rightProjection.set(leftProjection); + } + } + } + else { + leftProjection.setIdentity(); + rightProjection.setIdentity(); + } + } + + /** + * Copies the inverse of the current Vworld projection transform + * for each eye into the specified Transform3D objects. This + * transform takes points in clipping coordinates, which are in + * the range [-1,1] in <i>X</i>, <i>Y</i>, and <i>Z</i> after + * clipping and perspective division, and transforms them into + * virtual world coordinates. + * In monoscopic mode, the same inverse projection transform will + * be copied into both the right and left eye Transform3D objects. + * + * @param leftInverseProjection the Transform3D object that will + * receive a copy of the current inverse projection transform for + * the left eye. + * @param rightInverseProjection the Transform3D object that will + * receive a copy of the current inverse projection transform for + * the right eye. + * + * @since Java 3D 1.3 + */ + public void getInverseVworldProjection(Transform3D leftInverseProjection, + Transform3D rightInverseProjection) { + if (canvasViewCache != null) { + ViewPlatformRetained viewPlatformRetained = + (ViewPlatformRetained)view.getViewPlatform().retained; + + synchronized(canvasViewCache) { + leftInverseProjection.set( + canvasViewCache.getLeftCcToVworld()); + + // caluclate right eye if in stereo, otherwise + // this is the same as the left eye. + if (useStereo) { + rightInverseProjection.set( + canvasViewCache.getRightCcToVworld()); + } + else { + rightInverseProjection.set(leftInverseProjection); + } + } + + } + else { + leftInverseProjection.setIdentity(); + rightInverseProjection.setIdentity(); + } + } + + + /** + * Retrieves the physical width of this canvas window in meters. + * @return the physical window width in meters. + */ + public double getPhysicalWidth() { + double width = 0.0; + + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + width = canvasViewCache.getPhysicalWindowWidth(); + } + } + + return width; + } + + /** + * Retrieves the physical height of this canvas window in meters. + * @return the physical window height in meters. + */ + public double getPhysicalHeight() { + double height = 0.0; + + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + height = canvasViewCache.getPhysicalWindowHeight(); + } + } + + return height; + } + + /** + * Retrieves the current Virtual World coordinates to ImagePlate + * coordinates transform and places it into the specified object. + * @param t the Transform3D object that will receive the + * transform + */ + // TODO: Document -- This will return the transform of left plate. + public void getVworldToImagePlate(Transform3D t) { + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + t.set(canvasViewCache.getVworldToImagePlate()); + } + } + else { + t.setIdentity(); + } + } + + void getLastVworldToImagePlate(Transform3D t) { + if (canvasViewCache != null) { + synchronized(canvasViewCache) { + t.set(canvasViewCache.getLastVworldToImagePlate()); + } + } + else { + t.setIdentity(); + } + } + + /** + * Sets view that points to this Canvas3D. + * @param view view object that points to this Canvas3D + */ + void setView(View view) { + pendingView = view; + + // We can't set View directly here in user thread since + // other threads may using canvas.view + // e.g. In Renderer, we use canvas3d.view.inCallBack + // before and after postSwap(), if view change in between + // than view.inCallBack may never reset to false. + VirtualUniverse.mc.postRequest(MasterControl.SET_VIEW, this); + evaluateActive(); + } + + void computeViewCache() { + synchronized(cvLock) { + if (view == null) { + canvasViewCache = null; + } else { + + canvasViewCache = new CanvasViewCache(this, + screen.screenViewCache, + view.viewCache); + synchronized (dirtyMaskLock) { + cvDirtyMask = (STEREO_DIRTY | MONOSCOPIC_VIEW_POLICY_DIRTY + | EYE_IN_IMAGE_PLATE_DIRTY | + MOVED_OR_RESIZED_DIRTY); + } + } + } + } + + /** + * Gets view that points to this Canvas3D. + * @return view object that points to this Canvas3D + */ + public View getView() { + return pendingView; + } + + /** + * Returns a status flag indicating whether or not stereo + * is available. + * This is equivalent to: + * <ul> + * <code> + * ((Boolean)queryProperties(). + * get("stereoAvailable")). + * booleanValue() + * </code> + * </ul> + * + * @return a flag indicating whether stereo is available + */ + public boolean getStereoAvailable() { + return ((Boolean)queryProperties().get("stereoAvailable")). + booleanValue(); + } + + /** + * Turns stereo on or off. Note that this attribute is used + * only when stereo is available. Enabling stereo on a Canvas3D + * that does not support stereo has no effect. + * @param flag enables or disables the display of stereo + * + * @see #queryProperties + */ + public void setStereoEnable(boolean flag) { + stereoEnable = flag; + useStereo = stereoEnable && stereoAvailable; + synchronized(dirtyMaskLock) { + cvDirtyMask |= Canvas3D.STEREO_DIRTY; + } + redraw(); + } + + /** + * Returns a status flag indicating whether or not stereo + * is enabled. + * @return a flag indicating whether stereo is enabled + */ + public boolean getStereoEnable() { + return this.stereoEnable; + } + + + /** + * Specifies how Java 3D generates monoscopic view. If set to + * View.LEFT_EYE_VIEW, the view generated corresponds to the view as + * seen from the left eye. If set to View.RIGHT_EYE_VIEW, the view + * generated corresponds to the view as seen from the right + * eye. If set to View.CYCLOPEAN_EYE_VIEW, the view generated + * corresponds to the view as seen from the 'center eye', the + * fictional eye half-way between the left and right eye. The + * default monoscopic view policy is View.CYCLOPEAN_EYE_VIEW. + * <p> + * NOTE: for backward compatibility with Java 3D 1.1, if this + * attribute is set to its default value of + * View.CYCLOPEAN_EYE_VIEW, the monoscopic view policy in the + * View object will be used. An application should not use both + * the deprecated View method and this Canvas3D method at the same + * time. + * @param policy one of View.LEFT_EYE_VIEW, View.RIGHT_EYE_VIEW, or + * View.CYCLOPEAN_EYE_VIEW. + * + * @exception IllegalStateException if the specified + * policy is CYCLOPEAN_EYE_VIEW, the canvas is a stereo canvas, + * and the viewPolicy for the associated view is HMD_VIEW + * + * @since Java 3D 1.2 + */ + public void setMonoscopicViewPolicy(int policy) { + + + if((view !=null) && (view.viewPolicy == View.HMD_VIEW) && + (monoscopicViewPolicy == View.CYCLOPEAN_EYE_VIEW) && + (!useStereo)) { + throw new + IllegalStateException(J3dI18N.getString("View31")); + } + + monoscopicViewPolicy = policy; + synchronized(dirtyMaskLock) { + cvDirtyMask |= Canvas3D.MONOSCOPIC_VIEW_POLICY_DIRTY; + } + redraw(); + } + + + /** + * Returns policy on how Java 3D generates monoscopic view. + * @return policy one of View.LEFT_EYE_VIEW, View.RIGHT_EYE_VIEW or + * View.CYCLOPEAN_EYE_VIEW. + * + * @since Java 3D 1.2 + */ + public int getMonoscopicViewPolicy() { + return this.monoscopicViewPolicy; + } + + + /** + * Returns a status flag indicating whether or not double + * buffering is available. + * This is equivalent to: + * <ul> + * <code> + * ((Boolean)queryProperties(). + * get("doubleBufferAvailable")). + * booleanValue() + * </code> + * </ul> + * + * @return a flag indicating whether double buffering is available. + */ + public boolean getDoubleBufferAvailable() { + return ((Boolean)queryProperties().get("doubleBufferAvailable")). + booleanValue(); + } + + /** + * Turns double buffering on or off. If double buffering + * is off, all drawing is to the front buffer and no buffer swap + * is done between frames. It should be stressed that running + * Java 3D with double buffering disabled is not recommended. + * Enabling double buffering on a Canvas3D + * that does not support double buffering has no effect. + * + * @param flag enables or disables double buffering. + * + * @see #queryProperties + */ + public void setDoubleBufferEnable(boolean flag) { + doubleBufferEnable = flag; + useDoubleBuffer = doubleBufferEnable && doubleBufferAvailable; + if (Thread.currentThread() == screen.renderer) { + setRenderMode(ctx, FIELD_ALL, useDoubleBuffer); + } + redraw(); + } + + /** + * Returns a status flag indicating whether or not double + * buffering is enabled. + * @return a flag indicating if double buffering is enabled. + */ + public boolean getDoubleBufferEnable() { + return doubleBufferEnable; + } + + /** + * Returns a status flag indicating whether or not scene + * antialiasing is available. + * This is equivalent to: + * <ul> + * <code> + * ((Boolean)queryProperties(). + * get("sceneAntialiasingAvailable")). + * booleanValue() + * </code> + * </ul> + * + * @return a flag indicating whether scene antialiasing is available. + */ + public boolean getSceneAntialiasingAvailable() { + return ((Boolean)queryProperties().get("sceneAntialiasingAvailable")). + booleanValue(); + } + + + /** + * Returns a read-only Map object containing key-value pairs that define + * various properties for this Canvas3D. All of the keys are + * String objects. The values are key-specific, but most will be + * Boolean, Integer, Float, Double, or String objects. + * + * <p> + * The currently defined keys are: + * + * <p> + * <ul> + * <table BORDER=1 CELLSPACING=1 CELLPADDING=1> + * <tr> + * <td><b>Key (String)</b></td> + * <td><b>Value Type</b></td> + * </tr> + * <tr> + * <td><code>doubleBufferAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>stereoAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>sceneAntialiasingAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>sceneAntialiasingNumPasses</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>texture3DAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureColorTableSize</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>textureLodRangeAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureLodOffsetAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureWidthMax</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>textureHeightMax</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>textureBoundaryWidthMax</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>textureEnvCombineAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureCombineDot3Available</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureCombineSubtractAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureUnitStateMax</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>textureCubeMapAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureDetailAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureSharpenAvailable</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureFilter4Available</code></td> + * <td>Boolean</td> + * </tr> + * <tr> + * <td><code>textureAnisotropicFilterDegreeMax</code></td> + * <td>Float</td> + * </tr> + * <tr> + * <td><code>compressedGeometry.majorVersionNumber</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>compressedGeometry.minorVersionNumber</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>compressedGeometry.minorMinorVersionNumber</code></td> + * <td>Integer</td> + * </tr> + * <tr> + * <td><code>native.version</code></td> + * <td>String</td> + * </tr> + * </table> + * </ul> + * + * <p> + * The descriptions of the values returned for each key are as follows: + * + * <p> + * <ul> + * <li> + * <code>doubleBufferAvailable</code> + * <ul> + * A Boolean indicating whether or not double buffering + * is available for this Canvas3D. This is equivalent to + * the getDoubleBufferAvailable method. If this flag is false, + * the Canvas3D will be rendered in single buffer mode; requests + * to enable double buffering will be ignored. + * </ul> + * </li> + * + * <li> + * <code>stereoAvailable</code> + * <ul> + * A Boolean indicating whether or not stereo + * is available for this Canvas3D. This is equivalent to + * the getStereoAvailable method. If this flag is false, + * the Canvas3D will be rendered in monoscopic mode; requests + * to enable stereo will be ignored. + * </ul> + * </li> + * + * <li> + * <code>sceneAntialiasingAvailable</code> + * <ul> + * A Boolean indicating whether or not scene antialiasing + * is available for this Canvas3D. This is equivalent to + * the getSceneAntialiasingAvailable method. If this flag is false, + * requests to enable scene antialiasing will be ignored. + * </ul> + * </li> + * + * + * <li> + * <code>sceneAntialiasingNumPasses</code> + * <ul> + * An Integer indicating the number of passes scene antialiasing + * requires to render a single frame for this Canvas3D. + * If this value is zero, scene antialiasing is not supported. + * If this value is one, multisampling antialiasing is used. + * Otherwise, the number indicates the number of rendering passes + * needed. + * </ul> + * </li> + * + * <li> + * <code>texture3DAvailable</code> + * <ul> + * A Boolean indicating whether or not 3D Texture mapping + * is available for this Canvas3D. If this flag is false, + * 3D texture mapping is either not supported by the underlying + * rendering layer or is otherwise unavailable for this + * particular Canvas3D. All use of 3D texture mapping will be + * ignored in this case. + * </ul> + * </li> + * + * <li> + * <code>textureColorTableSize</code> + * <ul> + * An Integer indicating the maximum size of the texture color + * table for this Canvas3D. If the size is 0, the texture + * color table is either not supported by the underlying rendering + * layer or is otherwise unavailable for this particular + * Canvas3D. An attempt to use a texture color table larger than + * textureColorTableSize will be ignored; no color lookup will be + * performed. + * </ul> + * </li> + * + * <li> + * <code>textureLodRangeAvailable</code> + * <ul> + * A Boolean indicating whether or not setting only a subset of mipmap + * levels and setting a range of texture LOD are available for this + * Canvas3D. + * If it indicates false, setting a subset of mipmap levels and + * setting a texture LOD range are not supported by the underlying + * rendering layer, and an attempt to set base level, or maximum level, + * or minimum LOD, or maximum LOD will be ignored. In this case, + * images for all mipmap levels must be defined for the texture to be + * valid. + * </ul> + * </li> + * + * <li> + * <code>textureLodOffsetAvailable</code> + * <ul> + * A Boolean indicating whether or not setting texture LOD offset is + * available for this Canvas3D. If it indicates false, setting + * texture LOD offset is not supported by the underlying rendering + * layer, and an attempt to set the texture LOD offset will be ignored. + * </ul> + * </li> + * + * <li> + * <code>textureWidthMax</code> + * <ul> + * An Integer indicating the maximum texture width supported by + * this Canvas3D. If the width of a texture exceeds the maximum texture + * width for a Canvas3D, then the texture will be effectively disabled + * for that Canvas3D. + * </ul> + * </li> + * + * <li> + * <code>textureHeightMax</code> + * <ul> + * An Integer indicating the maximum texture height supported by + * this Canvas3D. If the height of a texture exceeds the maximum texture + * height for a Canvas3D, then the texture will be effectively disabled + * for that Canvas3D. + * </ul> + * </li> + * + * <li> + * <code>textureBoundaryWidthMax</code> + * <ul> + * An Integer indicating the maximum texture boundary width + * supported by the underlying rendering layer for this Canvas3D. If + * the maximum supported texture boundary width is 0, then texture + * boundary is not supported by the underlying rendering layer. + * An attempt to specify a texture boundary width > the + * textureBoundaryWidthMax will effectively disable the texture. + * </ul> + * </li> + * + * <li> + * <code>textureEnvCombineAvailable</code> + * <ul> + * A Boolean indicating whether or not texture environment combine + * operation is supported for this Canvas3D. If it indicates false, + * then texture environment combine is not supported by the + * underlying rendering layer, and an attempt to specify COMBINE + * as the texture mode will be ignored. The texture mode in effect + * will be REPLACE. + * </ul> + * </li> + * + * <li> + * <code>textureCombineDot3Available</code> + * <ul> + * A Boolean indicating whether or not texture combine mode + * COMBINE_DOT3 is + * supported for this Canvas3D. If it indicates false, then + * texture combine mode COMBINE_DOT3 is not supported by + * the underlying rendering layer, and an attempt to specify + * COMBINE_DOT3 as the texture combine mode will be ignored. + * The texture combine mode in effect will be COMBINE_REPLACE. + * </ul> + * </li> + * + * <li> + * <code>textureCombineSubtractAvailable</code> + * <ul> + * A Boolean indicating whether or not texture combine mode + * COMBINE_SUBTRACT is + * supported for this Canvas3D. If it indicates false, then + * texture combine mode COMBINE_SUBTRACT is not supported by + * the underlying rendering layer, and an attempt to specify + * COMBINE_SUBTRACT as the texture combine mode will be ignored. + * The texture combine mode in effect will be COMBINE_REPLACE. + * </ul> + * </li> + * + * <li> + * <code>textureUnitStateMax</code> + * <ul> + * An Integer indicating the maximum number of texture unit states + * supported by the underlying rendering layer. Java3D allows an + * application to specify number of texture unit states more than + * what the underlying rendering layer supports; in this case, Java3D + * will use multi-pass to support the specified number of texture + * unit states. + * </ul> + * </li> + * + * <li> + * <code>textureCubeMapAvailable</code> + * <ul> + * A Boolean indicating whether or not texture cube map is supported + * for this Canvas3D. If it indicates false, then texture cube map + * is not supported by the underlying rendering layer, and an attempt + * to specify NORMAL_MAP or REFLECTION_MAP as the texture generation + * mode will be ignored. The texture generation mode in effect will + * be SPHERE_MAP. + * </ul> + * </li> + * + * <li> + * <code>textureDetailAvailable</code> + * <ul> + * A Boolean indicating whether or not detail texture is supported + * for this Canvas3D. If it indicates false, then detail texture is + * not supported by the underlying rendering layer, and an attempt + * to specify LINEAR_DETAIL, LINEAR_DETAIL_ALPHA or + * LINEAR_DETAIL_RGB as the texture magnification filter mode will + * be ignored. The texture magnification filter mode in effect will + * be BASE_LEVEL_LINEAR. + * </ul> + * </li> + * + * <li> + * <code>textureSharpenAvailable</code> + * <ul> + * A Boolean indicating whether or not sharpen texture is supported + * for this Canvas3D. If it indicates false, then sharpen texture + * is not supported by the underlying rendering layer, and an attempt + * to specify LINEAR_SHARPEN, LINEAR_SHARPEN_ALPHA or + * LINEAR_SHARPEN_RGB as the texture magnification filter mode + * will be ignored. The texture magnification filter mode in effect + * will be BASE_LEVEL_LINEAR. + * </ul> + * </li> + * + * <li> + * <code>textureFilter4Available</code> + * <ul> + * A Boolean indicating whether or not filter4 is supported for this + * Canvas3D. If it indicates flase, then filter4 is not supported + * by the underlying rendering layer, and an attempt to specify + * FILTER_4 as the texture minification filter mode or texture + * magnification filter mode will be ignored. The texture filter mode + * in effect will be BASE_LEVEL_LINEAR. + * </ul> + * </li> + * + * <li> + * <code>textureAnisotropicFilterDegreeMax</code> + * <ul> + * A Float indicating the maximum degree of anisotropic filter + * available for this Canvas3D. If it indicates 1.0, setting + * anisotropic filter is not supported by the underlying rendering + * layer, and an attempt to set anisotropic filter degree will be ignored. + * </ul> + * </li> + * + * <li> + * <code>compressedGeometry.majorVersionNumber</code><br> + * <code>compressedGeometry.minorVersionNumber</code><br> + * <code>compressedGeometry.minorMinorVersionNumber</code> + * <ul> + * Integers indicating the major, minor, and minor-minor + * version numbers, respectively, of the version of compressed + * geometry supported by this version of Java 3D. + * </ul> + * </li> + * + * <li> + * <code>native.version</code> + * <ul> + * A String indicating the version number of the native graphics + * library. The format of this string is defined by the native + * library. + * </ul> + * </li> + * </ul> + * + * @return the properties of this Canavs3D + * + * @since Java 3D 1.2 + */ + public final Map queryProperties() { + if (queryProps == null) { + boolean createDummyCtx = false; + + synchronized (VirtualUniverse.mc.contextCreationLock) { + if (ctx == 0) { + createDummyCtx = true; + } + } + + if (createDummyCtx) { + GraphicsConfigTemplate3D.setQueryProps(this); + + } + //create query Properties + createQueryProps(); + } + return queryProps; + } + + void createQueryContext() { + // create a dummy context to query for support of certain + // extensions, the context will destroy immediately + // inside the native code after setting the various + // fields in this object + createQueryContext(screen.display, window, vid, + offScreen, 10, 10); + } + + /** + * Creates the query properties for this Canvas. + */ + private void createQueryProps() { + // Create lists of keys and values + ArrayList keys = new ArrayList(); + ArrayList values = new ArrayList(); + int pass = 0; + + // properties not associated with graphics context + keys.add("doubleBufferAvailable"); + values.add(new Boolean(doubleBufferAvailable)); + + keys.add("stereoAvailable"); + values.add(new Boolean(stereoAvailable)); + + keys.add("sceneAntialiasingAvailable"); + values.add(new Boolean(sceneAntialiasingAvailable)); + + keys.add("sceneAntialiasingNumPasses"); + + if (sceneAntialiasingAvailable) { + pass = (sceneAntialiasingMultiSamplesAvailable ? + 1: Renderer.NUM_ACCUMULATION_SAMPLES); + } + values.add(new Integer(pass)); + + keys.add("compressedGeometry.majorVersionNumber"); + values.add(new Integer(GeometryDecompressor.majorVersionNumber)); + keys.add("compressedGeometry.minorVersionNumber"); + values.add(new Integer(GeometryDecompressor.minorVersionNumber)); + keys.add("compressedGeometry.minorMinorVersionNumber"); + values.add(new Integer(GeometryDecompressor.minorMinorVersionNumber)); + + // Properties associated with graphics context + keys.add("texture3DAvailable"); + values.add(new Boolean((textureExtendedFeatures & TEXTURE_3D) != 0)); + + keys.add("textureColorTableSize"); + values.add(new Integer(textureColorTableSize)); + + keys.add("textureEnvCombineAvailable"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_COMBINE) != 0)); + + keys.add("textureCombineDot3Available"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_COMBINE_DOT3) != 0)); + + keys.add("textureCombineSubtractAvailable"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_COMBINE_SUBTRACT) != 0)); + + keys.add("textureCubeMapAvailable"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_CUBE_MAP) != 0)); + + keys.add("textureSharpenAvailable"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_SHARPEN) != 0)); + + keys.add("textureDetailAvailable"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_DETAIL) != 0)); + + keys.add("textureFilter4Available"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_FILTER4) != 0)); + + keys.add("textureAnisotropicFilterDegreeMax"); + values.add(new Float(anisotropicDegreeMax)); + + keys.add("textureWidthMax"); + values.add(new Integer(textureWidthMax)); + + keys.add("textureHeightMax"); + values.add(new Integer(textureHeightMax)); + + keys.add("textureBoundaryWidthMax"); + values.add(new Integer(textureBoundaryWidthMax)); + + keys.add("textureLodRangeAvailable"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_LOD_RANGE) != 0)); + + keys.add("textureLodOffsetAvailable"); + values.add(new Boolean( + (textureExtendedFeatures & TEXTURE_LOD_OFFSET) != 0)); + + keys.add("textureUnitStateMax"); + values.add(new Integer(numTexUnitSupported)); + + keys.add("native.version"); + if(nativeGraphicsVersion == null) + nativeGraphicsVersion = ""; + values.add(nativeGraphicsVersion); + + // Now Create read-only properties object + queryProps = + new J3dQueryProps((String[]) keys.toArray(new String[0]), + values.toArray()); + } + + + // Set internal render mode to one of FIELD_ALL, FIELD_LEFT or + // FIELD_RIGHT. Note that it is up to the caller to ensure that + // stereo is available before setting the mode to FIELD_LEFT or + // FIELD_RIGHT. The boolean isTRUE for double buffered mode, FALSE + // foe single buffering. + native void setRenderMode(long ctx, int mode, boolean doubleBuffer); + + // Set glDepthMask. + native void setDepthBufferWriteEnable(long ctx, boolean mode); + + /** + * Update the view cache associated with this canvas. + */ + void updateViewCache(boolean flag, CanvasViewCache cvc, + BoundingBox frustumBBox, boolean doInfinite) { + + synchronized(cvLock) { + if (firstPaintCalled && (canvasViewCache != null)) { + canvasViewCache.snapshot(); + canvasViewCache.computeDerivedData(flag, cvc, frustumBBox, + doInfinite); + } + } + } + + /** + * Set depthBufferWriteEnableOverride flag + */ + void setDepthBufferWriteEnableOverride(boolean flag) { + depthBufferWriteEnableOverride = flag; + } + + /** + * Set depthBufferEnableOverride flag + */ + void setDepthBufferEnableOverride(boolean flag) { + depthBufferEnableOverride = flag; + } + + // Static initializer for Canvas3D class + static { + VirtualUniverse.loadLibraries(); + } + + + void resetTexture(long ctx, int texUnitIndex) { + // D3D also need to reset texture attributes + this.resetTextureNative(ctx, texUnitIndex); + + if (texUnitIndex < 0) { + texUnitIndex = 0; + } + texUnitState[texUnitIndex].mirror = null; + texUnitState[texUnitIndex].texture = null; + + if (VirtualUniverse.mc.isD3D()) { + texUnitState[texUnitIndex].texAttrs = null; + texUnitState[texUnitIndex].texGen = null; + } + } + + + // use by D3D only + void resetTextureBin() { + Object obj; + TextureRetained tex; + DetailTextureImage detailTex; + + // We don't use rdr.objectId for background texture in D3D + // so there is no need to handle rdr.objectId + if ((graphics2D != null) && + (graphics2D.objectId != -1)) { + VirtualUniverse.mc.freeTexture2DId(graphics2D.objectId); + // let J3DGraphics2DImpl to initialize texture again + graphics2D.objectId = -1; + } + + for (int id = textureIDResourceTable.size()-1; id > 0; id--) { + obj = textureIDResourceTable.get(id); + if (obj != null) { + if (obj instanceof TextureRetained) { + tex = (TextureRetained) obj; + tex.resourceCreationMask &= ~canvasBit; + } else { + detailTex = (DetailTextureImage) obj; + for (int i=0; i < detailTex.resourceCreationMask.length; i++) { + detailTex.resourceCreationMask[i] &= ~canvasBit; + } + } + } + } + } + + + void d3dResize() { + int status = resizeD3DCanvas(ctx); + + antialiasingSet = false; + + // We need to reevaluate everything since d3d may create + // a new ctx + if (status != NOCHANGE) { + resetRendering(status); + } + } + + void d3dToggle() { + int status = toggleFullScreenMode(ctx); + + antialiasingSet = false; + if (status != NOCHANGE) { + resetRendering(status); + } + } + + // use by D3D only + void notifyD3DPeer(int cmd) { + if (active) { + if (isRunning) { + if ((view != null) && + (view.active) && + // it is possible that view is set active by MC + // but renderer not yet set + (screen.renderer != null)) { + VirtualUniverse.mc.postRequest(MasterControl.STOP_RENDERER, this); + + while (isRunningStatus) { + MasterControl.threadYield(); + } + J3dMessage renderMessage = VirtualUniverse.mc.getMessage(); + renderMessage.threads = J3dThread.RENDER_THREAD; + if (cmd == RESIZE) { + renderMessage.type = J3dMessage.RESIZE_CANVAS; + } else { + renderMessage.type = J3dMessage.TOGGLE_CANVAS; + } + renderMessage.universe = null; + renderMessage.view = null; + renderMessage.args[0] = this; + + screen.renderer.rendererStructure.addMessage(renderMessage); + VirtualUniverse.mc.postRequest(MasterControl.START_RENDERER, this); + VirtualUniverse.mc.sendRunMessage(view, + J3dThread.RENDER_THREAD); + } + } else { + // may be in immediate mode + reEvaluateCanvasCmd = cmd; + } + } + } + + // reset all attributes so that everything e.g. display list, + // texture will recreate again in the next frame + void resetRendering(int status) { + + if (status == RECREATEDDRAW) { + // D3D use MANAGE_POOL when createTexture, so there + // is no need to download texture again in case of RESETSURFACE + resetTextureBin(); + screen.renderer.needToResendTextureDown = true; + } + + reset(); + + cvDirtyMask |= VIEW_INFO_DIRTY; + needToRebuildDisplayList = true; + + ctxTimeStamp = VirtualUniverse.mc.getContextTimeStamp(); + } + + + void reset() { + int i; + + byteBuffer = new byte[1]; + currentAppear = new AppearanceRetained(); + currentMaterial = new MaterialRetained(); + viewFrustum = new CachedFrustum(); + canvasDirty = 0xffff; + lightBin = null; + environmentSet = null; + attributeBin = null; + textureBin = null; + renderMolecule = null; + polygonAttributes = null; + lineAttributes = null; + pointAttributes = null; + material = null; + enableLighting = false; + transparency = null; + coloringAttributes = null; + texture = null; + texAttrs = null; + if (texUnitState != null) { + TextureUnitStateRetained tus; + for (i=0; i < texUnitState.length; i++) { + tus = texUnitState[i]; + if (tus != null) { + tus.texAttrs = null; + tus.texGen = null; + } + } + } + texCoordGeneration = null; + renderingAttrs = null; + appearance = null; + appHandle = null; + dirtyRenderMoleculeList.clear(); + displayListResourceFreeList.clear(); + + dirtyDlistPerRinfoList.clear(); + textureIdResourceFreeList.clear(); + + lightChanged = true; + modelMatrix = null; + modelClip = null; + fog = null; + texLinearMode = false; + sceneAmbient = new Color3f(); + + + for (i=0; i< frameCount.length;i++) { + frameCount[i] = -1; + } + + for (i=0; i < lights.length; i++) { + lights[i] = null; + } + + if (currentLights != null) { + for (i=0; i < currentLights.length; i++) { + currentLights[i] = null; + } + } + + enableMask = -1; + stateUpdateMask = 0; + depthBufferWriteEnableOverride = false; + depthBufferEnableOverride = false; + depthBufferWriteEnable = true; + vfPlanesValid = false; + lightChanged = false; + + for (i=0; i < curStateToUpdate.length; i++) { + curStateToUpdate[i] = null; + } + + } + + + void resetImmediateRendering(int status) { + canvasDirty = 0xffff; + ra = null; + + setSceneAmbient(ctx, 0.0f, 0.0f, 0.0f); + disableFog(ctx); + resetRenderingAttributes(ctx, false, false); + + resetTexture(ctx, -1); + resetTexCoordGeneration(ctx); + resetTextureAttributes(ctx); + texUnitState[0].texAttrs = null; + texUnitState[0].texGen = null; + + resetPolygonAttributes(ctx); + resetLineAttributes(ctx); + resetPointAttributes(ctx); + resetTransparency(ctx, + RenderMolecule.SURFACE, + PolygonAttributes.POLYGON_FILL, + false, false); + resetColoringAttributes(ctx, + 1.0f, 1.0f, + 1.0f, 1.0f, false); + updateMaterial(ctx, 1.0f, 1.0f, 1.0f, 1.0f); + resetRendering(NOCHANGE); + makeCtxCurrent(); + cvDirtyMask |= VIEW_INFO_DIRTY; + needToRebuildDisplayList = true; + + ctxTimeStamp = VirtualUniverse.mc.getContextTimeStamp(); + if (status == RECREATEDDRAW) { + screen.renderer.needToResendTextureDown = true; + } + } + + + // overide Canvas.getSize() + public Dimension getSize() { + if (!fullScreenMode) { + return super.getSize(); + } else { + return new Dimension(fullscreenWidth, fullscreenHeight); + } + } + + public Dimension getSize(Dimension rv) { + if (!fullScreenMode) { + return super.getSize(rv); + } else { + if (rv == null) { + return new Dimension(fullscreenWidth, fullscreenHeight); + } else { + rv.setSize(fullscreenWidth, fullscreenHeight); + return rv; + } + } + } + + public Point getLocationOnScreen() { + if (!fullScreenMode) { + try { + return super.getLocationOnScreen(); + } catch (IllegalComponentStateException e) {} + } + return new Point(); + } + + public int getX() { + if (!fullScreenMode) { + return super.getX(); + } else { + return 0; + } + } + + + public int getY() { + if (!fullScreenMode) { + return super.getY(); + } else { + return 0; + } + } + + public int getWidth() { + if (!fullScreenMode) { + return super.getWidth(); + } else { + return screen.screenSize.width; + } + } + + public int getHeight() { + if (!fullScreenMode) { + return super.getHeight(); + } else { + return screen.screenSize.height; + } + } + + public Point getLocation(Point rv) { + if (!fullScreenMode) { + return super.getLocation(rv); + } else { + if (rv != null) { + rv.setLocation(0, 0); + return rv; + } else { + return new Point(); + } + } + } + + public Point getLocation() { + if (!fullScreenMode) { + return super.getLocation(); + } else { + return new Point(); + } + } + + public Rectangle getBounds() { + if (!fullScreenMode) { + return super.getBounds(); + } else { + return new Rectangle(0, 0, + screen.screenSize.width, + screen.screenSize.height); + } + } + + public Rectangle getBounds(Rectangle rv) { + if (!fullScreenMode) { + return super.getBounds(rv); + } else { + if (rv != null) { + rv.setBounds(0, 0, + screen.screenSize.width, + screen.screenSize.height); + return rv; + } else { + return new Rectangle(0, 0, + screen.screenSize.width, + screen.screenSize.height); + } + } + } + + void setModelViewMatrix(long ctx, double[] viewMatrix, Transform3D mTrans) { + setModelViewMatrix(ctx, viewMatrix, mTrans.mat); + if (!useStereo) { + this.modelMatrix = mTrans; + } else { + if (rightStereoPass) { + // Only set cache in right stereo pass, otherwise + // if the left stero pass set the cache value, + // setModelViewMatrix() in right stereo pass will not + // perform in RenderMolecules. + this.modelMatrix = mTrans; + } + } + } + + void setDepthBufferWriteEnable(boolean mode) { + depthBufferWriteEnable = mode; + setDepthBufferWriteEnable(ctx, mode); + } + + void setTexUnitStateMap(int texUnitStateIndex, int texUnitIndex) { + texUnitStateMap[texUnitIndex] = texUnitStateIndex; + } + + void setNumActiveTexUnit(int n) { + numActiveTexUnit = n; + } + + int getNumActiveTexUnit() { + return numActiveTexUnit; + } + + void setLastActiveTexUnit(int n) { + lastActiveTexUnit = n; + } + + int getLastActiveTexUnit() { + return lastActiveTexUnit; + } + + // update the underlying layer of the current texture unit state map + void updateTexUnitStateMap() { + updateTexUnitStateMap(ctx, numActiveTexUnit, texUnitStateMap); + } + + boolean supportGlobalAlpha() { + return ((extensionsSupported & SUN_GLOBAL_ALPHA) != 0); + } + + boolean supportVideoResize() { + return ((extensionsSupported & SUN_VIDEO_RESIZE) != 0); + } + + /** enable separate specular color if the functionality + * is availabe for this canvas and it is not overriden by the + * property j3d.disableSeparateSpecular. + */ + void enableSeparateSpecularColor() { + if (((extensionsSupported & EXT_SEPARATE_SPECULAR_COLOR) != 0) && + !VirtualUniverse.mc.disableSeparateSpecularColor) { + updateSeparateSpecularColorEnable(ctx, true); + } + } + + final void beginScene() { + beginScene(ctx); + } + + final void endScene() { + endScene(ctx); + } + + void removeCtx(boolean offscreen) { + if ((screen != null) && + (screen.renderer != null) && + (ctx != 0)) { + VirtualUniverse.mc.postRequest(MasterControl.FREE_CONTEXT, + new Object[]{this, + new Long(screen.display), + new Integer(window), + new Long(ctx)}); + if (!offscreen) { + while (ctxTimeStamp != 0) { + MasterControl.threadYield(); + } + } + ctx = 0; + } + } + + /** + * Serialization of Canvas3D objects is not supported. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + private void writeObject(java.io.ObjectOutputStream out) + throws java.io.IOException { + + throw new UnsupportedOperationException(J3dI18N.getString("Canvas3D20")); + } + + /** + * Serialization of Canvas3D objects is not supported. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + private void readObject(java.io.ObjectInputStream in) + throws java.io.IOException, ClassNotFoundException { + + throw new UnsupportedOperationException(J3dI18N.getString("Canvas3D20")); + } + + + // mark that the current bin specified by the bit is already updated + void setStateIsUpdated(int bit) { + stateUpdateMask &= ~(1 << bit); + } + + // mark that the current bin specified by the bit needs to be updated + void setStateToUpdate(int bit) { + stateUpdateMask |= 1 << bit; + } + + // mark that the bin specified by the bit needs to be updated + void setStateToUpdate(int bit, Object bin) { + stateUpdateMask |= 1 << bit; + curStateToUpdate[bit] = bin; + } + + // update LightBin, EnvironmentSet, & AttributeBin if neccessary + // according to the stateUpdateMask + + static int ENV_STATE_MASK = (1 << LIGHTBIN_BIT) | + (1 << ENVIRONMENTSET_BIT) | + (1 << ATTRIBUTEBIN_BIT); + + void updateEnvState() { + + if ((stateUpdateMask & ENV_STATE_MASK) == 0) + return; + + if ((stateUpdateMask & (1 << LIGHTBIN_BIT)) != 0) { + ((LightBin)curStateToUpdate[LIGHTBIN_BIT]).updateAttributes(this); + } + + if ((stateUpdateMask & (1 << ENVIRONMENTSET_BIT)) != 0) { + ((EnvironmentSet) + curStateToUpdate[ENVIRONMENTSET_BIT]).updateAttributes(this); + } + + if ((stateUpdateMask & (1 << ATTRIBUTEBIN_BIT)) != 0) { + ((AttributeBin) + curStateToUpdate[ATTRIBUTEBIN_BIT]).updateAttributes(this); + } + + // reset the state update mask for those environment state bits + stateUpdateMask &= ~ENV_STATE_MASK; + } + + /** + * update state if neccessary according to the stateUpdatedMask + */ + void updateState(int pass, int dirtyBits) { + + + if (stateUpdateMask == 0) + return; + + updateEnvState(); + + if ((stateUpdateMask & (1 << TEXTUREBIN_BIT)) != 0) { + ((TextureBin) + curStateToUpdate[TEXTUREBIN_BIT]).updateAttributes(this, pass); + } + + if ((stateUpdateMask & (1 << RENDERMOLECULE_BIT)) != 0) { + ((RenderMolecule) + curStateToUpdate[RENDERMOLECULE_BIT]).updateAttributes(this, + dirtyBits); + + } + + if ((stateUpdateMask & (1 << TRANSPARENCY_BIT)) != 0) { + ((RenderMolecule)curStateToUpdate[RENDERMOLECULE_BIT]).updateTransparencyAttributes(this); + stateUpdateMask &= ~(1 << TRANSPARENCY_BIT); + } + + // reset state update mask + stateUpdateMask = 0; + } + + /** + * obj is either TextureRetained or DetailTextureImage + * if obj is DetailTextureImage then we just clear + * the resourceCreationMask of all the formats + * no matter it is create or not since we don't + * remember the format information for simplicity. + * We don't need to check duplicate value of id in the + * table since this procedure is invoke only when id + * of texture is -1 one time only. + * This is always call from Renderer thread. + */ + void addTextureResource(int id, Object obj) { + if (id <= 0) { + return; + } + + if (useSharedCtx) { + screen.renderer.addTextureResource(id, obj); + } else { + // This will replace the previous key if exists + if (textureIDResourceTable.size() <= id) { + for (int i=textureIDResourceTable.size(); + i < id; i++) { + textureIDResourceTable.add(null); + } + textureIDResourceTable.add(obj); + } else { + textureIDResourceTable.set(id, obj); + } + + } + } + + // handle free resource in the FreeList + void freeResourcesInFreeList(long ctx) { + Iterator it; + ArrayList list; + int i, val; + GeometryArrayRetained geo; + + // free resource for those canvases that + // don't use shared ctx + if (displayListResourceFreeList.size() > 0) { + for (it = displayListResourceFreeList.iterator(); it.hasNext();) { + val = ((Integer) it.next()).intValue(); + if (val <= 0) { + continue; + } + freeDisplayList(ctx, val); + } + displayListResourceFreeList.clear(); + } + + if (textureIdResourceFreeList.size() > 0) { + for (it = textureIdResourceFreeList.iterator(); it.hasNext();) { + val = ((Integer) it.next()).intValue(); + if (val <= 0) { + continue; + } + if (val >= textureIDResourceTable.size()) { + System.out.println("Error in freeResourcesInFreeList : ResourceIDTableSize = " + + textureIDResourceTable.size() + + " val = " + val); + } else { + textureIDResourceTable.set(val, null); + } + freeTexture(ctx, val); + } + textureIdResourceFreeList.clear(); + } + } + + void freeContextResources(Renderer rdr, boolean freeBackground, + long ctx) { + + + Object obj; + TextureRetained tex; + DetailTextureImage detailTex; + + if (rdr == null) { + return; + } + + if (freeBackground) { + // Free Background Texture + // Note that we use non-shared ctx to create + // it so there is no need to do so in + // Renderer.freeContextResources() + if (rdr.objectId > 0) { + Canvas3D.freeTexture(ctx, rdr.objectId); + VirtualUniverse.mc.freeTexture2DId(rdr.objectId); + rdr.objectId = -1; + + } + // Free Graphics2D Texture + if ((graphics2D != null) && + (graphics2D.objectId != -1)) { + Canvas3D.freeTexture(ctx, graphics2D.objectId); + VirtualUniverse.mc.freeTexture2DId(graphics2D.objectId); + graphics2D.objectId = -1; + } + } + + + for (int id = textureIDResourceTable.size()-1; id > 0; id--) { + obj = textureIDResourceTable.get(id); + if (obj == null) { + continue; + } + freeTexture(ctx, id); + if (obj instanceof TextureRetained) { + tex = (TextureRetained) obj; + synchronized (tex.resourceLock) { + tex.resourceCreationMask &= ~canvasBit; + if (tex.resourceCreationMask == 0) { + + tex.freeTextureId(id); + } + } + } else if (obj instanceof DetailTextureImage) { + detailTex = ((DetailTextureImage) obj); + detailTex.freeDetailTextureId(id, canvasBit); + } + } + textureIDResourceTable.clear(); + + freeAllDisplayListResources(); + } + + void freeAllDisplayListResources() { + if ((view != null) && (view.renderBin != null)) { + view.renderBin.freeAllDisplayListResources(this); + if (useSharedCtx) { + // We need to rebuild all other Canvas3D resource + // shared by this Canvas3D. Since we didn't + // remember resource in Renderer but RenderBin only. + if ((screen != null) && (screen.renderer != null)) { + screen.renderer.needToRebuildDisplayList = true; + } + } + } + + } +} diff --git a/src/classes/share/javax/media/j3d/CanvasViewCache.java b/src/classes/share/javax/media/j3d/CanvasViewCache.java new file mode 100644 index 0000000..43469bc --- /dev/null +++ b/src/classes/share/javax/media/j3d/CanvasViewCache.java @@ -0,0 +1,2007 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.Point; +import java.awt.Dimension; +import java.awt.Rectangle; +import java.awt.IllegalComponentStateException; +import javax.vecmath.*; + +/** + * The CanvasViewCache class is used to cache all data, both API data + * and derived data, that is dependent on the Canvas3D or Screen3D. + * The final view and projection matrices are stored here. + */ + +class CanvasViewCache extends Object { + // Used for debugging only + private static Object debugLock = new Object(); + + // The canvas associated with this canvas view cache + private Canvas3D canvas; + + // Mask that indicates this CanvasViewCache view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + int cvcDirtyMask = 0; + + // The screen view cache associated with this canvas view cache + private ScreenViewCache screenViewCache; + + // The view cache associated with this canvas view cache + private ViewCache viewCache; + + // ************* + // API/INPUT DATA + // ************* + + // The position and size of the canvas (in pixels) + private int awtCanvasX; + private int awtCanvasY; + private int awtCanvasWidth; + private int awtCanvasHeight; + + // The current RenderBin used for rendering during the frame + // associated with this snapshot. + private RenderBin renderBin; + + // Flag indicating whether or not stereo will be used. Computed by + // Canvas3D as: useStereo = stereoEnable && stereoAvailable + private boolean useStereo; + + // Current monoscopic view policy from canvas + private int monoscopicViewPolicy; + + // The manual positions of the left and right eyes in image-plate + // coordinates. + // Note that these values are only used in non-head-tracked mode + // when the view's window eyepoint policy is one of RELATIVE_TO_SCREEN + // or RELATIVE_TO_WINDOW. + private Point3d leftManualEyeInImagePlate = new Point3d(); + private Point3d rightManualEyeInImagePlate = new Point3d(); + + // ************* + // DERIVED DATA + // ************* + + // The width and height of the screen in meters (from ScreenViewCache) + double physicalScreenWidth; + double physicalScreenHeight; + + // The width and height of the screen in pixels (from ScreenViewCache) + int screenWidth; + int screenHeight; + + // Meters per pixel in the X and Y dimension (from ScreenViewCache) + double metersPerPixelX; + double metersPerPixelY; + + // The position and size of the canvas (in pixels) + private int canvasX; + private int canvasY; + private int canvasWidth; + private int canvasHeight; + + // Either the Canvas' or the View's monoscopicViewPolicy + private int effectiveMonoscopicViewPolicy; + + // The current cached projection transforms. + private Transform3D leftProjection = new Transform3D(); + private Transform3D rightProjection = new Transform3D(); + private Transform3D infLeftProjection = new Transform3D(); + private Transform3D infRightProjection = new Transform3D(); + + // The current cached viewing transforms. + private Transform3D leftVpcToEc = new Transform3D(); + private Transform3D rightVpcToEc = new Transform3D(); + private Transform3D infLeftVpcToEc = new Transform3D(); + private Transform3D infRightVpcToEc = new Transform3D(); + + // The current cached inverse viewing transforms. + private Transform3D leftEcToVpc = new Transform3D(); + private Transform3D rightEcToVpc = new Transform3D(); + private Transform3D infLeftEcToVpc = new Transform3D(); + private Transform3D infRightEcToVpc = new Transform3D(); + + // Arrays of Vector4d objects that represent the plane equations for + // the 6 planes in the viewing frustum in ViewPlatform coordinates. + private Vector4d[] leftFrustumPlanes = new Vector4d[6]; + private Vector4d[] rightFrustumPlanes = new Vector4d[6]; + + // Arrays of Vector4d objects that represent the volume of viewing frustum + private Point4d leftFrustumPoints[] = new Point4d[8]; + private Point4d rightFrustumPoints[] = new Point4d[8]; + + // Calibration matrix from Screen object for HMD mode using + // non-field-sequential stereo + + private Transform3D headTrackerToLeftImagePlate = new Transform3D(); + private Transform3D headTrackerToRightImagePlate = new Transform3D(); + + // Head tracked version of eye in imageplate + private Point3d leftTrackedEyeInImagePlate = new Point3d(); + private Point3d rightTrackedEyeInImagePlate = new Point3d(); + + // Derived version of eye in image plate coordinates + private Point3d leftEyeInImagePlate = new Point3d(); + private Point3d rightEyeInImagePlate = new Point3d(); + private Point3d centerEyeInImagePlate = new Point3d(); + + // Derived version of nominalEyeOffsetFromNominalScreen + private double nominalEyeOffset; + + // Physical window position,size and center (in image plate coordinates) + private double physicalWindowXLeft; + private double physicalWindowYBottom; + private double physicalWindowXRight; + private double physicalWindowYTop; + private double physicalWindowWidth; + private double physicalWindowHeight; + private Point3d physicalWindowCenter = new Point3d(); + + // Screen scale value from viewCache or from screen size. + private double screenScale; + + // Window scale value that compensates for window size if + // the window resize policy is PHYSICAL_WORLD. + private double windowScale; + + // ViewPlatform scale that takes coordinates from view platform + // coordinates and scales them to physical coordinates + private double viewPlatformScale; + + // Various derived transforms + + private Transform3D leftCcToVworld = new Transform3D(); + private Transform3D rightCcToVworld = new Transform3D(); + + private Transform3D coexistenceToLeftPlate = new Transform3D(); + private Transform3D coexistenceToRightPlate = new Transform3D(); + + private Transform3D vpcToCoexistence = new Transform3D(); + + private Transform3D vpcToLeftPlate = new Transform3D(); + private Transform3D vpcToRightPlate = new Transform3D(); + private Transform3D leftPlateToVpc = new Transform3D(); + private Transform3D rightPlateToVpc = new Transform3D(); + private Transform3D vworldToLeftPlate = new Transform3D(); + private Transform3D lastVworldToLeftPlate = new Transform3D(); + private Transform3D vworldToRightPlate = new Transform3D(); + private Transform3D leftPlateToVworld = new Transform3D(); + private Transform3D rightPlateToVworld = new Transform3D(); + private Transform3D headToLeftImagePlate = new Transform3D(); + private Transform3D headToRightImagePlate = new Transform3D(); + + private Transform3D vworldToTrackerBase = new Transform3D(); + private Transform3D tempTrans = new Transform3D(); + private Transform3D headToVworld = new Transform3D(); + private Vector3d coexistenceCenter = new Vector3d(); + + // scale for transformimg clip and fog distances + private double vworldToCoexistenceScale; + private double infVworldToCoexistenceScale; + + // + // Temporary matrices and vectors, so we dont generate garbage + // + private Transform3D tMat1 = new Transform3D(); + private Transform3D tMat2 = new Transform3D(); + private Vector3d tVec1 = new Vector3d(); + private Vector3d tVec2 = new Vector3d(); + private Vector3d tVec3 = new Vector3d(); + private Point3d tPnt1 = new Point3d(); + private Point3d tPnt2 = new Point3d(); + + private Matrix4d tMatrix = new Matrix4d(); + + /** + * The view platform transforms. + */ + private Transform3D vworldToVpc = new Transform3D(); + private Transform3D vpcToVworld = new Transform3D(); + private Transform3D infVworldToVpc = new Transform3D(); + + // This flag is used to remember the last time doInfinite flag + // is true or not. + // If this cache is updated twice, the first time in RenderBin + // updateViewCache() and the second time in Renderer with + // geometryBackground. The first time will reset the vcDirtyMask + // to 0 so that geometry background will not get updated the + // second time doComputeDerivedData() is invoked when view change. + private boolean lastDoInfinite = false; + private boolean updateLastTime = false; + + void getCanvasPositionAndSize() { + if(J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2) { + System.out.println("Get canvas position and size"); + System.out.println("Before"); + System.out.println("Canvas pos = (" + awtCanvasX + ", " + + awtCanvasY + "), size = " + awtCanvasWidth + + "x" + awtCanvasHeight); + System.out.println("After"); + } + awtCanvasX = canvas.newPosition.x; + awtCanvasY = canvas.newPosition.y; + awtCanvasWidth = canvas.newSize.width; + awtCanvasHeight = canvas.newSize.height; + + // The following works around problem when awt creates 0-size + // window at startup + if ((awtCanvasWidth <= 0) || (awtCanvasHeight <= 0)) { + awtCanvasWidth = 1; + awtCanvasHeight = 1; + } + + if (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1) { + System.out.println("Canvas pos = (" + awtCanvasX + ", " + + awtCanvasY + "), size = " + awtCanvasWidth + + "x" + awtCanvasHeight); + } + } + + void computefrustumBBox(BoundingBox frustumBBox) { + int i; + + for(i = 0; i < leftFrustumPoints.length; i++) { + if(frustumBBox.lower.x > leftFrustumPoints[i].x) + frustumBBox.lower.x = leftFrustumPoints[i].x; + if(frustumBBox.lower.y > leftFrustumPoints[i].y) + frustumBBox.lower.y = leftFrustumPoints[i].y; + if(frustumBBox.lower.z > leftFrustumPoints[i].z) + frustumBBox.lower.z = leftFrustumPoints[i].z; + + if(frustumBBox.upper.x < leftFrustumPoints[i].x) + frustumBBox.upper.x = leftFrustumPoints[i].x; + if(frustumBBox.upper.y < leftFrustumPoints[i].y) + frustumBBox.upper.y = leftFrustumPoints[i].y; + if(frustumBBox.upper.z < leftFrustumPoints[i].z) + frustumBBox.upper.z = leftFrustumPoints[i].z; + } + + if(useStereo) { + + for(i = 0; i< rightFrustumPoints.length; i++) { + if(frustumBBox.lower.x > rightFrustumPoints[i].x) + frustumBBox.lower.x = rightFrustumPoints[i].x; + if(frustumBBox.lower.y > rightFrustumPoints[i].y) + frustumBBox.lower.y = rightFrustumPoints[i].y; + if(frustumBBox.lower.z > rightFrustumPoints[i].z) + frustumBBox.lower.z = rightFrustumPoints[i].z; + + if(frustumBBox.upper.x < rightFrustumPoints[i].x) + frustumBBox.upper.x = rightFrustumPoints[i].x; + if(frustumBBox.upper.y < rightFrustumPoints[i].y) + frustumBBox.upper.y = rightFrustumPoints[i].y; + if(frustumBBox.upper.z < rightFrustumPoints[i].z) + frustumBBox.upper.z = rightFrustumPoints[i].z; + } + + } + } + + + void copyComputedCanvasViewCache(CanvasViewCache cvc, boolean doInfinite) { + // For performance reason, only data needed by renderer are copied. + // useStereo, + // canvasWidth, + // canvasHeight, + // leftProjection, + // rightProjection, + // leftVpcToEc, + // rightVpcToEc, + // leftFrustumPlanes, + // rightFrustumPlanes, + // vpcToVworld, + // vworldToVpc. + + cvc.useStereo = useStereo; + cvc.canvasWidth = canvasWidth; + cvc.canvasHeight = canvasHeight; + cvc.leftProjection.set(leftProjection); + cvc.rightProjection.set(rightProjection); + cvc.leftVpcToEc.set(leftVpcToEc) ; + cvc.rightVpcToEc.set(rightVpcToEc) ; + + cvc.vpcToVworld = vpcToVworld; + cvc.vworldToVpc.set(vworldToVpc); + + if (doInfinite) { + cvc.infLeftProjection.set(infLeftProjection); + cvc.infRightProjection.set(infRightProjection); + cvc.infLeftVpcToEc.set(infLeftVpcToEc) ; + cvc.infRightVpcToEc.set(infRightVpcToEc) ; + cvc.infVworldToVpc.set(infVworldToVpc); + } + + for (int i = 0; i < leftFrustumPlanes.length; i++) { + cvc.leftFrustumPlanes[i].x = leftFrustumPlanes[i].x; + cvc.leftFrustumPlanes[i].y = leftFrustumPlanes[i].y; + cvc.leftFrustumPlanes[i].z = leftFrustumPlanes[i].z; + cvc.leftFrustumPlanes[i].w = leftFrustumPlanes[i].w; + + cvc.rightFrustumPlanes[i].x = rightFrustumPlanes[i].x; + cvc.rightFrustumPlanes[i].y = rightFrustumPlanes[i].y; + cvc.rightFrustumPlanes[i].z = rightFrustumPlanes[i].z; + cvc.rightFrustumPlanes[i].w = rightFrustumPlanes[i].w; + } + } + + + /** + * Take snapshot of all per-canvas API parameters and input values. + * NOTE: This is probably not needed, but we'll do it for symmetry + * with the ScreenViewCache and ViewCache objects. + */ + synchronized void snapshot() { + cvcDirtyMask = canvas.cvDirtyMask; + canvas.cvDirtyMask = 0; + useStereo = canvas.useStereo; + monoscopicViewPolicy = canvas.monoscopicViewPolicy; + leftManualEyeInImagePlate.set(canvas.leftManualEyeInImagePlate); + rightManualEyeInImagePlate.set(canvas.rightManualEyeInImagePlate); + + if(( cvcDirtyMask & Canvas3D.MOVED_OR_RESIZED_DIRTY) != 0) { + getCanvasPositionAndSize(); + } + + renderBin = canvas.view.renderBin; + + } + + /** + * Compute derived data using the snapshot of the per-canvas, + * per-screen and per-view data. + */ + synchronized void computeDerivedData(boolean currentFlag, + CanvasViewCache cvc, BoundingBox frustumBBox, boolean doInfinite) { + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) { + synchronized(debugLock) { + System.out.println("------------------------------"); + doComputeDerivedData(currentFlag,cvc,frustumBBox,doInfinite); + } + } + else { + doComputeDerivedData(currentFlag,cvc,frustumBBox,doInfinite); + } + } + + /** + * Compute derived data using the snapshot of the per-canvas, + * per-screen and per-view data. Caller must synchronize before + * calling this method. + */ + private void doComputeDerivedData(boolean currentFlag, + CanvasViewCache cvc, BoundingBox frustumBBox, boolean doInfinite) { + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + if(cvcDirtyMask != 0) + System.out.println("cvcDirtyMask : " + cvcDirtyMask); + + if(screenViewCache.scrvcDirtyMask != 0) + System.out.println("scrvcDirtyMask : "+ + screenViewCache.scrvcDirtyMask); + + if(viewCache.vcDirtyMask != 0) + System.out.println("vcDirtyMask : " + viewCache.vcDirtyMask); + } + + + // NOTE: This fix is only fixing the symptoms, but not the + // root of the bug. We shouldn't have to check for null here. + if(viewCache.vpRetained == null) { + System.out.println("CanvasViewCache : Error! viewCache.vpRetained is null"); + return; + } + + // This flag is use to force a computation when a ViewPlatformTransform + // is detected. No sync. needed. We're doing a read of t/f. + // TODO: Peeking at the dirty flag is a hack. Need to revisit this. + boolean vprNotDirty = (viewCache.vpRetained.vprDirtyMask == 0); + + if(!canvas.offScreen && + (vprNotDirty) && + (cvcDirtyMask == 0) && + (screenViewCache.scrvcDirtyMask == 0) && + (viewCache.vcDirtyMask == 0) && + !(updateLastTime && (doInfinite != lastDoInfinite))) { + if(frustumBBox != null) + computefrustumBBox(frustumBBox); + + // Copy the computed data into cvc. + if(cvc != null) { + copyComputedCanvasViewCache(cvc, doInfinite); + } + lastDoInfinite = doInfinite; + updateLastTime = false; + return; + } + + lastDoInfinite = doInfinite; + updateLastTime = true; + + if(currentFlag) { + vpcToVworld.set(viewCache.vpRetained.getCurrentLocalToVworld(null)); + } + else { + vpcToVworld.set(viewCache.vpRetained.getLastLocalToVworld(null)); + } + + // System.out.println("vpcToVworld is \n" + vpcToVworld); + + try { + vworldToVpc.invert(vpcToVworld); + } + catch (SingularMatrixException e) { + vworldToVpc.setIdentity(); + //System.out.println("SingularMatrixException encountered when doing vworldToVpc invert"); + } + if (doInfinite) { + vworldToVpc.getRotation(infVworldToVpc); + } + + // Compute global flags + if (monoscopicViewPolicy == View.CYCLOPEAN_EYE_VIEW) + effectiveMonoscopicViewPolicy = viewCache.monoscopicViewPolicy; + else + effectiveMonoscopicViewPolicy = monoscopicViewPolicy; + + // Recompute info about current canvas window + computeCanvasInfo(); + + // Compute coexistence center (in plate coordinates) + computeCoexistenceCenter(); + + // Get Eye position in image-plate coordinates + cacheEyePosition(); + + // Compute VPC to COE and COE to PLATE transforms + computeVpcToCoexistence(); + computeCoexistenceToPlate(); + + // Compute view and projection matrices + computeView(doInfinite); + + + computePlateToVworld(); + + if (!currentFlag) { + // save the result for use in RasterRetained computeWinCoord + lastVworldToLeftPlate.set(vworldToLeftPlate); + } + computeHeadToVworld(); + + if (frustumBBox != null) + computefrustumBBox(frustumBBox); + + // Copy the computed data into cvc. + if(cvc != null) + copyComputedCanvasViewCache(cvc, doInfinite); + + canvas.canvasDirty |= Canvas3D.VWORLD_SCALE_DIRTY; + + // reset screen view dirty mask if canvas is offScreen. Note: + // there is only one canvas per offscreen, so it is ok to + // do the reset here. + if (canvas.offScreen) { + screenViewCache.scrvcDirtyMask = 0; + } + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) { + // Print some data : + System.out.println("useStereo = " + useStereo); + System.out.println("leftProjection:\n" + leftProjection); + System.out.println("rightProjection:\n " + rightProjection); + System.out.println("leftVpcToEc:\n" + leftVpcToEc); + System.out.println("rightVpcToEc:\n" + rightVpcToEc); + System.out.println("vpcToVworld:\n" + vpcToVworld); + System.out.println("vworldToVpc:\n" + vworldToVpc); + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + int i; + for (i = 0; i < leftFrustumPlanes.length; i++) { + System.out.println("leftFrustumPlanes " + i + " is " + + leftFrustumPlanes[i]); + } + + for (i = 0; i < rightFrustumPlanes.length; i++) { + System.out.println("rightFrustumPlanes " + i + " is " + + rightFrustumPlanes[i]); + } + } + } + + } + + private void computeCanvasInfo() { + // Copy the screen width and height info into derived parameters + physicalScreenWidth = screenViewCache.physicalScreenWidth; + physicalScreenHeight = screenViewCache.physicalScreenHeight; + + screenWidth = screenViewCache.screenWidth; + screenHeight = screenViewCache.screenHeight; + + metersPerPixelX = screenViewCache.metersPerPixelX; + metersPerPixelY = screenViewCache.metersPerPixelY; + + // If a multi-screen virtual device (e.g. Xinerama) is being used, + // then awtCanvasX and awtCanvasY are relative to the origin of that + // virtual screen. Subtract the origin of the physical screen to + // compute the origin in physical (image plate) coordinates. + Rectangle screenBounds = canvas.graphicsConfiguration.getBounds(); + canvasX = awtCanvasX - screenBounds.x; + canvasY = awtCanvasY - screenBounds.y; + + // Use awtCanvasWidth and awtCanvasHeight as reported. + canvasWidth = awtCanvasWidth; + canvasHeight = awtCanvasHeight; + + // Convert the window system ``pixel'' coordinate location and size + // of the window into physical units (meters) and coordinate system. + + // Window width and Height in meters + physicalWindowWidth = canvasWidth * metersPerPixelX; + physicalWindowHeight = canvasHeight * metersPerPixelY; + + // Compute the 4 corners of the window in physical units + physicalWindowXLeft = metersPerPixelX * + (double) canvasX; + physicalWindowYBottom = metersPerPixelY * + (double)(screenHeight - canvasHeight - canvasY); + + physicalWindowXRight = physicalWindowXLeft + physicalWindowWidth; + physicalWindowYTop = physicalWindowYBottom + physicalWindowHeight; + + // Cache the physical location of the center of the window + physicalWindowCenter.x = + physicalWindowXLeft + physicalWindowWidth / 2.0; + physicalWindowCenter.y = + physicalWindowYBottom + physicalWindowHeight / 2.0; + physicalWindowCenter.z = 0.0; + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("Canvas pos = (" + awtCanvasX + ", " + + awtCanvasY + "), size = " + awtCanvasWidth + + "x" + awtCanvasHeight); + + System.out.println("Window LL corner (in plate coordinates): " + + "(" + physicalWindowXLeft + "," + physicalWindowYBottom + ")"); + + System.out.println("Window size (in plate coordinates): " + + "(" + physicalWindowWidth + "," + physicalWindowHeight + ")"); + + System.out.println("Window center (in plate coordinates): " + + physicalWindowCenter); + + System.out.println(); + } + + // Compute the view platform scale. This combines + // the screen scale and the window scale. + computeViewPlatformScale(); + + if (!viewCache.compatibilityModeEnable && + viewCache.viewPolicy == View.HMD_VIEW) { + if (!useStereo) { + switch(effectiveMonoscopicViewPolicy) { + case View.CYCLOPEAN_EYE_VIEW: + if(J3dDebug.devPhase) { + System.out.println("CanvasViewCache : Should never reach here.\n" + + "HMD_VIEW with CYCLOPEAN_EYE_VIEW is not allowed"); + } + break; + + case View.LEFT_EYE_VIEW: + headTrackerToLeftImagePlate.set(screenViewCache. + headTrackerToLeftImagePlate); + break; + + case View.RIGHT_EYE_VIEW: + headTrackerToLeftImagePlate.set(screenViewCache. + headTrackerToRightImagePlate); + break; + } + } + else { + headTrackerToLeftImagePlate.set(screenViewCache. + headTrackerToLeftImagePlate); + + headTrackerToRightImagePlate.set(screenViewCache. + headTrackerToRightImagePlate); + } + + } + } + + // Routine to compute the center of coexistence coordinates in + // imageplate coordinates. Also compute the scale from Vpc + private void computeViewPlatformScale() { + windowScale = screenScale = 1.0; + + if (!viewCache.compatibilityModeEnable) { + switch (viewCache.screenScalePolicy) { + case View.SCALE_SCREEN_SIZE: + screenScale = physicalScreenWidth / 2.0; + break; + case View.SCALE_EXPLICIT: + screenScale = viewCache.screenScale; + break; + } + + if (viewCache.windowResizePolicy == View.PHYSICAL_WORLD) { + windowScale = physicalWindowWidth / physicalScreenWidth; + } + } + + viewPlatformScale = windowScale * screenScale; + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("viewCache.windowResizePolicy = " + + viewCache.windowResizePolicy); + System.out.println("physicalWindowWidth = " + physicalWindowWidth); + System.out.println("physicalScreenWidth = " + physicalScreenWidth); + System.out.println("windowScale = " + windowScale); + System.out.println("screenScale = " + screenScale); + System.out.println("viewPlatformScale = " + viewPlatformScale); + } + } + + private void cacheEyePosFixedField() { + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) + System.out.println("cacheEyePosFixedField:"); + + // y is always the window center + rightEyeInImagePlate.y = + leftEyeInImagePlate.y = + physicalWindowCenter.y; + + if (!useStereo) { + switch(effectiveMonoscopicViewPolicy) { + case View.CYCLOPEAN_EYE_VIEW: + leftEyeInImagePlate.x = physicalWindowCenter.x; + break; + + case View.LEFT_EYE_VIEW: + leftEyeInImagePlate.x = + physicalWindowCenter.x + viewCache.leftEyePosInHead.x; + break; + + case View.RIGHT_EYE_VIEW: + leftEyeInImagePlate.x = + physicalWindowCenter.x + viewCache.rightEyePosInHead.x; + break; + } + + // Set right as well just in case + rightEyeInImagePlate.x = leftEyeInImagePlate.x; + } + else { + leftEyeInImagePlate.x = + physicalWindowCenter.x + viewCache.leftEyePosInHead.x; + + rightEyeInImagePlate.x = + physicalWindowCenter.x + viewCache.rightEyePosInHead.x; + } + + // + // Derive the z distance by constraining the field of view of the + // window width to be constant. + // + rightEyeInImagePlate.z = + leftEyeInImagePlate.z = + physicalWindowWidth / + (2.0 * Math.tan(viewCache.fieldOfView / 2.0)); + + // Denote that eyes-in-ImagePlate fields have changed so that + // these new values can be sent to the AudioDevice + if (this.viewCache.view.soundScheduler != null) + this.viewCache.view.soundScheduler.setListenerFlag( + SoundScheduler.EYE_POSITIONS_CHANGED); + } + + /** + * Case of view eye position contrainted to center of window, but + * with z distance from plate eye pos. + */ + private void cacheEyePosWindowRelative() { + + if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) + System.out.println("cacheEyePosWindowRelative:"); + + // y is always the window center + rightEyeInImagePlate.y = + leftEyeInImagePlate.y = + physicalWindowCenter.y; + + // z is always from the existing eye pos + rightEyeInImagePlate.z = + leftEyeInImagePlate.z = + leftManualEyeInImagePlate.z; + + if (!useStereo) { + + switch(effectiveMonoscopicViewPolicy) { + + case View.CYCLOPEAN_EYE_VIEW: + leftEyeInImagePlate.x = + physicalWindowCenter.x; + break; + + case View.LEFT_EYE_VIEW: + leftEyeInImagePlate.x = + physicalWindowCenter.x + + viewCache.leftEyePosInHead.x; + break; + + case View.RIGHT_EYE_VIEW: + leftEyeInImagePlate.x = + physicalWindowCenter.x + + viewCache.rightEyePosInHead.x; + break; + + } + + // Set right as well just in case + rightEyeInImagePlate.x = + leftEyeInImagePlate.x; + + } + else { + + leftEyeInImagePlate.x = + physicalWindowCenter.x + + viewCache.leftEyePosInHead.x; + + rightEyeInImagePlate.x = + physicalWindowCenter.x + + viewCache.rightEyePosInHead.x; + + // Right z gets its own value + rightEyeInImagePlate.z = + rightManualEyeInImagePlate.z; + } + + // Denote that eyes-in-ImagePlate fields have changed so that + // these new values can be sent to the AudioDevice + if (this.viewCache.view.soundScheduler != null) + this.viewCache.view.soundScheduler.setListenerFlag( + SoundScheduler.EYE_POSITIONS_CHANGED); + } + + /** + * Common routine used when head tracking and when using manual + * relative_to_screen eyepoint policy. + */ + private void cacheEyePosScreenRelative(Point3d leftEye, Point3d rightEye) { + if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) + System.out.println("cacheEyePosScreenRelative:"); + + if (!useStereo) { + switch(effectiveMonoscopicViewPolicy) { + + case View.CYCLOPEAN_EYE_VIEW: + leftEyeInImagePlate.x = (leftEye.x + rightEye.x) / 2.0; + leftEyeInImagePlate.y = (leftEye.y + rightEye.y) / 2.0; + leftEyeInImagePlate.z = (leftEye.z + rightEye.z) / 2.0; + break; + + case View.LEFT_EYE_VIEW: + leftEyeInImagePlate.set(leftEye); + break; + + case View.RIGHT_EYE_VIEW: + leftEyeInImagePlate.set(rightEye); + break; + + } + + // Set right as well just in case + rightEyeInImagePlate.set(leftEyeInImagePlate); + } + else { + leftEyeInImagePlate.set(leftEye); + rightEyeInImagePlate.set(rightEye); + } + + // Denote that eyes-in-ImagePlate fields have changed so that + // these new values can be sent to the AudioDevice + if (this.viewCache.view.soundScheduler != null) + this.viewCache.view.soundScheduler.setListenerFlag( + SoundScheduler.EYE_POSITIONS_CHANGED); + } + + private void cacheEyePosCoexistenceRelative(Point3d leftManualEyeInCoexistence, + Point3d rightManualEyeInCoexistence) { + + tPnt1.set(leftManualEyeInCoexistence); + viewCache.coexistenceToTrackerBase.transform(tPnt1); + screenViewCache.trackerBaseToImagePlate.transform(tPnt1); + tPnt1.add(coexistenceCenter); + + tPnt2.set(rightManualEyeInCoexistence); + viewCache.coexistenceToTrackerBase.transform(tPnt2); + screenViewCache.trackerBaseToImagePlate.transform(tPnt2); + tPnt2.add(coexistenceCenter); + + cacheEyePosScreenRelative(tPnt1, tPnt2); + + } + + /** + * Compute the head-tracked eye position for the right and + * left eyes. + */ + private void computeTrackedEyePosition() { + if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("computeTrackedEyePosition:"); + System.out.println("viewCache.headTrackerToTrackerBase:"); + System.out.println(viewCache.headTrackerToTrackerBase); + + System.out.println("viewCache.headToHeadTracker:"); + System.out.println(viewCache.headToHeadTracker); + } + + if (viewCache.viewPolicy != View.HMD_VIEW) { + if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("screenViewCache.trackerBaseToImagePlate:"); + System.out.println(screenViewCache.trackerBaseToImagePlate); + } + + headToLeftImagePlate.set(coexistenceCenter); + headToLeftImagePlate.mul(screenViewCache.trackerBaseToImagePlate); + headToLeftImagePlate.mul(viewCache.headTrackerToTrackerBase); + headToLeftImagePlate.mul(viewCache.headToHeadTracker); + + headToLeftImagePlate.transform(viewCache.leftEyePosInHead, + leftTrackedEyeInImagePlate); + + headToLeftImagePlate.transform(viewCache.rightEyePosInHead, + rightTrackedEyeInImagePlate); + } + else { + if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("headTrackerToLeftImagePlate:"); + System.out.println(headTrackerToLeftImagePlate); + } + + headToLeftImagePlate.mul(headTrackerToLeftImagePlate, + viewCache.headToHeadTracker); + + headToLeftImagePlate.transform(viewCache.leftEyePosInHead, + leftTrackedEyeInImagePlate); + + if(useStereo) { + headToRightImagePlate.mul(headTrackerToRightImagePlate, + viewCache.headToHeadTracker); + + headToRightImagePlate.transform(viewCache.rightEyePosInHead, + rightTrackedEyeInImagePlate); + } + else { // HMD_VIEW with no stereo. + headToLeftImagePlate.transform(viewCache.rightEyePosInHead, + rightTrackedEyeInImagePlate); + } + + } + + if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("headToLeftImagePlate:"); + System.out.println(headToLeftImagePlate); + System.out.println("headToRightImagePlate:"); + System.out.println(headToRightImagePlate); + + } + } + + /** + * Routine to cache the current eye position in image plate + * coordinates. + */ + private void cacheEyePosition() { + if (viewCache.compatibilityModeEnable) { + // TODO: Compute compatibility mode eye position in ImagePlate??? + cacheEyePosScreenRelative(leftManualEyeInImagePlate, + rightManualEyeInImagePlate); + } + else if (viewCache.getDoHeadTracking()) { + computeTrackedEyePosition(); + cacheEyePosScreenRelative(leftTrackedEyeInImagePlate, + rightTrackedEyeInImagePlate); + } + else { + switch (viewCache.windowEyepointPolicy) { + + case View.RELATIVE_TO_FIELD_OF_VIEW: + cacheEyePosFixedField(); + break; + + case View.RELATIVE_TO_WINDOW: + cacheEyePosWindowRelative(); + break; + + case View.RELATIVE_TO_SCREEN: + cacheEyePosScreenRelative(leftManualEyeInImagePlate, + rightManualEyeInImagePlate); + break; + + case View.RELATIVE_TO_COEXISTENCE: + cacheEyePosCoexistenceRelative(viewCache.leftManualEyeInCoexistence, + viewCache.rightManualEyeInCoexistence); + break; + } + } + + // Compute center eye + centerEyeInImagePlate.add(leftEyeInImagePlate, rightEyeInImagePlate); + centerEyeInImagePlate.scale(0.5); + + // Compute derived value of nominalEyeOffsetFromNominalScreen + if (viewCache.windowEyepointPolicy == View.RELATIVE_TO_FIELD_OF_VIEW) + nominalEyeOffset = centerEyeInImagePlate.z; + else + nominalEyeOffset = viewCache.nominalEyeOffsetFromNominalScreen; + + if ((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) { + System.out.println("leftEyeInImagePlate = " + + leftEyeInImagePlate); + System.out.println("rightEyeInImagePlate = " + + rightEyeInImagePlate); + System.out.println("centerEyeInImagePlate = " + + centerEyeInImagePlate); + System.out.println("nominalEyeOffset = " + + nominalEyeOffset); + System.out.println(); + } + } + + private void computePlateToVworld() { + if (viewCache.compatibilityModeEnable) { + // TODO: implement this correctly for compat mode + leftPlateToVworld.setIdentity(); + vworldToLeftPlate.setIdentity(); + } + else { + try { + leftPlateToVpc.invert(vpcToLeftPlate); + } + catch (SingularMatrixException e) { + leftPlateToVpc.setIdentity(); + /* + System.out.println("SingularMatrixException encountered when doing" + + " leftPlateToVpc invert"); + */ + } + + leftPlateToVworld.mul(vpcToVworld, leftPlateToVpc); + vworldToLeftPlate.mul(vpcToLeftPlate, vworldToVpc); + + if(useStereo) { + try { + rightPlateToVpc.invert(vpcToRightPlate); + } + catch (SingularMatrixException e) { + rightPlateToVpc.setIdentity(); + /* + System.out.println("SingularMatrixException encountered when doing" + + " rightPlateToVpc invert"); + */ + } + + rightPlateToVworld.mul(vpcToVworld, rightPlateToVpc); + vworldToRightPlate.mul(vpcToRightPlate, vworldToVpc); + + } + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("vpcToVworld:"); + System.out.println(vpcToVworld); + System.out.println("vpcToLeftPlate:"); + System.out.println(vpcToLeftPlate); + if(useStereo) { + System.out.println("vpcToRightPlate:"); + System.out.println(vpcToRightPlate); + + } + + } + } + + // Denote that eyes-in-ImagePlate fields have changed so that + // these new values can be sent to the AudioDevice + if (this.viewCache.view.soundScheduler != null) + this.viewCache.view.soundScheduler.setListenerFlag( + SoundScheduler.IMAGE_PLATE_TO_VWORLD_CHANGED); + } + + + private void computeHeadToVworld() { + // Concatenate headToLeftImagePlate with leftPlateToVworld + + if (viewCache.compatibilityModeEnable) { + // TODO: implement this correctly for compat mode + headToVworld.setIdentity(); + } + else { + headToVworld.mul(leftPlateToVworld, headToLeftImagePlate); + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("leftPlateToVworld:"); + System.out.println(leftPlateToVworld); + System.out.println("headToLeftImagePlate:"); + System.out.println(headToLeftImagePlate); + System.out.println("...gives -> headToVworld:"); + System.out.println(headToVworld); + } + } + + // Denote that eyes-in-ImagePlate fields have changed so that + // these new values can be sent to the AudioDevice + if (this.viewCache.view.soundScheduler != null) + this.viewCache.view.soundScheduler.setListenerFlag( + SoundScheduler.HEAD_TO_VWORLD_CHANGED); + } + + private void computeVpcToCoexistence() { + // Create a transform with the view platform to coexistence scale + tMat1.set(viewPlatformScale); + + // TODO: Is this really correct to ignore HMD? + + if (viewCache.viewPolicy != View.HMD_VIEW) { + switch (viewCache.coexistenceCenterInPworldPolicy) { + case View.NOMINAL_SCREEN : + switch (viewCache.viewAttachPolicy) { + case View.NOMINAL_SCREEN: + tMat2.setIdentity(); + break; + case View.NOMINAL_HEAD: + tVec1.set(0.0, 0.0, nominalEyeOffset); + tMat2.set(tVec1); + break; + case View.NOMINAL_FEET: + tVec1.set(0.0, -viewCache.nominalEyeHeightFromGround, + nominalEyeOffset); + tMat2.set(tVec1); + break; + } + + break; + case View.NOMINAL_HEAD : + switch (viewCache.viewAttachPolicy) { + case View.NOMINAL_SCREEN: + tVec1.set(0.0, 0.0, -nominalEyeOffset); + tMat2.set(tVec1); + break; + case View.NOMINAL_HEAD: + tMat2.setIdentity(); + break; + case View.NOMINAL_FEET: + tVec1.set(0.0, -viewCache.nominalEyeHeightFromGround, + 0.0); + tMat2.set(tVec1); + break; + } + break; + case View.NOMINAL_FEET: + switch (viewCache.viewAttachPolicy) { + case View.NOMINAL_SCREEN: + tVec1.set(0.0, + viewCache.nominalEyeHeightFromGround, -nominalEyeOffset); + tMat2.set(tVec1); + break; + case View.NOMINAL_HEAD: + tVec1.set(0.0, viewCache.nominalEyeHeightFromGround, + 0.0); + tMat2.set(tVec1); + + break; + case View.NOMINAL_FEET: + tMat2.setIdentity(); + break; + } + break; + } + + vpcToCoexistence.mul(tMat2, tMat1); + } + else { + vpcToCoexistence.set(tMat1); + } + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("vpcToCoexistence:"); + System.out.println(vpcToCoexistence); + } + } + + private void computeCoexistenceCenter() { + + if ((!viewCache.compatibilityModeEnable) && + (viewCache.viewPolicy != View.HMD_VIEW) && + (viewCache.coexistenceCenteringEnable) && + (viewCache.coexistenceCenterInPworldPolicy == View.NOMINAL_SCREEN)) { + + // Compute the coexistence center in image plate coordinates + + // Image plate cordinates have their orgin in the lower + // left hand corner of the CRT visiable raster. + // The nominal coexistence center is at the *center* of + // targeted area: either the window or screen, depending + // on policy. + if (viewCache.windowMovementPolicy == View.VIRTUAL_WORLD) { + coexistenceCenter.x = physicalScreenWidth / 2.0; + coexistenceCenter.y = physicalScreenHeight / 2.0; + coexistenceCenter.z = 0.0; + } + else { // windowMovementPolicy == PHYSICAL_WORLD + coexistenceCenter.x = physicalWindowCenter.x; + coexistenceCenter.y = physicalWindowCenter.y; + coexistenceCenter.z = 0.0; + } + } + else { + coexistenceCenter.set(0.0, 0.0, 0.0); + } + + if(J3dDebug.devPhase) { + if (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1) { + System.out.println("coexistenceCenter = " + coexistenceCenter); + } + } + } + + private void computeCoexistenceToPlate() { + if (viewCache.compatibilityModeEnable) { + // TODO: implement this correctly + coexistenceToLeftPlate.setIdentity(); + return; + } + + if (viewCache.viewPolicy != View.HMD_VIEW) { + coexistenceToLeftPlate.set(coexistenceCenter); + coexistenceToLeftPlate.mul(screenViewCache.trackerBaseToImagePlate); + coexistenceToLeftPlate.mul(viewCache.coexistenceToTrackerBase); + + if(useStereo) { + coexistenceToRightPlate.set(coexistenceToLeftPlate); + } + } + else { + coexistenceToLeftPlate.mul(headTrackerToLeftImagePlate, + viewCache.trackerBaseToHeadTracker); + coexistenceToLeftPlate.mul(viewCache.coexistenceToTrackerBase); + + if(useStereo) { + coexistenceToRightPlate.mul(headTrackerToRightImagePlate, + viewCache.trackerBaseToHeadTracker); + coexistenceToRightPlate.mul(viewCache.coexistenceToTrackerBase); + } + } + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("coexistenceToLeftPlate:"); + System.out.println(coexistenceToLeftPlate); + if(useStereo) { + System.out.println("coexistenceToRightPlate:"); + System.out.println(coexistenceToRightPlate); + + } + } + } + + /** + * Computes the viewing matrices. + * + * computeView computes the following: + * + * <ul> + * left (& right) eye viewing matrices (only left is valid for mono view) + * </ul> + * + * This call works for both fixed screen and HMD displays. + */ + private void computeView(boolean doInfinite) { + int i,j; + int backClipPolicy; + double Fl, Fr, B, scale, backClipDistance; + + // compute scale used for transforming clip and fog distances + vworldToCoexistenceScale = vworldToVpc.getDistanceScale() + * vpcToCoexistence.getDistanceScale(); + if(doInfinite) { + infVworldToCoexistenceScale = infVworldToVpc.getDistanceScale() + * vpcToCoexistence.getDistanceScale(); + } + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("vworldToCoexistenceScale = " + + vworldToCoexistenceScale); + } + + // compute coexistenceToVworld transform -- dirty bit candidate!! + tempTrans.mul(viewCache.coexistenceToTrackerBase, vpcToCoexistence); + vworldToTrackerBase.mul(tempTrans, vworldToVpc); + + // If we are in compatibility mode, compute the view and + // projection matrices accordingly + if (viewCache.compatibilityModeEnable) { + leftProjection.set(viewCache.compatLeftProjection); + leftVpcToEc.set(viewCache.compatVpcToEc); + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) { + System.out.println("Left projection and view matrices"); + System.out.println("ecToCc (leftProjection) :"); + System.out.println(leftProjection); + System.out.println("vpcToEc:"); + System.out.println(leftVpcToEc); + } + + computeFrustumPlanes(leftProjection, leftVpcToEc, + leftFrustumPlanes, leftFrustumPoints, + leftCcToVworld); + + if(useStereo) { + rightProjection.set(viewCache.compatRightProjection); + rightVpcToEc.set(viewCache.compatVpcToEc); + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) { + System.out.println("Right projection and view matrices"); + System.out.println("ecToCc:"); + System.out.println("vpcToEc:"); + System.out.println(rightVpcToEc); + } + + computeFrustumPlanes(rightProjection, rightVpcToEc, + rightFrustumPlanes, rightFrustumPoints, + rightCcToVworld); + } + + return; + } + + // + // The clipping plane distances are set from the internal policy. + // + // Note that the plane distance follows the standard Z axis + // convention, e.g. negative numbers further away. + // Note that for policy from eye, the distance is negative in + // the direction of z in front of the eye. + // Note that for policy from screen, the distance is negative for + // locations behind the screen, and positive in front. + // + // The distance attributes are measured either in physical (plate) + // units, or vworld units. + // + + // Compute scale factor for front clip plane computation + if (viewCache.frontClipPolicy == View.VIRTUAL_EYE || + viewCache.frontClipPolicy == View.VIRTUAL_SCREEN) { + scale = vworldToCoexistenceScale; + } + else { + scale = windowScale; + } + + // Set left and right front clipping plane distances. + if(viewCache.frontClipPolicy == View.PHYSICAL_EYE || + viewCache.frontClipPolicy == View.VIRTUAL_EYE) { + Fl = leftEyeInImagePlate.z + + scale * -viewCache.frontClipDistance; + Fr = rightEyeInImagePlate.z + + scale * -viewCache.frontClipDistance; + } + else { + Fl = scale * -viewCache.frontClipDistance; + Fr = scale * -viewCache.frontClipDistance; + } + + // if there is an active clip node, use it and ignore the view's + // backclip + if ((renderBin != null) && (renderBin.backClipActive)) { + backClipPolicy = View.VIRTUAL_EYE; + backClipDistance = renderBin.backClipDistanceInVworld; + } else { + backClipPolicy = viewCache.backClipPolicy; + backClipDistance = viewCache.backClipDistance; + } + + // Compute scale factor for rear clip plane computation + if (backClipPolicy == View.VIRTUAL_EYE || + backClipPolicy == View.VIRTUAL_SCREEN) { + scale = vworldToCoexistenceScale; + } + else { + scale = windowScale; + } + + // Set left and right rear clipping plane distnaces. + if(backClipPolicy == View.PHYSICAL_EYE || + backClipPolicy == View.VIRTUAL_EYE) { + // Yes, left for both left and right rear. + B = leftEyeInImagePlate.z + + scale * -backClipDistance; + } + else { + B = scale * -backClipDistance; + } + + // TODO: Can optimize for HMD case. + if (true /*viewCache.viewPolicy != View.HMD_VIEW*/) { + + // Call buildProjView to build the projection and view matrices. + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("Left projection and view matrices"); + System.out.println("Fl " + Fl + " B " + B); + System.out.println("leftEyeInImagePlate\n" + leftEyeInImagePlate); + System.out.println("Before : leftProjection\n" + leftProjection); + System.out.println("Before leftVpcToEc\n" + leftVpcToEc); + } + + buildProjView(leftEyeInImagePlate, coexistenceToLeftPlate, + vpcToLeftPlate, Fl, B, leftProjection, leftVpcToEc, false); + + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("After : leftProjection\n" + leftProjection); + System.out.println("After leftVpcToEc\n" + leftVpcToEc); + } + + computeFrustumPlanes(leftProjection, leftVpcToEc, + leftFrustumPlanes, leftFrustumPoints, + leftCcToVworld); + + if(useStereo) { + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) + System.out.println("Right projection and view matrices"); + + buildProjView(rightEyeInImagePlate, coexistenceToRightPlate, + vpcToRightPlate, Fr, B, rightProjection, + rightVpcToEc, false); + + computeFrustumPlanes(rightProjection, rightVpcToEc, + rightFrustumPlanes, rightFrustumPoints, + rightCcToVworld); + } + + // + // Now to compute the left (& right) eye (and infinite) + // viewing matrices. + if(doInfinite) { + // Call buildProjView separately for infinite view + buildProjView(leftEyeInImagePlate, coexistenceToLeftPlate, + vpcToLeftPlate, leftEyeInImagePlate.z - 0.05, + leftEyeInImagePlate.z - 1.5, + infLeftProjection, infLeftVpcToEc, true); + + if(useStereo) { + buildProjView(rightEyeInImagePlate, coexistenceToRightPlate, + vpcToRightPlate, rightEyeInImagePlate.z - 0.05, + rightEyeInImagePlate.z - 1.5, + infRightProjection, infRightVpcToEc, true); + + } + } + } + // TODO: The following code has never been ported +// else { +// Point3d cen_eye; +// +// // HMD case. Just concatenate the approprate matrices together. +// // Additional work just for now +// +// compute_lr_plate_to_cc( &cen_eye, Fl, B, 0, &vb, 0); +// +// if(useStereo) { +// mat_mul_dpt(&right_eye_pos_in_head, +// head_to_right_plate, &cen_eye); +// compute_lr_plate_to_cc( &cen_eye, Fr, B, +// 1, &vb, 0); +// } +// +// // Make sure that coexistence_to_plate is current. +// // (It is usually constant for fixed plates, always varies for HMDs.) +// // For HMD case, computes finial matrices that will be used. +// // +// computeCoexistenceToPlate(); +// } + + } + + /** + * Debugging routine to analyze the projection matrix. + */ + private void analyzeProjection(Transform3D p, double xMax) { + if (viewCache.projectionPolicy == View.PARALLEL_PROJECTION) + System.out.println("PARALLEL_PROJECTION ="); + else + System.out.println("PERSPECTIVE_PROJECTION ="); + + System.out.println(p); + + double projectionPlaneZ = ((p.mat[0] * xMax + p.mat[3] - p.mat[15]) / + (p.mat[14] - p.mat[2])); + + System.out.println("projection plane at z = " + projectionPlaneZ); + } + + /** + * buildProjView creates a projection and viewing matrix. + * + * Inputs: + * ep : eye point, in plate coordinates + * coe2Plate : matrix from coexistence to image plate. + * F, B : front, back clipping planes, in plate coordinates + * doInfinite : flag to indicate ``at infinity'' view desired + * + * Output: + * vpc2Plate : matric from vpc to image plate. + * ecToCc : projection matrix from Eye Coordinates (EC) + * to Clipping Coordinates (CC) + * vpcToEc : view matrix from ViewPlatform Coordinates (VPC) + * to Eye Coordinates (EC) + */ + private void buildProjView(Point3d ep, + Transform3D coe2Plate, + Transform3D vpc2Plate, + double F, + double B, + Transform3D ecToCc, + Transform3D vpcToEc, + boolean doInfinite) { + + // Lx,Ly Hx,Hy will be adjusted window boundaries + double Lx, Hx, Ly, Hy; + Lx = physicalWindowXLeft; Hx = physicalWindowXRight; + Ly = physicalWindowYBottom; Hy = physicalWindowYTop; + + ecToCc.setIdentity(); + + + // TODO: we have no concept of glass correction in the Java 3D API + // + // Correction in apparent 3D position of window due to glass/CRT + // and spherical/cylinderical curvarure of CRT. + // This boils down to producing modified values of Lx Ly Hx Hy + // and is different for hot spot vs. window center corrections. + // + /* TODO: + double cx, cy; + if(viewPolicy != HMD_VIEW && enable_crt_glass_correction) { + if (correction_point == CORRECTION_POINT_WINDOW_CENTER) { + correct_crt( ep, Lx, Ly, &cx, &cy); Lx = cx; Ly = cy; + correct_crt( ep, Hx, Hy, &cx, &cy); Hx = cx; Hy = cy; + } + else { // must be hot spot correction + // Not real code yet, for now just do same as above. + correct_crt( ep, Lx, Ly, &cx, &cy); Lx = cx; Ly = cy; + correct_crt( ep, Hx, Hy, &cx, &cy); Hx = cx; Hy = cy; + } + } + */ + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("ep = " + ep); + System.out.println("Lx = " + Lx + ", Hx = " + Hx); + System.out.println("Ly = " + Ly + ", Hy = " + Hy); + System.out.println("F = " + F + ", B = " + B); + } + + // Compute the proper projection equation. Note that we + // do this in two steps: first we generate ImagePlateToCc, + // then we translate this by EcToPlate, resulting in a + // projection from EctoCc. + // + // A more efficient (and more accurate) approach would be to + // modify the equations below to directly project from EcToCc. + + if (viewCache.projectionPolicy == View.PARALLEL_PROJECTION) { + double inv_dx, inv_dy, inv_dz; + inv_dx = 1.0 / (Hx - Lx); + inv_dy = 1.0 / (Hy - Ly); + inv_dz = 1.0 / (F - B); + + ecToCc.mat[0] = 2.0 * inv_dx; + ecToCc.mat[3] = -(Hx + Lx) * inv_dx; + ecToCc.mat[5] = 2.0 * inv_dy; + ecToCc.mat[7] = -(Hy + Ly) * inv_dy; + ecToCc.mat[10] = 2.0 * inv_dz; + ecToCc.mat[11] = -(F + B) * inv_dz; + } + else { + double sxy, rzb, inv_dx, inv_dy; + + inv_dx = 1.0 / (Hx - Lx); + inv_dy = 1.0 / (Hy - Ly); + rzb = 1.0/(ep.z - B); + sxy = ep.z*rzb; + + ecToCc.mat[0] = sxy*2.0*inv_dx; + ecToCc.mat[5] = sxy*2.0*inv_dy; + + ecToCc.mat[2] = rzb*(Hx+Lx - 2.0*ep.x)*inv_dx; + ecToCc.mat[6] = rzb*(Hy+Ly - 2.0*ep.y)*inv_dy; + ecToCc.mat[10] = rzb*(B+F-2*ep.z)/(B-F); + ecToCc.mat[14] = -rzb; + + ecToCc.mat[3] = sxy*(-Hx-Lx)*inv_dx; + ecToCc.mat[7] = sxy*(-Hy-Ly)*inv_dy; + ecToCc.mat[11] = rzb*(B - ep.z - B*(B+F - 2*ep.z)/(B-F)); + ecToCc.mat[15] = sxy; + } + + // Since we set the matrix elements ourselves, we need to set the + // type field. A value of 0 means a non-affine matrix. + ecToCc.setOrthoDirtyBit(); + + // EC to ImagePlate matrix is a simple translation. + tVec1.set(ep.x, ep.y, ep.z); + tMat1.set(tVec1); + ecToCc.mul(tMat1); + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("ecToCc:"); + analyzeProjection(ecToCc, Hx); + } + + if(!doInfinite) { + // View matrix is: + // [plateToEc] [coexistence_to_plate] [vpc_to_coexistence] + // where vpc_to_coexistence includes the viewPlatformScale + + // First compute ViewPlatform to Plate + vpc2Plate.mul(coe2Plate, vpcToCoexistence); + + // ImagePlate to EC matrix is a simple translation. + tVec1.set(-ep.x, -ep.y, -ep.z); + tMat1.set(tVec1); + vpcToEc.mul(tMat1, vpc2Plate); + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + System.out.println("vpcToEc:"); + System.out.println(vpcToEc); + } + } + else { + // Final infinite composite is: + // [coexistence_to_eye] [vpc_to_coexistence (vom)] + // (does vworld_to_coe_scale_factor get used here??? ) + // + // The method is to relocate the coexistence org centered on + // the eye rather than the window center (via coexistence_to_eye). + // Computationaly simpler simplifed equation form may exist. + + // coexistence to eye is a simple translation. +/* + tVec1.set(ep.x, ep.y, ep.z); + tMat1.set(tVec1); + vpcToEc.mul(tMat1, vpcToCoexistence); + // First compute ViewPlatform to Plate + vpcToPlate.mul(coexistenceToPlatevpcToPlate, vpcToCoexistence); +*/ + + // ImagePlate to EC matrix is a simple translation. + tVec1.set(-ep.x, -ep.y, -ep.z); + tMat1.set(tVec1); + tMat1.mul(tMat1, vpc2Plate); + tMat1.getRotation(vpcToEc); // use only rotation component of transform + + } + + } + + /** + * Compute the plane equations for the frustum in ViewPlatform + * coordinates, plus its viewing frustum points. ccToVworld will + * be cached - used by Canavs3D.getInverseVworldProjection(). + */ + private void computeFrustumPlanes(Transform3D ecToCc, + Transform3D vpcToEc, + Vector4d [] frustumPlanes, + Point4d [] frustumPoints, + Transform3D ccToVworld) { + + // Compute the inverse of the Vworld to Cc transform. This + // gives us the Cc to Vworld transform. + tMat2.mul(ecToCc, vpcToEc); + ccToVworld.mul(tMat2, vworldToVpc); + // System.out.println("ccToVworld = " + ccToVworld); + try { + ccToVworld.invert(); + } + catch (SingularMatrixException e) { + ccToVworld.setIdentity(); + // System.out.println("SingularMatrixException encountered when doing invert in computeFrustumPlanes"); + } + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_2)) { + Transform3D t = new Transform3D(); + t.mul(ecToCc, vpcToEc); + t.mul(vworldToVpc); + System.out.println("\nvworldToCc = " + t); + System.out.println("ccToVworld = " + ccToVworld); + t.mul(ccToVworld); + System.out.println("vworldToCc * ccToVworld = " + t); + } + + // Transform the 8 corners of the viewing frustum into Vpc + frustumPoints[0].set(-1.0, -1.0, 1.0, 1.0); // lower-left-front + frustumPoints[1].set(-1.0, 1.0, 1.0, 1.0); // upper-left-front + frustumPoints[2].set( 1.0, 1.0, 1.0, 1.0); // upper-right-front + frustumPoints[3].set( 1.0, -1.0, 1.0, 1.0); // lower-right-front + frustumPoints[4].set(-1.0, -1.0, -1.0, 1.0); // lower-left-back + frustumPoints[5].set(-1.0, 1.0, -1.0, 1.0); // upper-left-back + frustumPoints[6].set( 1.0, 1.0, -1.0, 1.0); // upper-right-back + frustumPoints[7].set( 1.0, -1.0, -1.0, 1.0); // lower-right-back + + ccToVworld.get(tMatrix); + int i; + for (i = 0; i < frustumPoints.length; i++) { + tMatrix.transform(frustumPoints[i]); + double w_inv = 1.0 / frustumPoints[i].w; + frustumPoints[i].x *= w_inv; + frustumPoints[i].y *= w_inv; + frustumPoints[i].z *= w_inv; + } + + // Now compute the 6 plane equations + // left + computePlaneEq(frustumPoints[0], frustumPoints[4], + frustumPoints[5], frustumPoints[1], + frustumPlanes[0]); + + // right + computePlaneEq(frustumPoints[3], frustumPoints[2], + frustumPoints[6], frustumPoints[7], + frustumPlanes[1]); + + // top + computePlaneEq(frustumPoints[1], frustumPoints[5], + frustumPoints[6], frustumPoints[2], + frustumPlanes[2]); + + // bottom + computePlaneEq(frustumPoints[0], frustumPoints[3], + frustumPoints[7], frustumPoints[4], + frustumPlanes[3]); + + // front + computePlaneEq(frustumPoints[0], frustumPoints[1], + frustumPoints[2], frustumPoints[3], + frustumPlanes[4]); + + // back + computePlaneEq(frustumPoints[4], frustumPoints[7], + frustumPoints[6], frustumPoints[5], + frustumPlanes[5]); + + //System.out.println("left plane = " + frustumPlanes[0]); + //System.out.println("right plane = " + frustumPlanes[1]); + //System.out.println("top plane = " + frustumPlanes[2]); + //System.out.println("bottom plane = " + frustumPlanes[3]); + //System.out.println("front plane = " + frustumPlanes[4]); + //System.out.println("back plane = " + frustumPlanes[5]); + } + + private void computePlaneEq(Point4d p1, Point4d p2, Point4d p3, Point4d p4, + Vector4d planeEq) { + tVec1.x = p3.x - p1.x; + tVec1.y = p3.y - p1.y; + tVec1.z = p3.z - p1.z; + + tVec2.x = p2.x - p1.x; + tVec2.y = p2.y - p1.y; + tVec2.z = p2.z - p1.z; + + tVec3.cross(tVec2, tVec1); + tVec3.normalize(); + planeEq.x = tVec3.x; + planeEq.y = tVec3.y; + planeEq.z = tVec3.z; + planeEq.w = -(planeEq.x * p1.x + planeEq.y * p1.y + planeEq.z * p1.z); + } + + // Get methods for returning derived data values. + // Eventually, these get functions will cause some of the parameters + // to be lazily evaluated. + // + // NOTE: in the case of Transform3D, and Tuple objects, a reference + // to the actual derived data is returned. In these cases, the caller + // must ensure that the returned data is not modified. + // + // NOTE: the snapshot and computeDerivedData methods are synchronized. + // Callers of the following methods that can run asynchronously with + // the renderer must call these methods and copy the data from within + // a synchronized block on the canvas view cache object. + + int getCanvasX() { + return canvasX; + } + + int getCanvasY() { + return canvasY; + } + + int getCanvasWidth() { + return canvasWidth; + } + + int getCanvasHeight() { + return canvasHeight; + } + + double getPhysicalWindowWidth() { + return physicalWindowWidth; + } + + double getPhysicalWindowHeight() { + return physicalWindowHeight; + } + + boolean getUseStereo() { + return useStereo; + } + + Transform3D getLeftProjection() { + return leftProjection; + } + + Transform3D getRightProjection() { + return rightProjection; + } + + Transform3D getLeftVpcToEc() { + return leftVpcToEc; + } + + Transform3D getRightVpcToEc() { + return rightVpcToEc; + } + + Transform3D getLeftEcToVpc() { + return leftEcToVpc; + } + + Transform3D getRightEcToVpc() { + return rightEcToVpc; + } + + Transform3D getInfLeftProjection() { + return infLeftProjection; + } + + Transform3D getInfRightProjection() { + return infLeftProjection; + } + + Transform3D getInfLeftVpcToEc() { + return infLeftVpcToEc; + } + + Transform3D getInfRightVpcToEc() { + return infRightVpcToEc; + } + + Transform3D getInfLeftEcToVpc() { + return infLeftEcToVpc; + } + + Transform3D getInfgRightEcToVpc() { + return infRightEcToVpc; + } + + Transform3D getInfVworldToVpc() { + return infVworldToVpc; + } + + Transform3D getLeftCcToVworld() { + return leftCcToVworld; + } + + Transform3D getRightCcToVworld() { + return rightCcToVworld; + } + + Transform3D getImagePlateToVworld() { + // TODO: Document -- This will return the transform of left plate. + return leftPlateToVworld; + } + + + + Transform3D getLastVworldToImagePlate() { + // TODO: Document -- This will return the transform of left plate. + return lastVworldToLeftPlate; + + } + + Transform3D getVworldToImagePlate() { + // TODO: Document -- This will return the transform of left plate. + return vworldToLeftPlate; + } + + Transform3D getVworldToTrackerBase() { + return vworldToTrackerBase; + } + + double getVworldToCoexistenceScale() { + return vworldToCoexistenceScale; + } + + double getInfVworldToCoexistenceScale() { + return infVworldToCoexistenceScale; + } + + Point3d getLeftEyeInImagePlate() { + return leftEyeInImagePlate; + } + + Point3d getRightEyeInImagePlate() { + return rightEyeInImagePlate; + } + + Point3d getCenterEyeInImagePlate() { + return centerEyeInImagePlate; + } + + Transform3D getHeadToVworld() { + return headToVworld; + } + + Transform3D getVpcToVworld() { + return vpcToVworld; + } + + Transform3D getVworldToVpc() { + return vworldToVpc; + } + + + // Transform the specified X point in AWT window-relative coordinates + // to image plate coordinates + double getWindowXInImagePlate(double x) { + double xScreen = x + (double)canvasX; + return metersPerPixelX * xScreen; + } + + // Transform the specified Y point in AWT window-relative coordinates + // to image plate coordinates + double getWindowYInImagePlate(double y) { + double yScreen = y + (double)canvasY; + return metersPerPixelY * ((double)(screenHeight - 1) - yScreen); + } + + Vector4d[] getLeftFrustumPlanesInVworld() { + return leftFrustumPlanes; + } + + Vector4d[] getRightFrustumPlanesInVworld() { + return rightFrustumPlanes; + } + + + void getPixelLocationInImagePlate(double x, double y, double z, + Point3d imagePlatePoint) { + + double screenx = (x + canvasX)*metersPerPixelX; + double screeny = (screenHeight - 1 - canvasY - y)*metersPerPixelY; + + if ((viewCache.projectionPolicy == View.PERSPECTIVE_PROJECTION) && + (centerEyeInImagePlate.z != 0)) { + double zScale = 1.0 - z/centerEyeInImagePlate.z; + imagePlatePoint.x = (screenx - centerEyeInImagePlate.x)*zScale + + centerEyeInImagePlate.x; + imagePlatePoint.y = (screeny - centerEyeInImagePlate.y)*zScale + + centerEyeInImagePlate.y; + } else { + imagePlatePoint.x = screenx; + imagePlatePoint.y = screeny; + } + imagePlatePoint.z = z; + } + + /** + * Projects the specified point from image plate coordinates + * into AWT pixel coordinates. + */ + void getPixelLocationFromImagePlate(Point3d imagePlatePoint, + Point2d pixelLocation) { + + double screenX, screenY; + + if(viewCache.projectionPolicy == View.PERSPECTIVE_PROJECTION) { + // get the vector from centerEyeInImagePlate to imagePlatePoint + tVec1.sub(imagePlatePoint, centerEyeInImagePlate); + + // Scale this vector to make it end at the projection plane. + // Scale is ratio : + // eye->imagePlate Plane dist / eye->imagePlatePt dist + // eye dist to plane is eyePos.z (eye is in +z space) + // image->eye dist is -tVec1.z (image->eye is in -z dir) + //System.out.println("eye dist = " + (centerEyeInImagePlate.z)); + //System.out.println("image dist = " + (-tVec1.z)); + if (tVec1.z != 0) { + double zScale = centerEyeInImagePlate.z / (-tVec1.z); + screenX = centerEyeInImagePlate.x + tVec1.x * zScale; + screenY = centerEyeInImagePlate.y + tVec1.y * zScale; + + } else { + screenX = imagePlatePoint.x; + screenY = imagePlatePoint.y; + } + + } else { + screenX = imagePlatePoint.x; + screenY = imagePlatePoint.y; + } + + //System.out.println("screenX = " + screenX + " screenY = " + screenY); + // Note: screenPt is in image plate coords, at z=0 + + // Transform from image plate coords to screen coords + pixelLocation.x = (screenX / screenViewCache.metersPerPixelX) - canvasX; + pixelLocation.y = screenViewCache.screenHeight - 1 - + (screenY / screenViewCache.metersPerPixelY) - canvasY; + //System.out.println("pixelLocation = " + pixelLocation); + } + + /** + * Constructs and initializes a CanvasViewCache object. + * Note that the canvas, screen, screenCache, view, and + * viewCache parameters are all fixed at construction time + * and must be non-null. + */ + CanvasViewCache(Canvas3D canvas, + ScreenViewCache screenViewCache, + ViewCache viewCache) { + + this.canvas = canvas; + this.screenViewCache = screenViewCache; + this.viewCache = viewCache; + + // Set up the initial plane equations + int i; + for (i = 0; i < leftFrustumPlanes.length; i++) { + leftFrustumPlanes[i] = new Vector4d(); + rightFrustumPlanes[i] = new Vector4d(); + } + + for (i = 0; i < leftFrustumPoints.length; i++) { + leftFrustumPoints[i] = new Point4d(); + rightFrustumPoints[i] = new Point4d(); + } + + // canvas is null in Renderer copyOfCvCache + if (canvas != null) { + leftEyeInImagePlate.set(canvas.leftManualEyeInImagePlate); + rightEyeInImagePlate.set(canvas.rightManualEyeInImagePlate); + centerEyeInImagePlate.add(leftEyeInImagePlate, + rightEyeInImagePlate); + centerEyeInImagePlate.scale(0.5); + } + + if((J3dDebug.devPhase) && (J3dDebug.canvasViewCache >= J3dDebug.LEVEL_1)) + System.out.println("Constructed a CanvasViewCache"); + } + + synchronized void setCanvas(Canvas3D c) { + canvas = c; + } + + synchronized void setScreenViewCache(ScreenViewCache svc) { + screenViewCache = svc; + } + + synchronized void setViewCache(ViewCache vc) { + viewCache = vc; + } +} diff --git a/src/classes/share/javax/media/j3d/CanvasViewEventCatcher.java b/src/classes/share/javax/media/j3d/CanvasViewEventCatcher.java new file mode 100644 index 0000000..b84af92 --- /dev/null +++ b/src/classes/share/javax/media/j3d/CanvasViewEventCatcher.java @@ -0,0 +1,78 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; +import java.awt.*; +import java.awt.event.*; + +/** + * The CanvasViewEventCatcher class is used to track events on a Canvas3D that + * may cause view matries to change. + * + */ +class CanvasViewEventCatcher extends ComponentAdapter { + + // The canvas associated with this event catcher + Canvas3D canvas; + ArrayList parentList = new ArrayList(); + static final boolean DEBUG = false; + + CanvasViewEventCatcher(Canvas3D c) { + canvas = c; + } + + public void componentResized(ComponentEvent e) { + if (DEBUG) { + System.out.println("Component resized " + e); + } + + if(e.getComponent() == canvas ) { + if (DEBUG) { + System.out.println("It is canvas!"); + } + synchronized(canvas) { + canvas.cvDirtyMask |= Canvas3D.MOVED_OR_RESIZED_DIRTY; + canvas.resizeGraphics2D = true; + } + + // see comment below + try { + canvas.newSize = canvas.getSize(); + canvas.newPosition = canvas.getLocationOnScreen(); + } catch (IllegalComponentStateException ex) {} + + } + } + + public void componentMoved(ComponentEvent e) { + if (DEBUG) { + System.out.println("Component moved " + e); + } + + synchronized(canvas) { + canvas.cvDirtyMask |= Canvas3D.MOVED_OR_RESIZED_DIRTY; + } + // Can't sync. with canvas lock since canvas.getLocationOnScreen() + // required Component lock. The order is reverse of + // removeNotify() lock sequence which required Component lock + // first, then canvas lock in removeComponentListener() + + try { + canvas.newSize = canvas.getSize(); + canvas.newPosition = canvas.getLocationOnScreen(); + } catch (IllegalComponentStateException ex) {} + + } + +} diff --git a/src/classes/share/javax/media/j3d/CapabilityBits.java b/src/classes/share/javax/media/j3d/CapabilityBits.java new file mode 100644 index 0000000..db42d48 --- /dev/null +++ b/src/classes/share/javax/media/j3d/CapabilityBits.java @@ -0,0 +1,492 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * This CapabilityBits class provides a global namespace for all + * capability bits + */ +class CapabilityBits extends Object { + + // SceneGraphObject + + // Node extends SceneGraphObject + static final int NODE_ENABLE_COLLISION_REPORTING = 0; + static final int NODE_ENABLE_PICK_REPORTING = 1; + static final int NODE_ALLOW_PICK = 2; + static final int NODE_ALLOW_BOUNDS_READ = 3; + static final int NODE_ALLOW_BOUNDS_WRITE = 4; + static final int NODE_ALLOW_PICKABLE_READ = 5; + static final int NODE_ALLOW_PICKABLE_WRITE = 6; + static final int NODE_ALLOW_COLLIDABLE_READ = 7; + static final int NODE_ALLOW_COLLIDABLE_WRITE = 8; + static final int NODE_ALLOW_AUTO_COMPUTE_BOUNDS_READ = 9; + static final int NODE_ALLOW_AUTO_COMPUTE_BOUNDS_WRITE = 10; + static final int NODE_ALLOW_LOCAL_TO_VWORLD_READ = 11; + + + // Group extends Node + static final int GROUP_ALLOW_CHILDREN_READ = 12; + static final int GROUP_ALLOW_CHILDREN_WRITE = 13; + static final int GROUP_ALLOW_CHILDREN_EXTEND = 14; + static final int GROUP_ALLOW_COLLISION_BOUNDS_READ = 15; + static final int GROUP_ALLOW_COLLISION_BOUNDS_WRITE = 16; + + // BranchGroup extends Group + static final int BRANCH_GROUP_ALLOW_DETACH = 17; + + // SharedGroup extends Group + static final int SHARED_GROUP_ALLOW_LINK_READ = 17; + + // TransformGroup extends Group + static final int TRANSFORM_GROUP_ALLOW_TRANSFORM_READ = 17; + static final int TRANSFORM_GROUP_ALLOW_TRANSFORM_WRITE = 18; + + // Switch extends Group + static final int SWITCH_ALLOW_SWITCH_READ = 17; + static final int SWITCH_ALLOW_SWITCH_WRITE = 18; + + // ViewSpecificGroup extends Group + static final int VIEW_SPECIFIC_GROUP_ALLOW_VIEW_READ = 17; + static final int VIEW_SPECIFIC_GROUP_ALLOW_VIEW_WRITE = 18; + + // OrderedGroup extends Group + static final int ORDERED_GROUP_ALLOW_CHILD_INDEX_ORDER_READ = 17; + static final int ORDERED_GROUP_ALLOW_CHILD_INDEX_ORDER_WRITE = 18; + + + // Leaf extends Node + + // Background extends Leaf + static final int BACKGROUND_ALLOW_APPLICATION_BOUNDS_READ = 12; + static final int BACKGROUND_ALLOW_APPLICATION_BOUNDS_WRITE = 13; + static final int BACKGROUND_ALLOW_IMAGE_READ = 14; + static final int BACKGROUND_ALLOW_IMAGE_WRITE = 15; + static final int BACKGROUND_ALLOW_COLOR_READ = 16; + static final int BACKGROUND_ALLOW_COLOR_WRITE = 17; + static final int BACKGROUND_ALLOW_GEOMETRY_READ = 18; + static final int BACKGROUND_ALLOW_GEOMETRY_WRITE = 19; + static final int BACKGROUND_ALLOW_IMAGE_SCALE_MODE_READ = 20; + static final int BACKGROUND_ALLOW_IMAGE_SCALE_MODE_WRITE = 21; + + // BoundingLeaf extends Leaf + static final int BOUNDING_LEAF_ALLOW_REGION_READ = 12; + static final int BOUNDING_LEAF_ALLOW_REGION_WRITE = 13; + + // Clip extends Leaf + static final int CLIP_ALLOW_APPLICATION_BOUNDS_READ = 12; + static final int CLIP_ALLOW_APPLICATION_BOUNDS_WRITE = 13; + static final int CLIP_ALLOW_BACK_DISTANCE_READ = 14; + static final int CLIP_ALLOW_BACK_DISTANCE_WRITE = 15; + + // Morph extends Leaf + static final int MORPH_ALLOW_GEOMETRY_ARRAY_READ = 12; + static final int MORPH_ALLOW_GEOMETRY_ARRAY_WRITE = 13; + static final int MORPH_ALLOW_APPEARANCE_READ = 14; + static final int MORPH_ALLOW_APPEARANCE_WRITE = 15; + static final int MORPH_ALLOW_WEIGHTS_READ = 16; + static final int MORPH_ALLOW_WEIGHTS_WRITE = 17; + static final int MORPH_ALLOW_COLLISION_BOUNDS_READ = 18; + static final int MORPH_ALLOW_COLLISION_BOUNDS_WRITE = 19; + static final int MORPH_ALLOW_APPEARANCE_OVERRIDE_READ = 20; + static final int MORPH_ALLOW_APPEARANCE_OVERRIDE_WRITE = 21; + + // Link extends Leaf + static final int LINK_ALLOW_SHARED_GROUP_READ = 12; + static final int LINK_ALLOW_SHARED_GROUP_WRITE = 13; + + // Shape3D extends Leaf + static final int SHAPE3D_ALLOW_GEOMETRY_READ = 12; + static final int SHAPE3D_ALLOW_GEOMETRY_WRITE = 13; + static final int SHAPE3D_ALLOW_APPEARANCE_READ = 14; + static final int SHAPE3D_ALLOW_APPEARANCE_WRITE = 15; + static final int SHAPE3D_ALLOW_COLLISION_BOUNDS_READ = 16; + static final int SHAPE3D_ALLOW_COLLISION_BOUNDS_WRITE = 17; + static final int SHAPE3D_ALLOW_APPEARANCE_OVERRIDE_READ = 18; + static final int SHAPE3D_ALLOW_APPEARANCE_OVERRIDE_WRITE = 19; + + // OrientedShape3D extends Shape3D + static final int ORIENTED_SHAPE3D_ALLOW_MODE_READ = 20; + static final int ORIENTED_SHAPE3D_ALLOW_MODE_WRITE = 21; + static final int ORIENTED_SHAPE3D_ALLOW_AXIS_READ = 22; + static final int ORIENTED_SHAPE3D_ALLOW_AXIS_WRITE = 23; + static final int ORIENTED_SHAPE3D_ALLOW_POINT_READ = 24; + static final int ORIENTED_SHAPE3D_ALLOW_POINT_WRITE = 25; + static final int ORIENTED_SHAPE3D_ALLOW_SCALE_READ = 26; + static final int ORIENTED_SHAPE3D_ALLOW_SCALE_WRITE = 27; + + // Soundscape extends Leaf + static final int SOUNDSCAPE_ALLOW_APPLICATION_BOUNDS_READ = 12; + static final int SOUNDSCAPE_ALLOW_APPLICATION_BOUNDS_WRITE = 13; + static final int SOUNDSCAPE_ALLOW_ATTRIBUTES_READ = 14; + static final int SOUNDSCAPE_ALLOW_ATTRIBUTES_WRITE = 15; + + // ViewPlatform extends Leaf + static final int VIEW_PLATFORM_ALLOW_POLICY_READ = 12; + static final int VIEW_PLATFORM_ALLOW_POLICY_WRITE = 13; + + // Fog extends Leaf + static final int FOG_ALLOW_INFLUENCING_BOUNDS_READ = 12; + static final int FOG_ALLOW_INFLUENCING_BOUNDS_WRITE = 13; + static final int FOG_ALLOW_COLOR_READ = 14; + static final int FOG_ALLOW_COLOR_WRITE = 15; + + // ExponentialFog extends Fog + static final int EXPONENTIAL_FOG_ALLOW_DENSITY_READ = 16; + static final int EXPONENTIAL_FOG_ALLOW_DENSITY_WRITE = 17; + + // LinearFog extends Fog + static final int LINEAR_FOG_ALLOW_DISTANCE_READ = 16; + static final int LINEAR_FOG_ALLOW_DISTANCE_WRITE = 17; + + // Additional Fog bits (must go after LinearFog bits) + static final int FOG_ALLOW_SCOPE_READ = 18; + static final int FOG_ALLOW_SCOPE_WRITE = 19; + + // Light extends Leaf + static final int LIGHT_ALLOW_STATE_READ = 12; + static final int LIGHT_ALLOW_STATE_WRITE = 13; + static final int LIGHT_ALLOW_COLOR_READ = 14; + static final int LIGHT_ALLOW_COLOR_WRITE = 15; + static final int LIGHT_ALLOW_INFLUENCING_BOUNDS_READ = 16; + static final int LIGHT_ALLOW_INFLUENCING_BOUNDS_WRITE = 17; + + // DirectionalLight extends Light + static final int DIRECTIONAL_LIGHT_ALLOW_DIRECTION_READ = 18; + static final int DIRECTIONAL_LIGHT_ALLOW_DIRECTION_WRITE = 19; + + // PointLight extends Light + static final int POINT_LIGHT_ALLOW_POSITION_READ = 18; + static final int POINT_LIGHT_ALLOW_POSITION_WRITE = 19; + static final int POINT_LIGHT_ALLOW_ATTENUATION_READ = 20; + static final int POINT_LIGHT_ALLOW_ATTENUATION_WRITE = 21; + + // SpotLight extends PointLight + static final int SPOT_LIGHT_ALLOW_SPREAD_ANGLE_WRITE = 22; + static final int SPOT_LIGHT_ALLOW_SPREAD_ANGLE_READ = 23; + static final int SPOT_LIGHT_ALLOW_CONCENTRATION_WRITE = 24; + static final int SPOT_LIGHT_ALLOW_CONCENTRATION_READ = 25; + static final int SPOT_LIGHT_ALLOW_DIRECTION_WRITE = 26; + static final int SPOT_LIGHT_ALLOW_DIRECTION_READ = 27; + + // Additional Light bits (must go after SpotLight bits) + static final int LIGHT_ALLOW_SCOPE_READ = 28; + static final int LIGHT_ALLOW_SCOPE_WRITE = 29; + + // Sound extends Leaf + static final int SOUND_ALLOW_SOUND_DATA_READ = 12; + static final int SOUND_ALLOW_SOUND_DATA_WRITE = 13; + static final int SOUND_ALLOW_INITIAL_GAIN_READ = 14; + static final int SOUND_ALLOW_INITIAL_GAIN_WRITE = 15; + static final int SOUND_ALLOW_LOOP_READ = 16; + static final int SOUND_ALLOW_LOOP_WRITE = 17; + static final int SOUND_ALLOW_RELEASE_READ = 18; + static final int SOUND_ALLOW_RELEASE_WRITE = 19; + static final int SOUND_ALLOW_CONT_PLAY_READ = 20; + static final int SOUND_ALLOW_CONT_PLAY_WRITE = 21; + static final int SOUND_ALLOW_ENABLE_READ = 22; + static final int SOUND_ALLOW_ENABLE_WRITE = 23; + static final int SOUND_ALLOW_SCHEDULING_BOUNDS_READ = 24; + static final int SOUND_ALLOW_SCHEDULING_BOUNDS_WRITE = 25; + static final int SOUND_ALLOW_PRIORITY_READ = 26; + static final int SOUND_ALLOW_PRIORITY_WRITE = 27; + static final int SOUND_ALLOW_DURATION_READ = 28; + static final int SOUND_ALLOW_IS_READY_READ = 29; + static final int SOUND_ALLOW_IS_PLAYING_READ = 30; + static final int SOUND_ALLOW_CHANNELS_USED_READ = 31; + static final int SOUND_ALLOW_MUTE_READ = 40; + static final int SOUND_ALLOW_MUTE_WRITE = 41; + static final int SOUND_ALLOW_PAUSE_READ = 42; + static final int SOUND_ALLOW_PAUSE_WRITE = 43; + static final int SOUND_ALLOW_RATE_SCALE_FACTOR_READ = 44; + static final int SOUND_ALLOW_RATE_SCALE_FACTOR_WRITE = 45; + + // PointSound extends Sound + static final int POINT_SOUND_ALLOW_POSITION_READ = 32; + static final int POINT_SOUND_ALLOW_POSITION_WRITE = 33; + static final int POINT_SOUND_ALLOW_DISTANCE_GAIN_READ = 34; + static final int POINT_SOUND_ALLOW_DISTANCE_GAIN_WRITE = 35; + + // ConeSound extends PointSound + static final int CONE_SOUND_ALLOW_DIRECTION_READ = 36; + static final int CONE_SOUND_ALLOW_DIRECTION_WRITE = 37; + static final int CONE_SOUND_ALLOW_ANGULAR_ATTENUATION_READ = 38; + static final int CONE_SOUND_ALLOW_ANGULAR_ATTENUATION_WRITE = 39; + + // ModelClip extends Leaf + static final int MODEL_CLIP_ALLOW_INFLUENCING_BOUNDS_READ = 12; + static final int MODEL_CLIP_ALLOW_INFLUENCING_BOUNDS_WRITE = 13; + static final int MODEL_CLIP_ALLOW_PLANE_READ = 14; + static final int MODEL_CLIP_ALLOW_PLANE_WRITE = 15; + static final int MODEL_CLIP_ALLOW_ENABLE_READ = 16; + static final int MODEL_CLIP_ALLOW_ENABLE_WRITE = 17; + static final int MODEL_CLIP_ALLOW_SCOPE_READ = 18; + static final int MODEL_CLIP_ALLOW_SCOPE_WRITE = 19; + + // AlternateAppearance extends Leaf + static final int ALTERNATE_APPEARANCE_ALLOW_INFLUENCING_BOUNDS_READ = 12; + static final int ALTERNATE_APPEARANCE_ALLOW_INFLUENCING_BOUNDS_WRITE = 13; + static final int ALTERNATE_APPEARANCE_ALLOW_APPEARANCE_READ = 14; + static final int ALTERNATE_APPEARANCE_ALLOW_APPEARANCE_WRITE = 15; + static final int ALTERNATE_APPEARANCE_ALLOW_SCOPE_READ = 16; + static final int ALTERNATE_APPEARANCE_ALLOW_SCOPE_WRITE = 17; + + // NodeComponent extends SceneGraphObject + + // Appearance extends NodeComponent + static final int APPEARANCE_ALLOW_MATERIAL_READ = 0; + static final int APPEARANCE_ALLOW_MATERIAL_WRITE = 1; + static final int APPEARANCE_ALLOW_TEXTURE_READ = 2; + static final int APPEARANCE_ALLOW_TEXTURE_WRITE = 3; + static final int APPEARANCE_ALLOW_TEXGEN_READ = 4; + static final int APPEARANCE_ALLOW_TEXGEN_WRITE = 5; + static final int APPEARANCE_ALLOW_TEXTURE_ATTRIBUTES_READ = 6; + static final int APPEARANCE_ALLOW_TEXTURE_ATTRIBUTES_WRITE = 7; + static final int APPEARANCE_ALLOW_COLORING_ATTRIBUTES_READ = 8; + static final int APPEARANCE_ALLOW_COLORING_ATTRIBUTES_WRITE = 9; + static final int APPEARANCE_ALLOW_TRANSPARENCY_ATTRIBUTES_READ = 10; + static final int APPEARANCE_ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE = 11; + static final int APPEARANCE_ALLOW_RENDERING_ATTRIBUTES_READ = 12; + static final int APPEARANCE_ALLOW_RENDERING_ATTRIBUTES_WRITE = 13; + static final int APPEARANCE_ALLOW_POLYGON_ATTRIBUTES_READ = 14; + static final int APPEARANCE_ALLOW_POLYGON_ATTRIBUTES_WRITE = 15; + static final int APPEARANCE_ALLOW_LINE_ATTRIBUTES_READ = 16; + static final int APPEARANCE_ALLOW_LINE_ATTRIBUTES_WRITE = 17; + static final int APPEARANCE_ALLOW_POINT_ATTRIBUTES_READ = 18; + static final int APPEARANCE_ALLOW_POINT_ATTRIBUTES_WRITE = 19; + static final int APPEARANCE_ALLOW_TEXTURE_UNIT_STATE_READ = 20; + static final int APPEARANCE_ALLOW_TEXTURE_UNIT_STATE_WRITE = 21; + + // AuralAttributes extends NodeComponent + static final int AURAL_ATTRIBUTES_ALLOW_ATTRIBUTE_GAIN_READ = 0; + static final int AURAL_ATTRIBUTES_ALLOW_ATTRIBUTE_GAIN_WRITE = 1; + static final int AURAL_ATTRIBUTES_ALLOW_ROLLOFF_READ = 2; + static final int AURAL_ATTRIBUTES_ALLOW_ROLLOFF_WRITE = 3; + static final int AURAL_ATTRIBUTES_ALLOW_REFLECTION_COEFFICIENT_READ = 4; + static final int AURAL_ATTRIBUTES_ALLOW_REFLECTION_COEFFICIENT_WRITE = 5; + static final int AURAL_ATTRIBUTES_ALLOW_REVERB_DELAY_READ = 6; + static final int AURAL_ATTRIBUTES_ALLOW_REVERB_DELAY_WRITE = 7; + static final int AURAL_ATTRIBUTES_ALLOW_REVERB_ORDER_READ = 8; + static final int AURAL_ATTRIBUTES_ALLOW_REVERB_ORDER_WRITE = 9; + static final int AURAL_ATTRIBUTES_ALLOW_DISTANCE_FILTER_READ = 10; + static final int AURAL_ATTRIBUTES_ALLOW_DISTANCE_FILTER_WRITE = 11; + static final int AURAL_ATTRIBUTES_ALLOW_FREQUENCY_SCALE_FACTOR_READ = 12; + static final int AURAL_ATTRIBUTES_ALLOW_FREQUENCY_SCALE_FACTOR_WRITE = 13; + static final int AURAL_ATTRIBUTES_ALLOW_VELOCITY_SCALE_FACTOR_READ = 14; + static final int AURAL_ATTRIBUTES_ALLOW_VELOCITY_SCALE_FACTOR_WRITE = 15; + static final int AURAL_ATTRIBUTES_ALLOW_REFLECTION_DELAY_READ = 16; + static final int AURAL_ATTRIBUTES_ALLOW_REFLECTION_DELAY_WRITE = 17; + static final int AURAL_ATTRIBUTES_ALLOW_REVERB_COEFFICIENT_READ = 18; + static final int AURAL_ATTRIBUTES_ALLOW_REVERB_COEFFICIENT_WRITE = 19; + static final int AURAL_ATTRIBUTES_ALLOW_DECAY_TIME_READ = 20; + static final int AURAL_ATTRIBUTES_ALLOW_DECAY_TIME_WRITE = 21; + static final int AURAL_ATTRIBUTES_ALLOW_DECAY_FILTER_READ = 22; + static final int AURAL_ATTRIBUTES_ALLOW_DECAY_FILTER_WRITE = 23; + static final int AURAL_ATTRIBUTES_ALLOW_DIFFUSION_READ = 24; + static final int AURAL_ATTRIBUTES_ALLOW_DIFFUSION_WRITE = 25; + static final int AURAL_ATTRIBUTES_ALLOW_DENSITY_READ = 26; + static final int AURAL_ATTRIBUTES_ALLOW_DENSITY_WRITE = 27; + + // ColoringAttributes extends NodeComponent + static final int COLORING_ATTRIBUTES_ALLOW_COLOR_READ = 0; + static final int COLORING_ATTRIBUTES_ALLOW_COLOR_WRITE = 1; + static final int COLORING_ATTRIBUTES_ALLOW_SHADE_MODEL_READ = 2; + static final int COLORING_ATTRIBUTES_ALLOW_SHADE_MODEL_WRITE = 3; + + // DepthComponent extends NodeComponent + static final int DEPTH_COMPONENT_ALLOW_SIZE_READ = 0; + static final int DEPTH_COMPONENT_ALLOW_DATA_READ = 1; + + // ImageComponent extends NodeComponent + static final int IMAGE_COMPONENT_ALLOW_SIZE_READ = 0; + static final int IMAGE_COMPONENT_ALLOW_FORMAT_READ = 1; + static final int IMAGE_COMPONENT_ALLOW_IMAGE_READ = 2; + static final int IMAGE_COMPONENT_ALLOW_IMAGE_WRITE = 3; + + // LineAttributes extends NodeComponent + static final int LINE_ATTRIBUTES_ALLOW_WIDTH_READ = 0; + static final int LINE_ATTRIBUTES_ALLOW_WIDTH_WRITE = 1; + static final int LINE_ATTRIBUTES_ALLOW_PATTERN_READ = 2; + static final int LINE_ATTRIBUTES_ALLOW_PATTERN_WRITE = 3; + static final int LINE_ATTRIBUTES_ALLOW_ANTIALIASING_READ = 4; + static final int LINE_ATTRIBUTES_ALLOW_ANTIALIASING_WRITE = 5; + + // Material extends NodeComponent + static final int MATERIAL_ALLOW_COMPONENT_READ = 0; + static final int MATERIAL_ALLOW_COMPONENT_WRITE = 1; + + // MediaContainer extends NodeComponent + static final int MEDIA_CONTAINER_ALLOW_CACHE_READ = 0; + static final int MEDIA_CONTAINER_ALLOW_CACHE_WRITE = 1; + static final int MEDIA_CONTAINER_ALLOW_URL_READ = 2; + static final int MEDIA_CONTAINER_ALLOW_URL_WRITE = 3; + + // PointAttributes extends NodeComponent + static final int POINT_ATTRIBUTES_ALLOW_SIZE_READ = 0; + static final int POINT_ATTRIBUTES_ALLOW_SIZE_WRITE = 1; + static final int POINT_ATTRIBUTES_ALLOW_ANTIALIASING_READ = 2; + static final int POINT_ATTRIBUTES_ALLOW_ANTIALIASING_WRITE = 3; + + // PolygonAttributes extends NodeComponent + static final int POLYGON_ATTRIBUTES_ALLOW_CULL_FACE_READ = 0; + static final int POLYGON_ATTRIBUTES_ALLOW_CULL_FACE_WRITE = 1; + static final int POLYGON_ATTRIBUTES_ALLOW_MODE_READ = 2; + static final int POLYGON_ATTRIBUTES_ALLOW_MODE_WRITE = 3; + static final int POLYGON_ATTRIBUTES_ALLOW_OFFSET_READ = 4; + static final int POLYGON_ATTRIBUTES_ALLOW_OFFSET_WRITE = 5; + static final int POLYGON_ATTRIBUTES_ALLOW_NORMAL_FLIP_READ = 6; + static final int POLYGON_ATTRIBUTES_ALLOW_NORMAL_FLIP_WRITE = 7; + + // RenderingAttributes extends NodeComponent + static final int RENDERING_ATTRIBUTES_ALLOW_ALPHA_TEST_VALUE_READ = 0; + static final int RENDERING_ATTRIBUTES_ALLOW_ALPHA_TEST_VALUE_WRITE = 1; + static final int RENDERING_ATTRIBUTES_ALLOW_ALPHA_TEST_FUNCTION_READ = 2; + static final int RENDERING_ATTRIBUTES_ALLOW_ALPHA_TEST_FUNCTION_WRITE = 3; + static final int RENDERING_ATTRIBUTES_ALLOW_DEPTH_ENABLE_READ = 4; + static final int RENDERING_ATTRIBUTES_ALLOW_VISIBLE_READ = 5; + static final int RENDERING_ATTRIBUTES_ALLOW_VISIBLE_WRITE = 6; + static final int RENDERING_ATTRIBUTES_ALLOW_RASTER_OP_READ = 7; + static final int RENDERING_ATTRIBUTES_ALLOW_RASTER_OP_WRITE = 8; + static final int + RENDERING_ATTRIBUTES_ALLOW_IGNORE_VERTEX_COLORS_READ = 9; + static final int + RENDERING_ATTRIBUTES_ALLOW_IGNORE_VERTEX_COLORS_WRITE = 10; + static final int RENDERING_ATTRIBUTES_ALLOW_DEPTH_ENABLE_WRITE = 11; + + // TexCoordGeneration extends NodeComponent + static final int TEX_COORD_GENERATION_ALLOW_ENABLE_READ = 0; + static final int TEX_COORD_GENERATION_ALLOW_ENABLE_WRITE = 1; + static final int TEX_COORD_GENERATION_ALLOW_FORMAT_READ = 2; + static final int TEX_COORD_GENERATION_ALLOW_MODE_READ = 3; + static final int TEX_COORD_GENERATION_ALLOW_PLANE_READ = 4; + static final int TEX_COORD_GENERATION_ALLOW_PLANE_WRITE = 5; + + // Texture extends NodeComponent + static final int TEXTURE_ALLOW_ENABLE_READ = 0; + static final int TEXTURE_ALLOW_ENABLE_WRITE = 1; + static final int TEXTURE_ALLOW_BOUNDARY_MODE_READ = 2; + static final int TEXTURE_ALLOW_FILTER_READ = 3; + static final int TEXTURE_ALLOW_IMAGE_READ = 4; + static final int TEXTURE_ALLOW_MIPMAP_MODE_READ = 5; + static final int TEXTURE_ALLOW_BOUNDARY_COLOR_READ = 6; + static final int TEXTURE_ALLOW_IMAGE_WRITE = 7; + static final int TEXTURE_ALLOW_SIZE_READ = 8; + static final int TEXTURE_ALLOW_FORMAT_READ = 9; + static final int TEXTURE_ALLOW_LOD_RANGE_READ = 10; + static final int TEXTURE_ALLOW_LOD_RANGE_WRITE = 11; + static final int TEXTURE_ALLOW_ANISOTROPIC_FILTER_READ = 12; + static final int TEXTURE_ALLOW_SHARPEN_TEXTURE_READ = 13; + static final int TEXTURE_ALLOW_FILTER4_READ = 14; + + // Texture2D extends Texture + static final int TEXTURE2D_ALLOW_DETAIL_TEXTURE_READ = 15; + + // TextureAttributes extends NodeComponent + static final int TEXTURE_ATTRIBUTES_ALLOW_MODE_READ = 0; + static final int TEXTURE_ATTRIBUTES_ALLOW_MODE_WRITE = 1; + static final int TEXTURE_ATTRIBUTES_ALLOW_BLEND_COLOR_READ = 2; + static final int TEXTURE_ATTRIBUTES_ALLOW_BLEND_COLOR_WRITE = 3; + static final int TEXTURE_ATTRIBUTES_ALLOW_TRANSFORM_READ = 4; + static final int TEXTURE_ATTRIBUTES_ALLOW_TRANSFORM_WRITE = 5; + static final int TEXTURE_ATTRIBUTES_ALLOW_COLOR_TABLE_READ = 6; + static final int TEXTURE_ATTRIBUTES_ALLOW_COLOR_TABLE_WRITE = 7; + static final int TEXTURE_ATTRIBUTES_ALLOW_COMBINE_READ = 8; + static final int TEXTURE_ATTRIBUTES_ALLOW_COMBINE_WRITE = 9; + + // TransparencyAttributes extends NodeComponent + static final int TRANSPARENCY_ATTRIBUTES_ALLOW_MODE_READ = 0; + static final int TRANSPARENCY_ATTRIBUTES_ALLOW_MODE_WRITE = 1; + static final int TRANSPARENCY_ATTRIBUTES_ALLOW_VALUE_READ = 2; + static final int TRANSPARENCY_ATTRIBUTES_ALLOW_VALUE_WRITE = 3; + static final int TRANSPARENCY_ATTRIBUTES_ALLOW_BLEND_FUNCTION_READ = 4; + static final int TRANSPARENCY_ATTRIBUTES_ALLOW_BLEND_FUNCTION_WRITE = 5; + + // TextureUnitState extends NodeComponent + static final int TEXTURE_UNIT_STATE_ALLOW_STATE_READ = 0; + static final int TEXTURE_UNIT_STATE_ALLOW_STATE_WRITE = 1; + + // Geometry extends NodeComponent + // NOTE: additional bits are below the subclasses + + // GeometryArray extends Geometry + static final int GEOMETRY_ARRAY_ALLOW_COORDINATE_READ = 0; + static final int GEOMETRY_ARRAY_ALLOW_COORDINATE_WRITE = 1; + static final int GEOMETRY_ARRAY_ALLOW_COLOR_READ = 2; + static final int GEOMETRY_ARRAY_ALLOW_COLOR_WRITE = 3; + static final int GEOMETRY_ARRAY_ALLOW_NORMAL_READ = 4; + static final int GEOMETRY_ARRAY_ALLOW_NORMAL_WRITE = 5; + static final int GEOMETRY_ARRAY_ALLOW_TEXCOORD_READ = 6; + static final int GEOMETRY_ARRAY_ALLOW_TEXCOORD_WRITE = 7; + static final int GEOMETRY_ARRAY_ALLOW_COUNT_READ = 8; + + // IndexedGeometryArray extends GeometryArray + static final int INDEXED_GEOMETRY_ARRAY_ALLOW_COORDINATE_INDEX_READ = 9; + static final int INDEXED_GEOMETRY_ARRAY_ALLOW_COORDINATE_INDEX_WRITE= 10; + static final int INDEXED_GEOMETRY_ARRAY_ALLOW_COLOR_INDEX_READ = 11; + static final int INDEXED_GEOMETRY_ARRAY_ALLOW_COLOR_INDEX_WRITE = 12; + static final int INDEXED_GEOMETRY_ARRAY_ALLOW_NORMAL_INDEX_READ = 13; + static final int INDEXED_GEOMETRY_ARRAY_ALLOW_NORMAL_INDEX_WRITE = 14; + static final int INDEXED_GEOMETRY_ARRAY_ALLOW_TEXCOORD_INDEX_READ = 15; + static final int INDEXED_GEOMETRY_ARRAY_ALLOW_TEXCOORD_INDEX_WRITE = 16; + + // Additional GeometryArray bits (must go after IndexedGeometryArray bits) + static final int GEOMETRY_ARRAY_ALLOW_FORMAT_READ = 17; + static final int J3D_1_2_GEOMETRY_ARRAY_ALLOW_REF_DATA_READ = 18; + static final int GEOMETRY_ARRAY_ALLOW_REF_DATA_WRITE = 19; + static final int GEOMETRY_ARRAY_ALLOW_COUNT_WRITE = 20; + static final int GEOMETRY_ARRAY_ALLOW_REF_DATA_READ = 21; + + // CompressedGeometry extends Geometry + static final int COMPRESSED_GEOMETRY_ALLOW_COUNT_READ = 0; + static final int COMPRESSED_GEOMETRY_ALLOW_HEADER_READ = 1; + static final int COMPRESSED_GEOMETRY_ALLOW_GEOMETRY_READ = 2; + static final int COMPRESSED_GEOMETRY_ALLOW_REF_DATA_READ = 3; + + // Raster extends Geometry + static final int RASTER_ALLOW_POSITION_READ = 0; + static final int RASTER_ALLOW_POSITION_WRITE = 1; + static final int RASTER_ALLOW_OFFSET_READ = 2; + static final int RASTER_ALLOW_OFFSET_WRITE = 3; + static final int RASTER_ALLOW_IMAGE_READ = 4; + static final int RASTER_ALLOW_IMAGE_WRITE = 5; + static final int RASTER_ALLOW_DEPTH_COMPONENT_READ = 6; + static final int RASTER_ALLOW_DEPTH_COMPONENT_WRITE = 7; + static final int RASTER_ALLOW_SIZE_READ = 8; + static final int RASTER_ALLOW_SIZE_WRITE = 9; + static final int RASTER_ALLOW_TYPE_READ = 10; + static final int RASTER_ALLOW_CLIP_MODE_READ = 11; + static final int RASTER_ALLOW_CLIP_MODE_WRITE = 12; + + // Text3D extends Geometry + static final int TEXT3D_ALLOW_FONT3D_READ = 0; + static final int TEXT3D_ALLOW_FONT3D_WRITE = 1; + static final int TEXT3D_ALLOW_STRING_READ = 2; + static final int TEXT3D_ALLOW_STRING_WRITE = 3; + static final int TEXT3D_ALLOW_POSITION_READ = 4; + static final int TEXT3D_ALLOW_POSITION_WRITE = 5; + static final int TEXT3D_ALLOW_ALIGNMENT_READ = 6; + static final int TEXT3D_ALLOW_ALIGNMENT_WRITE = 7; + static final int TEXT3D_ALLOW_PATH_READ = 8; + static final int TEXT3D_ALLOW_PATH_WRITE = 9; + static final int TEXT3D_ALLOW_CHARACTER_SPACING_READ = 10; + static final int TEXT3D_ALLOW_CHARACTER_SPACING_WRITE = 11; + static final int TEXT3D_ALLOW_BOUNDING_BOX_READ = 12; + + // Additional geometry bits (must go after GeometryArray bits) + // NOTE: ALLOW_INTERSECT was duplicated by the old value of + // ALLOW_REF_DATA_READ in Java 3D 1.2. + static final int GEOMETRY_ALLOW_INTERSECT = 18; + + // NOTE: any further additional Geometry bits must come after the + // last GeometryArray bit +} diff --git a/src/classes/share/javax/media/j3d/CapabilityNotSetException.java b/src/classes/share/javax/media/j3d/CapabilityNotSetException.java new file mode 100644 index 0000000..6872657 --- /dev/null +++ b/src/classes/share/javax/media/j3d/CapabilityNotSetException.java @@ -0,0 +1,37 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates an access to a live or + * compiled Scene Graph object without the required capability + * set. + */ +public class CapabilityNotSetException extends RestrictedAccessException { + +/** + * Create the exception object with default values. + */ + public CapabilityNotSetException(){ + } + +/** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public CapabilityNotSetException(String str){ + + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/Clip.java b/src/classes/share/javax/media/j3d/Clip.java new file mode 100644 index 0000000..143a200 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Clip.java @@ -0,0 +1,284 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The Clip leaf node defines the back, or far, clip distance in + * the virtual universe. + * The distance is specified in the local coordinate system of this node. + * This node also specifies an application + * region in which this clip node is active. + * A Clip node is active when its application region intersects + * the ViewPlatform's activation volume. If multiple Clip nodes + * are active, the Clip node that is "closest" to the eye will be + * used. + * If no clip node is in scope of the view platform + * associated with the current view, then the back clip distance is + * defined by the View object. + * The front clip distance is always defined by the + * View object. + * + * @see View + */ +public class Clip extends Leaf { + + /** + * Specifies that the Clip allows read access to its application + * bounds and bounding leaf at runtime. + */ + public static final int + ALLOW_APPLICATION_BOUNDS_READ = CapabilityBits.CLIP_ALLOW_APPLICATION_BOUNDS_READ; + + /** + * Specifies that the Clip allows write access to its application + * bounds and bounding leaf at runtime. + */ + public static final int + ALLOW_APPLICATION_BOUNDS_WRITE = CapabilityBits.CLIP_ALLOW_APPLICATION_BOUNDS_WRITE; + + /** + * Specifies that the Clip allows read access to its back distance + * at runtime. + */ + public static final int + ALLOW_BACK_DISTANCE_READ = CapabilityBits.CLIP_ALLOW_BACK_DISTANCE_READ; + + /** + * Specifies that the Clip allows write access to its back distance + * at runtime. + */ + public static final int + ALLOW_BACK_DISTANCE_WRITE = CapabilityBits.CLIP_ALLOW_BACK_DISTANCE_WRITE; + + /** + * Constructs a Clip node with default parameters. The default + * values are as follows: + * <ul> + * back clip distance : 100 meters<sr> + * application bounds : null<br> + * application bounding leaf : null<br> + * </ul> + */ + public Clip () { + // Just use the defaults + } + + /** + * Constructs a Clip node with the specified back clip distance. + */ + public Clip(double backDistance) { + ((ClipRetained)this.retained).initBackDistance(backDistance); + } + + /** + * Sets the back clip distance to the specified value. + * There are several considerations that need to be taken into + * account when choosing values for the front and back clip + * distances. These are enumerated in the description of + * <a href=View.html#setFrontClipDistance(double)> + * View.setFrontClipDistance</a>. + * @param backDistance the new back clip distance in meters + * @see View#setFrontClipDistance + * @see View#setBackClipDistance + */ + public void setBackDistance(double backDistance) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_BACK_DISTANCE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Clip0")); + + if (isLive()) + ((ClipRetained)this.retained).setBackDistance(backDistance); + else + ((ClipRetained)this.retained).initBackDistance(backDistance); + } + + /** + * Retrieves the back clip distance. + * @return the current back clip distance, in meters + */ + public double getBackDistance() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_BACK_DISTANCE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Clip1")); + return ((ClipRetained)this.retained).getBackDistance(); + } + + /** + * Set the Clip's application region to the specified bounds. + * This is used when the application bounding leaf is set to null. + * @param region the bounds that contains the Clip's new application + * region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setApplicationBounds(Bounds region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Clip2")); + + if (isLive()) + ((ClipRetained)this.retained).setApplicationBounds(region); + else + ((ClipRetained)this.retained).initApplicationBounds(region); + } + + /** + * Retrieves the Clip node's application bounds. + * @return this Clip's application bounds information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getApplicationBounds() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Clip3")); + + return ((ClipRetained)this.retained).getApplicationBounds(); + } + + /** + * Set the Clip's application region to the specified bounding leaf. + * When set to a value other than null, this overrides the application + * bounds object. + * @param region the bounding leaf node used to specify the Clip + * node's new application region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setApplicationBoundingLeaf(BoundingLeaf region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Clip2")); + + if (isLive()) + ((ClipRetained)this.retained).setApplicationBoundingLeaf(region); + else + ((ClipRetained)this.retained).initApplicationBoundingLeaf(region); + } + + /** + * Retrieves the Clip node's application bounding leaf. + * @return this Clip's application bounding leaf information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BoundingLeaf getApplicationBoundingLeaf() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Clip3")); + + return ((ClipRetained)this.retained).getApplicationBoundingLeaf(); + } + + /** + * Creates the retained mode ClipRetained object that this + * Clip component object will point to. + */ + void createRetained() { + this.retained = new ClipRetained(); + this.retained.setSource(this); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Clip c = new Clip(); + c.duplicateNode(this, forceDuplicate); + return c; + } + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + ClipRetained rt = (ClipRetained) retained; + BoundingLeaf bl = rt.getApplicationBoundingLeaf(); + + // check for applicationBoundingLeaf + if (bl != null) { + Object o = referenceTable.getNewObjectReference(bl); + rt.initApplicationBoundingLeaf((BoundingLeaf) o); + } + } + + + /** + * Copies all Clip information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + ClipRetained attr = (ClipRetained) originalNode.retained; + ClipRetained rt = (ClipRetained) retained; + + rt.initBackDistance(attr.getBackDistance()); + rt.initApplicationBounds(attr.getApplicationBounds()); + + // correct value will set in updateNodeReferences + rt.initApplicationBoundingLeaf(attr.getApplicationBoundingLeaf()); + } +} diff --git a/src/classes/share/javax/media/j3d/ClipRetained.java b/src/classes/share/javax/media/j3d/ClipRetained.java new file mode 100644 index 0000000..b4e7fcc --- /dev/null +++ b/src/classes/share/javax/media/j3d/ClipRetained.java @@ -0,0 +1,384 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; + +/** + * The Clip leaf node defines the back, or far, clipping distance in + * the virtual universe. The front clipping plane is defined in the + * View object. If no clip node is in scope of the view platform + * associated with the current view, then the back clipping plane is + * also defined by the View. + * @see View + */ +class ClipRetained extends LeafRetained { + + static final int BOUNDS_CHANGED = 0x00001; + static final int BOUNDINGLEAF_CHANGED = 0x00002; + static final int BACKDISTANCE_CHANGED = 0x00004; + + /** + * Clip's back distance + */ + double backDistance = 100.0; + + /** + * back distance scaled to vworld + */ + double backDistanceInVworld; + + /** + * The Boundary object defining the application region. + */ + Bounds applicationRegion = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * The transformed value of the applicationRegion. + */ + Bounds transformedRegion = null; + + // This is true when this object is referenced in an immediate mode context + boolean inImmCtx = false; + + + // Target threads to be notified when light changes + // Note, the rendering env structure only get notified + // when there is a bounds related change + static final int targetThreads = J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_RENDERING_ENVIRONMENT; + + + // Is true, if the clip is viewScoped + boolean isViewScoped = false; + + /** + * Constructs a Clip node with a default color (black). + */ + ClipRetained () { + this.nodeType = NodeRetained.CLIP; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + /** + * initializes the clip's back distance to the specified value. + * @param backDistance the new back clipping distance + */ + final void initBackDistance(double backDistance) { + this.backDistance = backDistance; + } + + + /** + * Sets the clip's back distance to the specified value. + * @param backDistance the new back clipping distance + */ + final void setBackDistance(double backDistance) { + this.backDistance = backDistance; + sendMessage(BACKDISTANCE_CHANGED, new Double(backDistance), null); + } + + /** + * Retrieves the clip's back distance. + * @return the current back clipping distance + */ + final double getBackDistance() { + return backDistance; + } + + + /** + * Initializes the Clip's application region. + * @param region a region that contains the Backgound's new application bounds + */ + final void initApplicationBounds(Bounds region) { + if (region != null) { + applicationRegion = (Bounds) region.clone(); + } else { + applicationRegion = null; + } + } + + /** + * Set the Clip's application region. + * @param region a region that contains the Clip's new application bounds + */ + final void setApplicationBounds(Bounds region) { + initApplicationBounds(region); + // Don't send the message if there is a valid boundingleaf + if (boundingLeaf == null) { + sendMessage(BOUNDS_CHANGED, + (region != null ? region.clone(): null), null); + } + } + + /** + * Get the Backgound's application region. + * @return this Clip's application bounds information + */ + final Bounds getApplicationBounds() { + return (applicationRegion != null ? + (Bounds) applicationRegion.clone() : null); + } + + /** + * Initializes the Clip's application region + * to the specified Leaf node. + */ + void initApplicationBoundingLeaf(BoundingLeaf region) { + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + } else { + boundingLeaf = null; + } + } + + /** + * Set the Clip's application region to the specified Leaf node. + */ + void setApplicationBoundingLeaf(BoundingLeaf region) { + if (boundingLeaf != null) + boundingLeaf.mirrorBoundingLeaf.removeUser(this); + + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + boundingLeaf.mirrorBoundingLeaf.addUser(this); + } else { + boundingLeaf = null; + } + sendMessage(BOUNDINGLEAF_CHANGED, + (boundingLeaf != null ? + boundingLeaf.mirrorBoundingLeaf : null), + (applicationRegion != null ? applicationRegion.clone() : null)); + } + + /** + * Get the Clip's application region + */ + BoundingLeaf getApplicationBoundingLeaf() { + return (boundingLeaf != null ? + (BoundingLeaf)boundingLeaf.source : null); + } + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return inImmCtx; + } + + /** + * This setLive routine first calls the superclass's method, then + * it adds itself to the list of lights + */ + void setLive(SetLiveState s) { + if (inImmCtx) { + throw new IllegalSharingException(J3dI18N.getString("ClipRetained0")); + } + + super.doSetLive(s); + + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("ClipRetained1")); + } + + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("ClipRetained2")); + } + + + initMirrorObject(); + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(this); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(this); + } + // process switch leaf + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.ENV_TARGETS); + } + switchState = (SwitchState)s.switchStates.get(0); + + // add this node to the transform target + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + + super.markAsLive(); + } + + /** + * This clearLive routine first calls the superclass's method, then + * it removes itself to the list of lights + */ + void clearLive(SetLiveState s) { + super.clearLive(s); + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(this); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(this); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.ENV_TARGETS); + } + } + + void initMirrorObject() { + Transform3D lastLocalToVworld = getLastLocalToVworld(); + + if (boundingLeaf != null) { + transformedRegion = (Bounds)boundingLeaf.mirrorBoundingLeaf.transformedRegion; + } + else { // Evaluate applicationRegion if not null + if (applicationRegion != null) { + transformedRegion = (Bounds)applicationRegion.clone(); + transformedRegion.transform(applicationRegion, lastLocalToVworld); + } + else { + transformedRegion = null; + } + + } + backDistanceInVworld = backDistance * + lastLocalToVworld.getDistanceScale(); + } + + + // The update Object function. + void updateImmediateMirrorObject(Object[] objs) { + int component = ((Integer)objs[1]).intValue(); + Transform3D trans; + Transform3D currentLocalToVworld = getCurrentLocalToVworld(); + + // Bounds message only sent when boundingleaf is null + if ((component & BOUNDS_CHANGED) != 0) { + if (objs[2] != null) { + transformedRegion = ((Bounds) objs[2]).copy(transformedRegion); + transformedRegion.transform(transformedRegion, + currentLocalToVworld); + } + else { + transformedRegion = null; + } + } + else if ((component & BOUNDINGLEAF_CHANGED) != 0) { + if (objs[2] != null) { + transformedRegion = ((BoundingLeafRetained)objs[2]).transformedRegion; + } + else { // Evaluate applicationRegion if not null + Bounds appRegion = (Bounds)objs[3]; + if (appRegion != null) { + transformedRegion = ((Bounds)appRegion).copy(transformedRegion); + transformedRegion.transform(appRegion, + currentLocalToVworld); + } + else { + transformedRegion = null; + } + + } + + } + else if ((component & BACKDISTANCE_CHANGED) != 0) { + backDistanceInVworld = ((Double)objs[2]).doubleValue() * + currentLocalToVworld.getDistanceScale(); + } + } + + /** Note: This routine will only be called on + * the mirror object - will update the object's + * cached region and transformed region + */ + + void updateBoundingLeaf() { + if (boundingLeaf != null && + boundingLeaf.mirrorBoundingLeaf.switchState.currentSwitchOn) { + transformedRegion = + boundingLeaf.mirrorBoundingLeaf.transformedRegion; + } else { // Evaluate applicationRegion if not null + if (applicationRegion != null) { + transformedRegion = applicationRegion.copy(transformedRegion); + transformedRegion.transform(applicationRegion, + getCurrentLocalToVworld()); + } else { + transformedRegion = null; + } + } + } + + void updateImmediateTransformChange() { + // If bounding leaf is null, tranform the bounds object + if (boundingLeaf == null) { + if (applicationRegion != null) { + transformedRegion = (Bounds)applicationRegion.clone(); + transformedRegion.transform(applicationRegion, + getCurrentLocalToVworld()); + } + } + } + + final void sendMessage(int attrMask, Object attr, Object attr2) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.CLIP_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = attr2; + VirtualUniverse.mc.processMessage(createMessage); + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + if (applicationRegion != null) { + applicationRegion.transform(xform.transform); + } + } + void getMirrorObjects(ArrayList leafList, HashKey key) { + leafList.add(this); + } +} diff --git a/src/classes/share/javax/media/j3d/ColorInterpolator.java b/src/classes/share/javax/media/j3d/ColorInterpolator.java new file mode 100644 index 0000000..b1b5b1c --- /dev/null +++ b/src/classes/share/javax/media/j3d/ColorInterpolator.java @@ -0,0 +1,296 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; +import java.util.Enumeration; + +/** + * Color interpolation behavior. This class defines a behavior that + * modifies the ambient, emissive, diffuse, or specular color of its + * target material object by linearly interpolating between a pair of + * specified colors, using the value generated by the specified Alpha + * object. + * The behavior modifies the color specified by the + * Material's colorTarget attribute, one of: AMBIENT, EMISSIVE, + * DIFFUSE, SPECULAR, or AMBIENT_AND_DIFFUSE. + * The ALLOW_COMPONENT_READ bit must be set in the Material object in + * order for the Material's colorTarget to be read. + * If the Material object's ALLOW_COMPONENT_READ bit is <i>not</i> set, the + * diffuse component will be modified. + * + * @see Material + */ + +public class ColorInterpolator extends Interpolator { + + Material target; + Color3f startColor = new Color3f(); + Color3f endColor = new Color3f(); + Color3f newColor = new Color3f(); + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private int prevColorTarget = -1; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, no parameter constructor used by cloneNode + ColorInterpolator() { + } + + /** + * Constructs a trivial color interpolator with a specified target, + * a starting color of black, and an ending color of white. + * @param alpha the alpha object for this interpolator + * @param target the material component object whose + * color is affected by this color interpolator + */ + public ColorInterpolator(Alpha alpha, + Material target) { + + super(alpha); + + this.target = target; + this.startColor.set(0.0f, 0.0f, 0.0f); + this.endColor.set(1.0f, 1.0f, 1.0f); + } + + /** + * Constructs a color interpolator with the specified target, + * starting color, and ending color. + * @param alpha the alpha object for this interpolator + * @param target the material component object whose + * color is affected by this color interpolator + * @param startColor the starting color + * @param endColor the ending color + */ + public ColorInterpolator(Alpha alpha, + Material target, + Color3f startColor, + Color3f endColor) { + + super(alpha); + + this.target = target; + this.startColor.set(startColor); + this.endColor.set(endColor); + } + + /** + * This method sets the startColor for this interpolator. + * @param color the new start color + */ + public void setStartColor(Color3f color) { + startColor.set(color); + prevAlphaValue = Float.NaN; + } + + /** + * This method retrieves this interpolator's startColor. + * @param color the vector that will receive the interpolator's start color + */ + public void getStartColor(Color3f color) { + color.set(startColor); + } + + /** + * This method sets the endColor for this interpolator. + * @param color the new end color + */ + public void setEndColor(Color3f color) { + endColor.set(color); + prevAlphaValue = Float.NaN; + } + + /** + * This method retrieves this interpolator's endColor. + * @param color the vector that will receive the interpolator's end color + */ + public void getEndColor(Color3f color) { + color.set(endColor); + } + + /** + * This method sets the target material component object for + * this interpolator. + * @param target the material component object whose + * color is affected by this color interpolator + */ + public void setTarget(Material target) { + this.target = target; + prevAlphaValue = Float.NaN; + } + + /** + * This method retrieves this interpolator's target material + * component object. + * @return the interpolator's target material component object + */ + public Material getTarget() { + return target; + } + + // The ColorInterpolator's initialize routine uses the default + // initialization routine. + + /** + * This method is invoked by the behavior scheduler every frame. + * It maps the alpha value that corresponds to the current time + * into a color value and updates the ambient, emissive, diffuse, + * or specular color (or both the ambient and diffuse color) of + * the specified target Material object with this new color value. + * + * @param criteria an enumeration of the criteria that caused the + * stimulus + */ + public void processStimulus(Enumeration criteria) { + + // Handle stimulus + WakeupCriterion criterion = passiveWakeupCriterion; + + if (alpha != null) { + float value = alpha.value(); + + int colorTarget = Material.DIFFUSE; + if (target.getCapability(Material.ALLOW_COMPONENT_READ)) + colorTarget = target.getColorTarget(); + + if (value != prevAlphaValue || colorTarget != prevColorTarget) { + newColor.x = (1.0f-value)*startColor.x + value*endColor.x; + newColor.y = (1.0f-value)*startColor.y + value*endColor.y; + newColor.z = (1.0f-value)*startColor.z + value*endColor.z; + + switch (colorTarget) { + case Material.AMBIENT: + target.setAmbientColor(newColor); + break; + case Material.AMBIENT_AND_DIFFUSE: + target.setAmbientColor(newColor); + // fall through + case Material.DIFFUSE: + target.setDiffuseColor(newColor); + break; + case Material.EMISSIVE: + target.setEmissiveColor(newColor); + break; + case Material.SPECULAR: + target.setSpecularColor(newColor); + break; + } + + prevAlphaValue = value; + prevColorTarget = colorTarget; + } + + if (!alpha.finished() && !alpha.isPaused()) { + criterion = defaultWakeupCriterion; + } + } + wakeupOn(criterion); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + ColorInterpolator ci = new ColorInterpolator(); + ci.duplicateNode(this, forceDuplicate); + return ci; + } + + + /** + * Copies all ColorInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + ColorInterpolator ci = (ColorInterpolator) originalNode; + + ci.getStartColor(startColor); + ci.getEndColor(endColor); + + // this reference will be updated in updateNodeReferences() + setTarget(ci.getTarget()); + } + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + super.updateNodeReferences(referenceTable); + + // check Material + NodeComponent nc = getTarget(); + + if (nc != null) { + setTarget((Material) referenceTable.getNewObjectReference(nc)); + } + } +} diff --git a/src/classes/share/javax/media/j3d/ColoringAttributes.java b/src/classes/share/javax/media/j3d/ColoringAttributes.java new file mode 100644 index 0000000..5a25fd1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ColoringAttributes.java @@ -0,0 +1,337 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; + +/** + * The ColoringAttributes object defines attributes used in + * color selection and shading model. + * + * <p> + * <b>Color</b> + * <p> + * The <code>setColor</code> methods set the current intrinsic red, green, and + * blue color values of this ColoringAttributes component object. + * This color is only used for unlit geometry. If lighting is enabled, + * the material colors are used in the lighting equation to produce + * the final color. When vertex colors are present in unlit + * geometry, those vertex colors are used in place of this + * ColoringAttributes color, unless the vertex colors are ignored. + * <p> + * There are two variations on the <code>setColor</code> methods, one + * that takes a Color3f and one that takes three floats. No alpha + * value is allowed (it's automatically set to 1.0). The float values + * range between 0.0 and 1.0, with 1.0 being full intensity of the + * color. A color value of (1.0, 1.0, 1.0) is white. + * <p> + * <b>Shading Model</b> + * <p> + * The <code>setShadeModel</code> method sets the shade model for this + * ColoringAttributes component object. The shade model may be one of + * the following:<p> + * <ul> + * <li>FASTEST - use the fastest available method for shading. This + * shading mode maps to whatever shading model the Java 3D implementor + * defines as the "fastest," which may be hardware-dependent.</li> + * <p> + * <li>NICEST - use the nicest (highest quality) available method + * for shading. This shading mode maps to whatever shading model + * the Java 3D implementor defines as the "nicest," shading + * model, which may be hardware-dependent.</li> + * <p> + * <li>SHADE_FLAT - use the flat shading model. This shading model + * does not interpolate color across the primitive. + * The primitive is drawn with a single color + * and the color of one vertex of the primitive is duplicated + * across all the vertices of the primitive.</li> + * <p> + * <li>SHADE_GOURAUD - use the Gouraud (smooth) shading model. + * This shading model smoothly interpolates the color at each vertex + * across the primitive. + * The primitive is drawn with many different colors + * and the color at each vertex is treated individually. For lines, + * the colors along the line segment are interpolated between + * the vertex colors. This is the default shade model if no other + * is specified.</li> + * <p></ul> + * + * @see Appearance + */ +public class ColoringAttributes extends NodeComponent { + /** + * Specifies that this ColoringAttributes object allows + * reading its color component information. + */ + public static final int + ALLOW_COLOR_READ = CapabilityBits.COLORING_ATTRIBUTES_ALLOW_COLOR_READ; + + /** + * Specifies that this ColoringAttributes object allows + * writing its color component information. + */ + public static final int + ALLOW_COLOR_WRITE = CapabilityBits.COLORING_ATTRIBUTES_ALLOW_COLOR_WRITE; + + /** + * Specifies that this ColoringAttributes object allows + * reading its shade model component information. + */ + public static final int + ALLOW_SHADE_MODEL_READ = CapabilityBits.COLORING_ATTRIBUTES_ALLOW_SHADE_MODEL_READ; + + /** + * Specifies that this ColoringAttributes object allows + * writing its shade model component information. + */ + public static final int + ALLOW_SHADE_MODEL_WRITE = CapabilityBits.COLORING_ATTRIBUTES_ALLOW_SHADE_MODEL_WRITE; + + /** + * Use the fastest available method for shading. + */ + public static final int FASTEST = 0; + /** + * Use the nicest available method for shading. + */ + public static final int NICEST = 1; + + /** + * Do not interpolate color across the primitive. + */ + public static final int SHADE_FLAT = 2; + /** + * Smoothly interpolate the color at each vertex across the primitive. + */ + public static final int SHADE_GOURAUD = 3; + + /** + * Constructs a ColoringAttributes node with default parameters. + * The default values are as follows: + * <ul> + * color = white (1,1,1)<br> + * shade model = SHADE_GOURAUD<br> + * </ul> + */ + public ColoringAttributes() { + // Just use default attributes + } + + /** + * Construct ColoringAttributes object with specified values. + * @param color the intrisic color + * @param shadeModel the shade model used; one of FASTEST, NICEST, + * SHADE_FLAT, or SHADE_GOURAUD + */ + public ColoringAttributes(Color3f color, int shadeModel) { + ((ColoringAttributesRetained)this.retained).initColor(color); + ((ColoringAttributesRetained)this.retained).initShadeModel(shadeModel); + + } + + /** + * Construct ColoringAttributes object with specified values. + * @param red red component of the intrisic color + * @param green green component of the intrisic color + * @param blue blue component of the intrisic color + * @param shadeModel the shade model used; one of FASTEST, NICEST, + * SHADE_FLAT, or SHADE_GOURAUD + */ + public ColoringAttributes(float red, float green, float blue, + int shadeModel) { + ((ColoringAttributesRetained)this.retained).initColor(red, green,blue); + ((ColoringAttributesRetained)this.retained).initShadeModel(shadeModel); + } + + /** + * Sets the intrinsic color of this ColoringAttributes + * component object. This color is only used for unlit geometry; + * if lighting is enabled, then the material colors are used in the + * lighting equation to produce the final color. + * When vertex colors are present in unlit geometry, those + * vertex colors are used in place of this ColoringAttributes color + * unless the vertex colors are ignored. + * @param color the color that is used when lighting is disabled + * or when material is null + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @see Material + * @see RenderingAttributes#setIgnoreVertexColors + */ + public void setColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ColoringAttributes0")); + + if (isLive()) + ((ColoringAttributesRetained)this.retained).setColor(color); + else + ((ColoringAttributesRetained)this.retained).initColor(color); + + } + + /** + * Sets the intrinsic color of this ColoringAttributes + * component object. This color is only used for unlit geometry; + * if lighting is enabled, then the material colors are used in the + * lighting equation to produce the final color. + * When vertex colors are present in unlit geometry, those + * vertex colors are used in place of this ColoringAttributes color + * unless the vertex colors are ignored. + * @param r the red component of the color + * @param g the green component of the color + * @param b the blue component of the color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @see Material + * @see RenderingAttributes#setIgnoreVertexColors + */ + public void setColor(float r, float g, float b) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ColoringAttributes0")); + + if (isLive()) + ((ColoringAttributesRetained)this.retained).setColor(r, g, b); + else + ((ColoringAttributesRetained)this.retained).initColor(r, g, b); + } + + /** + * Gets the intrinsic color of this ColoringAttributes + * component object. + * @param color the vector that will receive color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ColoringAttributes2")); + + ((ColoringAttributesRetained)this.retained).getColor(color); + } + + /** + * Sets the shade mode for this ColoringAttributes component object. + * @param shadeModel the shade mode to be used; one of FASTEST, + * NICEST, SHADE_FLAT, or SHADE_GOURAUD + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setShadeModel(int shadeModel) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SHADE_MODEL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ColoringAttributes3")); + + if (isLive()) + ((ColoringAttributesRetained)this.retained).setShadeModel(shadeModel); + else + ((ColoringAttributesRetained)this.retained).initShadeModel(shadeModel); + } + + /** + * Gets the shade mode for this ColoringAttributes component object. + * @return shadeModel the shade mode + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getShadeModel() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SHADE_MODEL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ColoringAttributes4")); + + return ((ColoringAttributesRetained)this.retained).getShadeModel(); + } + + /** + * Creates a retained mode ColoringAttributesRetained object that this + * ColoringAttributes component object will point to. + */ + void createRetained() { + this.retained = new ColoringAttributesRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + + public NodeComponent cloneNodeComponent() { + ColoringAttributes ca = new ColoringAttributes(); + ca.duplicateNodeComponent(this); + return ca; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + + super.duplicateAttributes(originalNodeComponent, + forceDuplicate); + + ColoringAttributesRetained attr = + (ColoringAttributesRetained) originalNodeComponent.retained; + + ColoringAttributesRetained rt = (ColoringAttributesRetained) retained; + Color3f c = new Color3f(); + attr.getColor(c); + + rt.initColor(c); + rt.initShadeModel(attr.getShadeModel()); + } + + /** + * Returns a String representation of this ColoringAttributes object. + * If the scene graph is live only those values with their + * Capability read bit set will be displayed. + */ + public String toString() { + StringBuffer str=new StringBuffer("ColoringAttributes:"); + String shadingModes[] = { "FASTEST", "NICEST", "SHADE_FLAT", + "SHADE_GOURAUD" }; + + try { + Color3f color=new Color3f(); + getColor( color ); + str.append( "Color="+color ); + } + catch (CapabilityNotSetException e) {str.append("Color=N/A");} + + try { + str.append( " ShadeModel="+shadingModes[getShadeModel()] ); + } + catch (CapabilityNotSetException ex) {str.append("ShadeModel=N/A");} + + return new String(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/ColoringAttributesRetained.java b/src/classes/share/javax/media/j3d/ColoringAttributesRetained.java new file mode 100644 index 0000000..1f0ae14 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ColoringAttributesRetained.java @@ -0,0 +1,253 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; +import java.util.ArrayList; + +/** + * The ColoringAttributesRetained object defines attributes that apply to + * to coloring mapping. + */ +class ColoringAttributesRetained extends NodeComponentRetained { + // A list of pre-defined bits to indicate which component + // in this ColoringAttributes object changed. + static final int COLOR_CHANGED = 0x01; + static final int SHADE_MODEL_CHANGED = 0x02; + + // Intrinsic color used when lighting is disabled or when + // material is null + Color3f color = new Color3f(1.0f, 1.0f, 1.0f); + + // Shade model (flat, smooth) + int shadeModel = ColoringAttributes.SHADE_GOURAUD; + + /** + * Sets the intrinsic color of this ColoringAttributes + * component object. + * @param color the color that is used when lighting is disabled + * or when material is null + */ + final void initColor(Color3f color) { + this.color.set(color); + } + + /** + * Sets the intrinsic color of this ColoringAttributes + * component object and sends a message notifying + * the interested structures of the change. + * @param color the color that is used when lighting is disabled + * or when material is null + */ + final void setColor(Color3f color) { + initColor(color); + sendMessage(COLOR_CHANGED, new Color3f(color)); + } + + /** + * Sets the intrinsic color of this ColoringAttributes + * component object. This color is used when lighting is disabled + * or when material is null. + * @param r the red component of the color + * @param g the green component of the color + * @param b the blue component of the color + */ + final void initColor(float r, float g, float b) { + this.color.set(r, g, b); + } + + /** + * Sets the intrinsic color of this ColoringAttributes + * component object and sends a message notifying + * the interested structures of the change. + * This color is used when lighting is disabled + * or when material is null. + * @param r the red component of the color + * @param g the green component of the color + * @param b the blue component of the color + */ + final void setColor(float r, float g, float b) { + initColor(r, g, b); + sendMessage(COLOR_CHANGED, new Color3f(r, g, b)); + } + + /** + * Gets the intrinsic color of this ColoringAttributes + * component object. + * @param color the vector that will receive color + */ + final void getColor(Color3f color) { + color.set(this.color); + } + + /** + * Sets the shade mode for this ColoringAttributes component object. + * @param shadeModel the shade mode to be used; one of FASTEST, + * NICEST, SHADE_FLAT, or SHADE_GOURAUD + */ + final void initShadeModel(int shadeModel) { + this.shadeModel = shadeModel; + } + + /** + * Sets the shade mode for this ColoringAttributes component object + * and sends a message notifying + * the interested structures of the change. + * @param shadeModel the shade mode to be used; one of FASTEST, + * NICEST, SHADE_FLAT, or SHADE_GOURAUD + */ + final void setShadeModel(int shadeModel) { + initShadeModel(shadeModel); + sendMessage(SHADE_MODEL_CHANGED, new Integer(shadeModel)); + } + + /** + * Gets the shade mode for this ColoringAttributes component object. + * @return shadeModel the shade mode + */ + final int getShadeModel() { + return shadeModel; + } + + + /** + * Creates and initializes a mirror object, point the mirror object + * to the retained object if the object is not editable + */ + synchronized void createMirrorObject() { + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror = this; + } else { + ColoringAttributesRetained mirrorCa + = new ColoringAttributesRetained(); + mirrorCa.source = source; + mirrorCa.set(this); + mirror = mirrorCa; + } + } else { + ((ColoringAttributesRetained) mirror).set(this); + } + } + /** + * These two native methods update the native context + */ + native void updateNative(long ctx, + float dRed, float dGreen, float dBlue, + float red, float green, float blue, + float alpha, + boolean lEnable, + int shadeModel); + + void updateNative(long ctx, + float dRed, float dGreen, float dBlue, + float alpha, boolean lEnable) { + updateNative(ctx, dRed, dBlue, dGreen, color.x, color.y, + color.z, alpha, + lEnable, shadeModel); + } + + /** + * Creates a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + ((ColoringAttributesRetained)mirror).set(this); + } + + /** Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + + ColoringAttributesRetained mirrorCa = + (ColoringAttributesRetained) mirror; + + if ((component & COLOR_CHANGED) != 0) { + mirrorCa.color.set(((Color3f)value)); + } + else if ((component & SHADE_MODEL_CHANGED) != 0) { + mirrorCa.shadeModel = ((Integer)value).intValue(); + } + } + + boolean equivalent(ColoringAttributesRetained cr) { + return ((cr != null) && + color.equals(cr.color) && + (shadeModel == cr.shadeModel)); + } + + + // This functions clones the retained side only and is used + // internally + protected Object clone() { + ColoringAttributesRetained cr = + (ColoringAttributesRetained)super.clone(); + cr.color = new Color3f(color); + // shadeModel is copied in super.clone() + return cr; + } + + // This functions clones the retained side only and is used + // internally + protected void set(ColoringAttributesRetained cr) { + super.set(cr); + color.set(cr.color); + shadeModel = cr.shadeModel; + } + + final void sendMessage(int attrMask, Object attr) { + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.COLORINGATTRIBUTES_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.COLORINGATTRIBUTES_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + } + void handleFrequencyChange(int bit) { + if (bit == ColoringAttributes.ALLOW_COLOR_WRITE || + bit == ColoringAttributes.ALLOW_SHADE_MODEL_WRITE) { + setFrequencyChangeMask(bit, 0x1); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/CompileState.java b/src/classes/share/javax/media/j3d/CompileState.java new file mode 100644 index 0000000..997b224 --- /dev/null +++ b/src/classes/share/javax/media/j3d/CompileState.java @@ -0,0 +1,325 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; +import java.security.*; + +/** + * The CompileState holds information used during a compile. It is + * passed to each SceneGraphObject (SGO) during the compile. Each SGO + * modifies the CompileState as necessary and passes the CompileState + * to its children (if any). + * + * The CompileState currently has two functions: appearance mapping + * and shape merging. + * + * Appearance mapping maintains a list of the unique appearances seen + * during the compile. getAppearance() is used to turn multiple, + * equivalent and static appearances into a single shared appearance. + * + * The shape mergings collects shapes that are potentially mergable + * during a compile. The shapes are sorted into a Map of Lists of + * shapes, using the shape's appearance as the key. After a subtree + * is traversed, the shapes are merged and added to the Group. + */ + +class CompileState { + // Appearance mapping stuff: + HashMap knownAppearances = new HashMap(); + int numAppearances = 0; + int numShared = 0; + int numShapes = 0; + + // Shape merging stuff: + HashMap shapeLists = null; // top entry in shapeListStack + int numMergeSets = 0; + int numMergeShapes = 0; + boolean compileVerbose = false; + + + static final int BOUNDS_READ = 0x00001; + static final int GEOMETRY_READ = 0x00002; + + + // scene graph flattening + + boolean keepTG = false; // used to force the immediate transform + // group to stay around + + boolean needNormalsTransform = false; // true if the current transform + // needs to push down normals + // transform to geometries + + // the current static transform group + TransformGroupRetained staticTransform = null; + + // parent group + GroupRetained parentGroup = null; + + // list of transform group + // for the current transform group + ArrayList transformGroupChildrenList = null; + + // list of objects that have a static + // transform that can be deferenced + // after compile + ArrayList staticTransformObjects = new ArrayList(1); + + int numTransformGroups = 0; + int numStaticTransformGroups = 0; + int numMergedTransformGroups = 0; + int numGroups = 0; + int numMergedGroups = 0; + int numShapesWSharedGeom = 0; + int numShapesWStaticTG = 0; + int numLinks = 0; + int numSwitches = 0; + int numOrderedGroups = 0; + int numMorphs = 0; + + CompileState() { + try { + compileVerbose = Boolean.getBoolean("javax.media.j3d.compileVerbose"); + } catch (AccessControlException e) { + compileVerbose = false; + } + initShapeMerge(); + } + + // Appearance mapping: + /** + * Returns an unique appearance which equals app. If appearance does not + * equal any previously found, the appearance will be added to the known + * appearances and be returned. If the apperance equals a previously known + * appearance, then the prevously known apperance will be returned + */ + AppearanceRetained getAppearance(AppearanceRetained app) { + AppearanceRetained retval; + + // see if the appearance has allready been classified + if (app.map == this) { + if (app.mapAppearance != null) { + numShared++; + return app.mapAppearance; + } + } + + // check if this appearance equals one one in the Map + if ((retval = (AppearanceRetained)knownAppearances.get(app)) != null) { + numShared++; + } else { + // not found, put this appearance in the map + knownAppearances.put(app, app); + numAppearances++; + numShared++; // sharing with self... + retval = app; + } + + // cache this result on the appearance in case it appears again + app.map = this; + app.mapAppearance = retval; + + return retval; + } + + // Shape Merging: + private void initShapeMerge() { + shapeLists = new HashMap(); + + } + + void addShape(Shape3DRetained shape) { + if (parentGroup != null) { + // sort the shapes into lists with equivalent appearances + Vector list; + if ((list = (Vector)shapeLists.get(shape.appearance)) == null) { + list = new Vector(); + shapeLists.put(shape.appearance, list); + } + // Add the shape to the list only if at its parent level + // no children can be added or removed .. + // Look for the first non-null geometry, there should be atleast + // one otherwise it wouldn't come here + GeometryRetained geometry = null; + int i = 0; + while (geometry == null && i < shape.geometryList.size()) { + geometry = (GeometryRetained) shape.geometryList.get(i); + i++; + } + if (shape.parent instanceof GroupRetained && ((GroupRetained)shape.parent).isStaticChildren() && geometry.geoType < GeometryArrayRetained.GEO_TYPE_RASTER) { + list.add(shape); + } + + } + } + + + void printStats() { + System.out.println("numTransformGroups= " + numTransformGroups); + System.out.println("numStaticTransformGroups= " + numStaticTransformGroups); + System.out.println("numMergedTransformGroups= " + numMergedTransformGroups); + System.out.println("numGroups= " + numGroups); + System.out.println("numMergedGroups= " + numMergedGroups); + System.out.println("numShapes= " + numShapes); + System.out.println("numShapesWStaticTG= " + numShapesWStaticTG); + System.out.println("numMergeShapes= " + numMergeShapes); + System.out.println("numMergeSets= " + numMergeSets); + System.out.println("numLinks= " + numLinks); + System.out.println("numSwitches= " + numSwitches); + System.out.println("numOrderedGroups= " + numOrderedGroups); + System.out.println("numMorphs= " + numMorphs); + } + + void doShapeMerge() { + + // System.out.println("doShapeMerge, shapeList = "+shapeLists); + if (shapeLists != null) { + // loop over the shapes in each list, creating a single shape + // for each. Add the shape to the group + Collection lists = shapeLists.values(); + Iterator listIterator = lists.iterator(); + Shape3DRetained mergeShape; + GeometryRetained firstGeo; + int num = 0; + int compileFlags = 0; + + while (listIterator.hasNext()) { + Vector curList = (Vector)listIterator.next(); + int numShapes = curList.size(); + Shape3DRetained[] shapes = new Shape3DRetained[numShapes]; + curList.copyInto(shapes); + Shape3DRetained[] toBeMergedShapes = new Shape3DRetained[numShapes]; + for (int i = 0; i < numShapes; i++) { + if (shapes[i] == null) { + continue; + } + firstGeo = null; + num = 0; + // Get the first non-null geometry + while (firstGeo == null && num < shapes[i].geometryList.size()) { + firstGeo = (GeometryRetained) shapes[i].geometryList.get(num); + num++; + } + + if (firstGeo != null && firstGeo instanceof GeometryArrayRetained) { + int numMerge = 0; + mergeShape = shapes[i]; + GeometryArrayRetained mergeGeo = (GeometryArrayRetained)firstGeo; + + toBeMergedShapes[numMerge++] = mergeShape; + // Determine if all mergeable shapes have the same boundsCompute + // and collisionBounds set the same way + compileFlags = getCompileFlags(mergeShape); + for (int j = i+1; j < numShapes; j++) { + if (shapes[j] == null) { + continue; + } + firstGeo = null; + num = 0; + // Get the first non-null geometry + while (firstGeo == null && num < shapes[j].geometryList.size()) { + firstGeo = (GeometryRetained) shapes[j].geometryList.get(num); + num++; + } + + // There is a non-null geometry for this shape .. + if (firstGeo != null && + shapes[j].isEquivalent(mergeShape) && + firstGeo.isEquivalenceClass(mergeGeo) && + ((GeometryArrayRetained)firstGeo).vertexFormat == mergeGeo.vertexFormat) { + // got one to merge, add shapes to merge, + toBeMergedShapes[numMerge++] = shapes[j]; + + compileFlags |= getCompileFlags(shapes[j]); + + // remove from shapes + shapes[j] = null; + } + } + if (numMerge > 1) { + + // remove the shapes from its parent before merge + // They all should + GroupRetained group = (GroupRetained)toBeMergedShapes[0].parent; + Shape3DRetained s; + for (int n = 0; n < numMerge; n++) { + s = toBeMergedShapes[n]; + boolean found = false; + int numChilds = group.numChildren(); + for (int k = 0; (k < numChilds && !found); k++) { + if (group.getChild(k).retained == s) { + found = true; + group.removeChild(k); + } + } + if (!found) { + System.out.println("ShapeSet.add(): Can't remove " + + "shape from parent, can't find shape!"); + } + + } + + mergeShape = new Shape3DCompileRetained(toBeMergedShapes, numMerge, compileFlags); + + if (J3dDebug.devPhase && J3dDebug.debug) { + if (J3dDebug.doDebug(J3dDebug.compileState, J3dDebug.LEVEL_3)) { + System.out.println("Dest is "+ parentGroup); + System.out.println("Compile Shape "+mergeShape); + System.out.println(mergeShape.geometryList.size()+" geoemtryList"); + for (int j = 0; j < mergeShape.geometryList.size(); j++) { + GeometryRetained geo = ((GeometryRetained)mergeShape.geometryList.get(j)); + if (geo != null) + System.out.println("\t Geo_type = "+geo.geoType); + } + + System.out.println(numMerge+" Shapes were merged "); + for (int j = 0; j < numMerge; j++) { + System.out.println("\t" + toBeMergedShapes[j]); + } + } + } + + // Set the source to one of the merged shape's source + mergeShape.setSource(toBeMergedShapes[0].source); + numMergeSets++; + numMergeShapes += numMerge ; + parentGroup.addChild((Node)mergeShape.source); + } + } + // add the shape to the dest + } + } + } + + // Clear the shapelists for the next merge + shapeLists.clear(); + + } + + + int getCompileFlags(Shape3DRetained shape) { + int cflag = 0; + + // If allow intersect is turned on , then geometry is readable + if (shape.allowIntersect() || + shape.source.getCapability(Shape3D.ALLOW_GEOMETRY_READ)|| + (shape.boundsAutoCompute && + shape.source.getCapability(Shape3D.ALLOW_BOUNDS_READ))) + cflag |= GEOMETRY_READ; + + return cflag; + + } + +} diff --git a/src/classes/share/javax/media/j3d/CompressedGeometry.java b/src/classes/share/javax/media/j3d/CompressedGeometry.java new file mode 100644 index 0000000..40e6657 --- /dev/null +++ b/src/classes/share/javax/media/j3d/CompressedGeometry.java @@ -0,0 +1,431 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The compressed geometry object is used to store geometry in a + * compressed format. Using compressed geometry reduces the amount + * of memory needed by a Java 3D application and increases the speed + * objects can be sent over the network. Once geometry decompression + * hardware support becomes available, increased rendering performance + * will also result from the use of compressed geometry. + * <p> + * Compressed geometry may be passed to this CompressedGeometry object + * in one of two ways: by copying the data into this object using the + * existing constructor, or by passing a reference to the data. + * <p> + * <ul> + * <li> + * <b>By Copying:</b> + * The existing CompressedGeometry constructor copies the buffer of + * compressed geometry data into this CompressedGeometry object. This + * is appropriate for many applications, and allows Java 3D to verify + * the data once and then not worry about it again. + * </li> + * <li><b>By Reference:</b> + * A new constructor and set of methods in Java 3D version 1.2 allows + * compressed geometry data to be accessed by reference, directly from + * the user's array. To use this feature, you need to construct a + * CompressedGeometry object with the <code>byReference</code> flag + * set to <code>true</code>. In this mode, a reference to the input + * data is saved, but the data itself is not necessarily copied. Note + * that the compressed geometry header is still copied into this + * compressed geometry object. Data referenced by a + * CompressedGeometry object must not be modified after the + * CompressedGeometry object is constructed. + * Applications + * must exercise care not to violate this rule. If any referenced + * compressed geometry data is modified after construction, + * the results are undefined. + * </li> + * </ul> + */ +public class CompressedGeometry extends Geometry { + + CompressedGeometryHeader cgHeader ; + + /** + * Specifies that this CompressedGeometry object allows reading its + * byte count information. + */ + public static final int + ALLOW_COUNT_READ = CapabilityBits.COMPRESSED_GEOMETRY_ALLOW_COUNT_READ ; + + /** + * Specifies that this CompressedGeometry object allows reading its + * header information. + */ + public static final int + ALLOW_HEADER_READ = CapabilityBits.COMPRESSED_GEOMETRY_ALLOW_HEADER_READ ; + + /** + * Specifies that this CompressedGeometry object allows reading its + * geometry data component information. + */ + public static final int + ALLOW_GEOMETRY_READ = + CapabilityBits.COMPRESSED_GEOMETRY_ALLOW_GEOMETRY_READ ; + + /** + * Specifies that this CompressedGeometry allows reading the geometry + * data reference information for this object. This is only used in + * by-reference geometry mode. + * + * @since Java 3D 1.2 + */ + public static final int + ALLOW_REF_DATA_READ = + CapabilityBits.COMPRESSED_GEOMETRY_ALLOW_REF_DATA_READ; + + /** + * Package scoped default constructor for use by cloneNodeComponent. + */ + CompressedGeometry() { + } + + /** + * Creates a new CompressedGeometry NodeComponent by copying + * the specified compressed geometry data into this object. + * If the version number of compressed geometry, as specified by + * the CompressedGeometryHeader, is incompatible with the + * supported version of compressed geometry in the current version + * of Java 3D, then the compressed geometry object will not be + * rendered. + * + * @param hdr the compressed geometry header. This is copied + * into the CompressedGeometry NodeComponent. + * + * @param compressedGeometry the compressed geometry data. The + * geometry must conform to the format described in Appendix B of + * the <i>Java 3D API Specification</i>. + * + * @exception IllegalArgumentException if a problem is detected with the + * header + * + * @see CompressedGeometryHeader + * @see Canvas3D#queryProperties + */ + public CompressedGeometry(CompressedGeometryHeader hdr, + byte[] compressedGeometry) { + this(hdr, compressedGeometry, false) ; + } + + /** + * Creates a new CompressedGeometry NodeComponent. The + * specified compressed geometry data is either copied into this + * object or is accessed by reference. + * If the version number of compressed geometry, as specified by + * the CompressedGeometryHeader, is incompatible with the + * supported version of compressed geometry in the current version + * of Java 3D, the compressed geometry object will not be + * rendered. + * + * @param hdr the compressed geometry header. This is copied + * into the CompressedGeometry NodeComponent. + * + * @param compressedGeometry the compressed geometry data. The + * geometry must conform to the format described in Appendix B of + * the <i>Java 3D API Specification</i>. + * + * @param byReference a flag that indicates whether the data is copied + * into this compressed geometry object or is accessed by reference. + * + * @exception IllegalArgumentException if a problem is detected with the + * header + * + * @see CompressedGeometryHeader + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.2 + */ + public CompressedGeometry(CompressedGeometryHeader hdr, + byte[] compressedGeometry, + boolean byReference) { + + if ((hdr.size + hdr.start) > compressedGeometry.length) + throw new IllegalArgumentException + (J3dI18N.getString("CompressedGeometry0")) ; + + // Create a separate copy of the given header. + cgHeader = new CompressedGeometryHeader() ; + hdr.copy(cgHeader) ; + + // Create the retained object. + ((CompressedGeometryRetained)this.retained).createCompressedGeometry + (cgHeader, compressedGeometry, byReference) ; + + // This constructor is designed to accept byte arrays that may contain + // possibly many large compressed geometry blocks interspersed with + // non-J3D-specific metadata. Only one of these blocks is used per + // CompressedGeometry object, so set the geometry offset to zero in + // the header if the data itself is copied. + if (!byReference) + cgHeader.start = 0 ; + } + + /** + * Creates a new CompressedGeometry NodeComponent. The + * specified compressed geometry data is accessed by reference + * from the specified buffer. + * If the version number of compressed geometry, as specified by + * the CompressedGeometryHeader, is incompatible with the + * supported version of compressed geometry in the current version + * of Java 3D, the compressed geometry object will not be + * rendered. + * + * @param hdr the compressed geometry header. This is copied + * into the CompressedGeometry NodeComponent. + * + * @param compressedGeometry a buffer containing an NIO byte buffer + * of compressed geometry data. The + * geometry must conform to the format described in Appendix B of + * the <i>Java 3D API Specification</i>. + * + * @exception UnsupportedOperationException this method is not + * yet implemented + * + * @exception IllegalArgumentException if a problem is detected with the + * header, + * or if the java.nio.Buffer contained in the specified J3DBuffer + * is not a java.nio.ByteBuffer object. + * + * @see CompressedGeometryHeader + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public CompressedGeometry(CompressedGeometryHeader hdr, + J3DBuffer compressedGeometry) { + throw new UnsupportedOperationException(J3dI18N.getString("CompressedGeometry9")) ; + } + + + /** + * Returns the size, in bytes, of the compressed geometry buffer. + * The size of the compressed geometry header is not included. + * + * @return the size, in bytes, of the compressed geometry buffer. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getByteCount() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException + (J3dI18N.getString("CompressedGeometry1")) ; + + return cgHeader.size ; + } + + /** + * Copies the compressed geometry header from the CompressedGeometry + * NodeComponent into the passed in parameter. + * + * @param hdr the CompressedGeometryHeader object into which to copy the + * CompressedGeometry NodeComponent's header; the offset field may differ + * from that which was originally specified if a copy of the original + * compressed geometry byte array was created. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see CompressedGeometryHeader + */ + public void getCompressedGeometryHeader(CompressedGeometryHeader hdr) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_HEADER_READ)) + throw new CapabilityNotSetException + (J3dI18N.getString("CompressedGeometry2")) ; + + cgHeader.copy(hdr) ; + } + + /** + * Retrieves the compressed geometry associated with the + * CompressedGeometry NodeComponent object. Copies the compressed + * geometry from the CompressedGeometry node into the given array. + * The array must be large enough to hold all of the bytes. + * The individual array elements must be allocated by the caller. + * + * @param compressedGeometry the array into which to copy the compressed + * geometry. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalStateException if the data access mode for this + * object is by-reference. + * + * @exception ArrayIndexOutOfBoundsException if compressedGeometry byte + * array is not large enough to receive the compressed geometry + */ + public void getCompressedGeometry(byte[] compressedGeometry) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_READ)) + throw new CapabilityNotSetException + (J3dI18N.getString("CompressedGeometry3")) ; + + if (isByReference()) + throw new IllegalStateException + (J3dI18N.getString("CompressedGeometry7")) ; + + if (cgHeader.size > compressedGeometry.length) + throw new ArrayIndexOutOfBoundsException + (J3dI18N.getString("CompressedGeometry4")) ; + + ((CompressedGeometryRetained)this.retained).copy(compressedGeometry) ; + } + + /** + * Decompresses the compressed geometry. Returns an array of Shape nodes + * containing the decompressed geometry objects, or null if the version + * number of the compressed geometry is incompatible with the decompressor + * in the current version of Java 3D. + * + * @return an array of Shape nodes containing the + * geometry decompressed from this CompressedGeometry NodeComponent + * object, or null if its version is incompatible + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Shape3D[] decompress() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_READ)) + throw new CapabilityNotSetException + (J3dI18N.getString("CompressedGeometry5")) ; + + CompressedGeometryRetained cgr = + (CompressedGeometryRetained)this.retained ; + + GeometryDecompressorShape3D decompressor = + new GeometryDecompressorShape3D() ; + + // Decompress the geometry as TriangleStripArrays. A combination of + // TriangleStripArrays and TrianglesFanArrays is more compact but + // requires twice as many Shape3D objects, resulting in slower + // rendering performance. + // + // Using TriangleArray output is currently the fastest, given the + // strip sizes observed from various compressed geometry objects, but + // produces about twice as many vertices. TriangleStripArray produces + // the same number of Shape3D objects as TriangleArray using 1/2 + // to 2/3 of the vertices, with only a marginal performance penalty. + // + // TODO revisit this + return decompressor.toTriangleStripArrays(cgr) ; + } + + + /** + * Retrieves the data access mode for this CompressedGeometry object. + * + * @return <code>true</code> if the data access mode for this + * CompressedGeometry object is by-reference; + * <code>false</code> if the data access mode is by-copying. + * + * @since Java 3D 1.2 + */ + public boolean isByReference() { + return ((CompressedGeometryRetained)this.retained).isByReference() ; + } + + + /** + * Gets the compressed geometry data reference. + * + * @return the current compressed geometry data reference; + * null is returned if this compressed geometry object was created + * with a J3DBuffer reference rather than a byte array. + * + * @exception IllegalStateException if the data access mode for this + * object is not by-reference. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public byte[] getCompressedGeometryRef() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ)) + throw new CapabilityNotSetException + (J3dI18N.getString("CompressedGeometry6")) ; + + if (!isByReference()) + throw new IllegalStateException + (J3dI18N.getString("CompressedGeometry8")) ; + + return ((CompressedGeometryRetained)this.retained).getReference() ; + } + + + /** + * Gets the compressed geometry data buffer reference. + * + * @return the current compressed geometry data buffer reference; + * null is returned if this compressed geometry object was created + * with a byte array reference rather than a J3DBuffer. + * + * @exception IllegalStateException if the data access mode for this + * object is not by-reference. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public J3DBuffer getCompressedGeometryBuffer() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ)) + throw new CapabilityNotSetException + (J3dI18N.getString("CompressedGeometry6")) ; + + if (!isByReference()) + throw new IllegalStateException + (J3dI18N.getString("CompressedGeometry8")) ; + + // TODO: implement this when NIO buffer support is added + return null; + } + + + /** + * Creates the retained mode CompressedGeometryRetained object that this + * CompressedGeometry object will point to. + */ + void createRetained() { + this.retained = new CompressedGeometryRetained() ; + this.retained.setSource(this) ; + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + CompressedGeometry cg = new CompressedGeometry() ; + + // Duplicate data specific to this class. + cg.cgHeader = new CompressedGeometryHeader() ; + cgHeader.copy(cg.cgHeader) ; + + // Duplicate the retained side. + CompressedGeometryRetained cgr = (CompressedGeometryRetained)retained ; + cgr.duplicate((CompressedGeometryRetained)cg.retained) ; + + // Duplicate superclass data and return. + cg.duplicateNodeComponent(this) ; + return cg ; + } +} diff --git a/src/classes/share/javax/media/j3d/CompressedGeometryHeader.java b/src/classes/share/javax/media/j3d/CompressedGeometryHeader.java new file mode 100644 index 0000000..afc4a79 --- /dev/null +++ b/src/classes/share/javax/media/j3d/CompressedGeometryHeader.java @@ -0,0 +1,239 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * The CompressedGeometrHeader object is used in conjunction with + * the CompressedGeometry object. The CompressedGeometrHeader object + * contains information specific to the compressed geometry stored in + * CompressedGeometry NodeComponent object. This information + * is used to aid the decompression of the compressed geometry. + * <P> + * All instance data is declared public and no get or set methods are + * provided. + * + * @see CompressedGeometry + */ +public class CompressedGeometryHeader extends Object { + + /** + * bufferType: compressed geometry is made up of individual points. + */ + public static final int POINT_BUFFER = 0 ; + + /** + * bufferType: compressed geometry is made up of line segments. + */ + public static final int LINE_BUFFER = 1 ; + + /** + * bufferType: compressed geometry is made up of triangles. + */ + public static final int TRIANGLE_BUFFER = 2 ; + + // Valid values for the bufferDataPresent field. + + /** + * bufferDataPresent: bit indicating that normal information is + * bundled with the vertices in the compressed geometry buffer. + */ + public static final int NORMAL_IN_BUFFER = 1 ; + + /** + * bufferDataPresent: bit indicating that RGB color information is + * bundled with the vertices in the compressed geometry buffer. + */ + public static final int COLOR_IN_BUFFER = 2 ; + + /** + * bufferDataPresent: bit indicating that alpha information is + * bundled with the vertices in the compressed geometry buffer. + */ + public static final int ALPHA_IN_BUFFER = 4 ; + + /** + * The major version number for the compressed geometry format that + * was used to compress the geometry. + * If the version number of compressed geometry is incompatible + * with the supported version of compressed geometry in the + * current version of Java 3D, the compressed geometry obejct will + * not be rendered. + * + * @see Canvas3D#queryProperties + */ + public int majorVersionNumber ; + + /** + * The minor version number for the compressed geometry format that + * was used to compress the geometry. + * If the version number of compressed geometry is incompatible + * with the supported version of compressed geometry in the + * current version of Java 3D, the compressed geometry obejct will + * not be rendered. + * + * @see Canvas3D#queryProperties + */ + public int minorVersionNumber ; + + /** + * The minor-minor version number for the compressed geometry format + * that was used to compress the geometry. + * If the version number of compressed geometry is incompatible + * with the supported version of compressed geometry in the + * current version of Java 3D, the compressed geometry obejct will + * not be rendered. + * + * @see Canvas3D#queryProperties + */ + public int minorMinorVersionNumber ; + + /** + * Describes the type of data in the compressed geometry buffer. + * Only one type may be present in any given compressed geometry + * buffer. + */ + public int bufferType ; + + /** + * Contains bits indicating what data is bundled with the vertices in the + * compressed geometry buffer. If this data is not present (e.g. color) + * then this info will be inherited from the Appearance node. + */ + public int bufferDataPresent ; + + /** + * Size of the compressed geometry in bytes. + */ + public int size ; + + /** + * Offset in bytes of the start of the compressed geometry from the + * beginning of the compressed geometry byte array passed to the + * CompressedGeometry constructor. <p> + * + * If the CompressedGeometry is created with reference access semantics, + * then this allow external compressors or file readers to embed several + * blocks of compressed geometry in a single large byte array, possibly + * interspersed with metadata that is not specific to Java 3D, without + * having to copy each block to a separate byte array. <p> + * + * If the CompressedGeometry is created with copy access semantics, then + * <code>size</code> bytes of compressed geometry data are copied from the + * offset indicated by <code>start</code> instead of copying the entire + * byte array. The getCompressedGeometry() method will return only the + * bytes used to construct the object, and the getCompressedGeometryHeader() + * method will return a header with the <code>start</code> field set to 0. + */ + public int start ; + + /** + * A point that defines the lower bound of the <i>x</i>, + * <i>y</i>, and <i>z</i> components for all positions in the + * compressed geometry buffer. If null, a lower bound of + * (-1,-1,-1) is assumed. Java 3D will use this information to + * construct a bounding box around compressed geometry objects + * that are used in nodes for which the auto compute bounds flag + * is true. The default value for this point is null. + * + * @since Java 3D 1.2 + */ + public Point3d lowerBound = null ; + + /** + * A point that defines the upper bound of the <i>x</i>, + * <i>y</i>, and <i>z</i> components for all positions in the + * compressed geometry buffer. If null, an upper bound of (1,1,1) + * is assumed. Java 3D will use this information to construct a + * bounding box around compressed geometry objects that are used + * in nodes for which the auto compute bounds flag is true. The + * default value for this point is null. + * + * @since Java 3D 1.2 + */ + public Point3d upperBound = null ; + + /** + * Creates a new CompressedGeometryHeader object used for the + * creation of a CompressedGeometry NodeComponent object. + * All instance data is declared public and no get or set methods are + * provided. All values are set to 0 by default and must be filled + * in by the application. + * + * @see CompressedGeometry + */ + public CompressedGeometryHeader() { + } + + /** + * Package-scoped method to copy current CompressedGeometryHeader object + * to the passed-in CompressedGeometryHeader object. + * + * @param hdr the CompressedGeometryHeader object into which to copy the + * current CompressedGeometryHeader. + */ + void copy(CompressedGeometryHeader hdr) { + hdr.majorVersionNumber = this.majorVersionNumber ; + hdr.minorVersionNumber = this.minorVersionNumber ; + hdr.minorMinorVersionNumber = this.minorMinorVersionNumber ; + hdr.bufferType = this.bufferType ; + hdr.bufferDataPresent = this.bufferDataPresent ; + hdr.size = this.size ; + hdr.start = this.start ; + hdr.lowerBound = this.lowerBound ; + hdr.upperBound = this.upperBound ; + } + + /** + * Returns a String describing the contents of the + * CompressedGeometryHeader object. + * + * @return a String describing contents of the compressed geometry header + */ + public String toString() { + String type = "UNKNOWN" ; + switch (bufferType) { + case POINT_BUFFER: type = "POINT_BUFFER" ; break ; + case LINE_BUFFER: type = "LINE_BUFFER" ; break ; + case TRIANGLE_BUFFER: type = "TRIANGLE_BUFFER" ; break ; + } + + String data = "" ; + if ((bufferDataPresent & NORMAL_IN_BUFFER) != 0) + data = data + "NORMALS " ; + if ((bufferDataPresent & COLOR_IN_BUFFER) != 0) + data = data + "COLORS " ; + if ((bufferDataPresent & ALPHA_IN_BUFFER) != 0) + data = data + "ALPHA " ; + + String lbound = "null" ; + if (lowerBound != null) + lbound = lowerBound.toString() ; + + String ubound = "null" ; + if (upperBound != null) + ubound = upperBound.toString() ; + + return + "majorVersionNumber: " + majorVersionNumber + " " + + "minorVersionNumber: " + minorVersionNumber + " " + + "minorMinorVersionNumber: " + minorMinorVersionNumber + "\n" + + "bufferType: " + type + " " + + "bufferDataPresent: " + data + "\n" + + "size: " + size + " " + + "start: " + start + "\n" + + "lower bound: " + lbound + "\n" + + "upper bound: " + ubound + " " ; + } +} diff --git a/src/classes/share/javax/media/j3d/CompressedGeometryRenderMethod.java b/src/classes/share/javax/media/j3d/CompressedGeometryRenderMethod.java new file mode 100644 index 0000000..0080ca1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/CompressedGeometryRenderMethod.java @@ -0,0 +1,103 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d ; + +/** + * The RenderMethod interface is used to create various ways to render + * different geometries. + */ + +class CompressedGeometryRenderMethod implements RenderMethod { + + /** + * The actual rendering code for this RenderMethod. + */ + public boolean render(RenderMolecule rm, Canvas3D cv, int pass, + RenderAtomListInfo ra, int dirtyBits) { + + CompressedGeometryRetained cgr ; + + + if (rm.doInfinite) { + cv.updateState(pass, dirtyBits); + while (ra != null) { + renderCompressedGeo(ra, rm, cv); + ra = ra.next; + } + return true; + } + + boolean isVisible = false; // True if any of the RAs is visible. + + while (ra != null) { + if (cv.ra == ra.renderAtom) { + if (cv.raIsVisible) { + cv.updateState(pass, dirtyBits); + renderCompressedGeo(ra, rm, cv); + isVisible = true; + } + } + else { + if (ra.renderAtom.localeVwcBounds.intersect(cv.viewFrustum)) { + cv.updateState(pass, dirtyBits); + cv.raIsVisible = true; + renderCompressedGeo(ra, rm, cv); + isVisible = true; + } + else { + cv.raIsVisible = false; + } + cv.ra = ra.renderAtom; + } + + ra = ra.next; + } + + return isVisible; + + } + + void renderCompressedGeo(RenderAtomListInfo ra, RenderMolecule rm, Canvas3D cv) { + + boolean useAlpha ; + CompressedGeometryRetained cgr ; + useAlpha = rm.useAlpha ; + + cgr = (CompressedGeometryRetained)ra.renderAtom.geometryAtom.geometryArray[ra.index]; + + /* force_decompression if lighting is disabled and + * ignoreVertexColors is TRUE, since there is no way for openGL + * to ignore vertexColors in this case, force decompression + */ + if (rm.textureBin.attributeBin.ignoreVertexColors && rm.enableLighting == false && cgr.mirrorGeometry == null) { + cgr.mirrorGeometry = cgr.getGeometry(true, cv) ; + } + else if (cgr.mirrorGeometry == null) { + // cgr.getGeometry() will decompress in software and return a + // GeometryRetained if hardware decompression isn't available, + // otherwise it just returns cgr. + cgr.mirrorGeometry = cgr.getGeometry(false, cv) ; + if (cgr.mirrorGeometry == null) + // decompressor error + return ; + } + + cgr.mirrorGeometry.execute + (cv, ra.renderAtom, rm.isNonUniformScale, + (useAlpha && ra.geometry().noAlpha), rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin.ignoreVertexColors, + -1) ; + } +} diff --git a/src/classes/share/javax/media/j3d/CompressedGeometryRetained.java b/src/classes/share/javax/media/j3d/CompressedGeometryRetained.java new file mode 100644 index 0000000..20b28fe --- /dev/null +++ b/src/classes/share/javax/media/j3d/CompressedGeometryRetained.java @@ -0,0 +1,439 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d ; +import javax.vecmath.* ; + +/** + * The compressed geometry object is used to store geometry in a + * compressed format. Using compressed geometry reduces the amount + * of memory needed by a Java 3D application and increases the speed + * objects can be sent over the network. Once geometry decompression + * hardware support becomes available, increased rendering performance + * will also result from the use of compressed geometry. + */ +class CompressedGeometryRetained extends GeometryRetained { + + // If not in by-reference mode, a 48-byte header as defined by the + // GL_SUNX_geometry_compression OpenGL extension is always concatenated to + // the beginning of the compressed geometry data and copied along with the + // it into a contiguous array. This allows hardware decompression using + // the obsolete experimental GL_SUNX_geometry_compression extension if + // that is all that is available. + // + // This is completely distinct and not to be confused with the cgHeader + // field on the non-retained side, although much of the data is + // essentially the same. + private static final int HEADER_LENGTH = 48 ; + + // These are the header locations examined. + private static final int HEADER_MAJOR_VERSION_OFFSET = 0 ; + private static final int HEADER_MINOR_VERSION_OFFSET = 1 ; + private static final int HEADER_MINOR_MINOR_VERSION_OFFSET = 2 ; + private static final int HEADER_BUFFER_TYPE_OFFSET = 3 ; + private static final int HEADER_BUFFER_DATA_OFFSET = 4 ; + + // The OpenGL compressed geometry extensions use bits instead of + // enumerations to represent the type of compressed geometry. + static final byte TYPE_POINT = 1 ; + static final byte TYPE_LINE = 2 ; + static final byte TYPE_TRIANGLE = 4 ; + + // Version number of this compressed geometry object. + int majorVersionNumber ; + int minorVersionNumber ; + int minorMinorVersionNumber ; + + // These fields are used by the native execute() method. + int packedVersion ; + int bufferType ; + int bufferContents ; + int renderFlags ; + int offset ; + int size ; + byte[] compressedGeometry ; + + // True if by-reference data access mode is in effect. + private boolean byReference = false ; + + // A reference to the original byte array with which this object was + // created. If hardware decompression is available but it doesn't support + // by-reference semantics, then an internal copy of the original byte array + // is made even when by-reference semantics have been requested. + private byte[] originalCompressedGeometry = null ; + + // True if the platform supports hardware decompression. + private static boolean hardwareDecompression = false ; + + // This field retains a reference to the GeometryRetained object used for + // geometry-based picking. It is normally the same reference as the + // mirror geometry used for rendering unless hardware decompression is + // supported. + private GeometryRetained pickGeometry = null ; + + /** + * Native method that returns availability of a native by-reference + * rendering API for compressed geometry. + */ + native boolean decompressByRef(long ctx) ; + + /** + * Native method that returns availability of hardware acceleration for + * compressed geometry of the given version. + */ + native boolean decompressHW(long ctx, int majorVersion, int minorVersion) ; + + /** + * Native method that does the rendering + */ + native void execute(long ctx, int version, int bufferType, + int bufferContents, int renderFlags, + int offset, int size, byte[] geometry) ; + + /** + * Method for calling native execute() method on behalf of the J3D renderer. + */ + void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha, + boolean multiScreen, int screen, + boolean ignoreVertexColors, int pass) { + + // TODO: alpha udpate + execute(cv.ctx, packedVersion, bufferType, bufferContents, + renderFlags, offset, size, compressedGeometry) ; + } + + /** + * The package-scoped constructor. + */ + CompressedGeometryRetained() { + this.geoType = GEO_TYPE_COMPRESSED ; + + // Compressed geometry is always bounded by [-1..1] on each axis, so + // set that as the initial bounding box. + geoBounds.setUpper( 1.0, 1.0, 1.0) ; + geoBounds.setLower(-1.0,-1.0,-1.0) ; + } + + /** + * Compressed geometry is immutable so this method does nothing. + */ + void computeBoundingBox() { + } + + /** + * Update this object. Compressed geometry is immutable so there's + * nothing to do. + */ + void update() { + isDirty = 0 ; + } + + /** + * Return true if the data access mode is by-reference. + */ + boolean isByReference() { + return this.byReference ; + } + + private void createByCopy(byte[] geometry) { + // Always copy a header along with the compressed geometry into a + // contiguous array in order to support hardware acceleration with the + // GL_SUNX_geometry_compression extension. The header is unnecessary + // if only the newer GL_SUN_geometry_compression API needs support. + compressedGeometry = new byte[HEADER_LENGTH + this.size] ; + + compressedGeometry[HEADER_MAJOR_VERSION_OFFSET] = + (byte)this.majorVersionNumber ; + + compressedGeometry[HEADER_MINOR_VERSION_OFFSET] = + (byte)this.minorVersionNumber ; + + compressedGeometry[HEADER_MINOR_MINOR_VERSION_OFFSET] = + (byte)this.minorMinorVersionNumber ; + + compressedGeometry[HEADER_BUFFER_TYPE_OFFSET] = + (byte)this.bufferType ; + + compressedGeometry[HEADER_BUFFER_DATA_OFFSET] = + (byte)this.bufferContents ; + + System.arraycopy(geometry, this.offset, + compressedGeometry, HEADER_LENGTH, this.size) ; + + this.offset = HEADER_LENGTH ; + } + + /** + * Creates the retained compressed geometry data. Data from the header is + * always copied; the compressed geometry is copied as well if the data + * access mode is not by-reference. + * + * @param hdr the compressed geometry header + * @param geometry the compressed geometry + * @param byReference if true then by-reference semantics requested + */ + void createCompressedGeometry(CompressedGeometryHeader hdr, + byte[] geometry, boolean byReference) { + + this.byReference = byReference ; + + if (hdr.lowerBound != null) + this.geoBounds.setLower(hdr.lowerBound) ; + + if (hdr.upperBound != null) + this.geoBounds.setUpper(hdr.upperBound) ; + + this.centroid.set(geoBounds.getCenter()); + recompCentroid = false; + this.majorVersionNumber = hdr.majorVersionNumber ; + this.minorVersionNumber = hdr.minorVersionNumber ; + this.minorMinorVersionNumber = hdr.minorMinorVersionNumber ; + + this.packedVersion = + (hdr.majorVersionNumber << 24) | + (hdr.minorVersionNumber << 16) | + (hdr.minorMinorVersionNumber << 8) ; + + switch(hdr.bufferType) { + case CompressedGeometryHeader.POINT_BUFFER: + this.bufferType = TYPE_POINT ; + break ; + case CompressedGeometryHeader.LINE_BUFFER: + this.bufferType = TYPE_LINE ; + break ; + case CompressedGeometryHeader.TRIANGLE_BUFFER: + this.bufferType = TYPE_TRIANGLE ; + break ; + } + + this.bufferContents = hdr.bufferDataPresent ; + this.renderFlags = 0 ; + + this.size = hdr.size ; + this.offset = hdr.start ; + + if (byReference) { + // Assume we can use the given reference, but maintain a second + // reference in case a copy is later needed. + this.compressedGeometry = geometry ; + this.originalCompressedGeometry = geometry ; + } else { + // Copy the original data into a format that can be used by both + // the software and native hardware decompressors. + createByCopy(geometry) ; + this.originalCompressedGeometry = null ; + } + } + + /** + * Decompress this object into a GeometryArrayRetained if hardware + * decompression is not available. Once decompressed the resulting + * geometry replaces the geometry reference in the associated RenderAtom + * as well as the mirror geometry reference in this object. + */ + GeometryRetained getGeometry(boolean forceDecompression, Canvas3D cv ) { + + if (forceDecompression) { + // forceDecompression is set to true if lighting is disabled and + // ignoreVertexColors is true, since there is no way for openGL to + // ignore vertexColors in this case. + GeometryDecompressorRetained gdr = + new GeometryDecompressorRetained() ; + + mirrorGeometry = gdr.decompress(this) ; + gdr.getBoundingBox(geoBounds) ; + pickGeometry = mirrorGeometry ; + } + else { + // Return this object if hardware decompression is available. + if (hardwareDecompression) + return (GeometryRetained)this ; + + // Check to see if hardware decompression is available. + if (decompressHW(cv.ctx, majorVersionNumber, minorVersionNumber)) { + hardwareDecompression = true ; + + // If hardware can't handle by-reference, punt to by-copy. + if (isByReference() && !decompressByRef(cv.ctx)) { + createByCopy(compressedGeometry) ; + } + + return (GeometryRetained)this ; + } + + // Decompress the data into a GeometryArrayRetained representation + // for the mirror geometry reference. + GeometryDecompressorRetained gdr = + new GeometryDecompressorRetained() ; + + mirrorGeometry = gdr.decompress(this) ; + gdr.getBoundingBox(geoBounds) ; + + // The mirror geometry contains a superset of the pick geometry + // data. Since hardware decompression isn't available, there's no + // need to retain separate pick geometry. + pickGeometry = mirrorGeometry ; + } + + return mirrorGeometry ; + } + + /** + * This method always decompresses the geometry and retains the result in + * order to support geometry-based picking and collision detection. The + * returned GeometryRetained object will contain only positions and + * connections. + */ + GeometryRetained getPickGeometry() { + // Return the pick geometry if available. + if (pickGeometry != null) + return pickGeometry ; + + // Decompress the data into a GeometryArrayRetained representation for + // the pick geometry reference. Retain it and its bounding box. + GeometryDecompressorRetained gdr = new GeometryDecompressorRetained() ; + gdr.setDecompressPositionsOnly(true) ; + + pickGeometry = gdr.decompress(this) ; + gdr.getBoundingBox(geoBounds) ; + return pickGeometry ; + } + + // + // The following intersect() methods are used to implement geometry-based + // picking and collision. + // + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + GeometryRetained geom = getPickGeometry() ; + return (geom != null ? + geom.intersect(pickShape, dist, iPnt) : false); + } + + boolean intersect(Bounds targetBound) { + GeometryRetained geom = getPickGeometry() ; + return (geom != null ? geom.intersect(targetBound) : false); + } + + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained g) { + GeometryRetained geom = getPickGeometry() ; + return (geom != null ? + geom.intersect(thisToOtherVworld, g) : false); + } + + boolean intersect(Point3d[] pnts) { + GeometryRetained geom = getPickGeometry() ; + return (geom != null ? geom.intersect(pnts) : false); + } + + /** + * Return a vertex format mask that's compatible with GeometryArray + * objects. + */ + int getVertexFormat() { + int vertexFormat = GeometryArray.COORDINATES ; + + if ((this.bufferContents & + CompressedGeometryHeader.NORMAL_IN_BUFFER) != 0) + vertexFormat |= GeometryArray.NORMALS ; + + if ((this.bufferContents & + CompressedGeometryHeader.COLOR_IN_BUFFER) != 0) + vertexFormat |= GeometryArray.COLOR ; + + if ((this.bufferContents & + CompressedGeometryHeader.ALPHA_IN_BUFFER) != 0) + vertexFormat |= GeometryArray.WITH_ALPHA ; + + return vertexFormat ; + } + + /** + * Return a buffer type that's compatible with CompressedGeometryHeader. + */ + int getBufferType() { + switch(this.bufferType) { + case TYPE_POINT: + return CompressedGeometryHeader.POINT_BUFFER ; + case TYPE_LINE: + return CompressedGeometryHeader.LINE_BUFFER ; + default: + case TYPE_TRIANGLE: + return CompressedGeometryHeader.TRIANGLE_BUFFER ; + } + } + + /** + * Copies compressed geometry data into the given array of bytes. + * The internal header information is not copied. + * + * @param buff array of bytes into which to copy compressed geometry + */ + void copy(byte[] buff) { + System.arraycopy(compressedGeometry, offset, buff, 0, size) ; + } + + /** + * Returns a reference to the original compressed geometry byte array, + * which may have been copied even if by-reference semantics have been + * requested. It will be null if byCopy is in effect. + * + * @return reference to array of bytes containing the compressed geometry. + */ + byte[] getReference() { + return originalCompressedGeometry ; + } + + /** + * Copies all retained data for cloneNodeComponent() on the non-retained + * side. This is unlike GeometryArray subclasses which just call the + * public API constructors and then duplicateNodeComponent() to invoke the + * GeometryArray implementation of duplicateAttributes(), since the + * CompressedGeometry class directly subclasses Geometry and calling the + * public constructors would cause a lot of redundant data copying. + */ + void duplicate(CompressedGeometryRetained cgr) { + cgr.majorVersionNumber = this.majorVersionNumber ; + cgr.minorVersionNumber = this.minorVersionNumber ; + cgr.minorMinorVersionNumber = this.minorMinorVersionNumber ; + + cgr.packedVersion = this.packedVersion ; + cgr.bufferType = this.bufferType ; + cgr.bufferContents = this.bufferContents ; + cgr.renderFlags = this.renderFlags ; + + cgr.offset = this.offset ; + cgr.size= this.size ; + + cgr.geoBounds.setLower(this.geoBounds.lower) ; + cgr.geoBounds.setUpper(this.geoBounds.upper) ; + cgr.pickGeometry = this.pickGeometry ; + cgr.byReference = this.byReference ; + + if (this.byReference) { + // Copy references only. + cgr.compressedGeometry = this.compressedGeometry ; + cgr.originalCompressedGeometry = this.originalCompressedGeometry ; + } else { + // Copy entire byte array including 48-byte native OpenGL header. + cgr.compressedGeometry = new byte[this.compressedGeometry.length] ; + System.arraycopy(this.compressedGeometry, 0, + cgr.compressedGeometry, 0, + this.compressedGeometry.length) ; + cgr.originalCompressedGeometry = null ; + } + } + + int getClassType() { + return COMPRESS_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/ConeSound.java b/src/classes/share/javax/media/j3d/ConeSound.java new file mode 100644 index 0000000..d4fe4a5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ConeSound.java @@ -0,0 +1,903 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.lang.Math; +import javax.vecmath.Point2f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; + + +/** + * The ConeSound node object defines a PointSound node whose sound source is + * directed along a specific vector in space. A ConeSound source is attenuated + * by gain scale factors and filters based on the angle between the vector from + * the source to the listener, and the ConeSound's direction vector. This + * attenuation is either a single spherical distance gain attenuation (as for + * a general PointSound source) or dual front and back distance gain + * attenuations defining elliptical attenuation areas. The angular filter and the + * active AuralAttribute component filter define what filtering is applied to + * the sound source. (See AuralAtttribute class for more details on filtering.) + * This node has the same attributes as a PointSound node with the addition of a + * direction vector and an array of points each containing: angular distance (in + * radians), gain scale factor, and filter (which for now consists of a lowpass + * filter cutoff frequency). Similar to the definition of the back distance gain + * array for PointSounds, a piece-wise linear curve (defined in terms of + * radians from the axis) specifies the slope of these additional attenuation + * values. + * <P> + * Distance Gain attuation + * <P><UL> + * A cone sound node can have one or two distance attenuation arrays. + * If none are set, no distance gain attenuation is performed (equivalent + * to using a distance gain of 1.0 for all distances). If only one distance + * attenuation array is set, sphere attenuation is assumed. If both a front + * and back distance attenuation are set, elliptical attenuation regions + * are defined. + *<P> + * Use PointSound setDistanceGain() method to set the front distance + * attenuation array separate from the back distance attenuation array. + * A front distance attenuation array defines monotonically-increasing + * distances from the sound source origin along the position direction + * vector. A back distance attenuation array (if given) defines + * monotonically-increasing distances from the sound source origin along the + * negative direction vector. The two arrays must be of the same length. + * The backDistance[i] gain values must be less than or equal to + * the frontDistance[i] gain values. + * <P> + * Gain scale factors are associated with distances from the listener to + * the sound source via an array of (distance, gain-scale-factor) pairs. + * The gain scale factor applied to the sound source is the linear + * interpolated gain value between the distance value range that includes + * the current distance from the listener to the sound source. + *<P> + * The getDistanceGainLength method defined for PointSound returns the length + * of the all distance gain attenuation arrays, including the back distance + * gain arrays. Arrays passed into getDistanceGain methods should all + * be at least this size. + * </UL> <P> + * Direction Methods + * <P><UL> + * This value is the sound source's direction vector. It is the axis from + * which angular distance is measured. + * </UL><P> + * Angular Attenuation + * <P><UL> + * Besides sound (linear) distance attenuation a ConeSound can optionally + * define angular gain and filter attenuation. + * <P> + * This attenuation is defined + * as a triple of (angular distance, gain-scale-factor, filter). The + * distance is measured as the angle in radians between the ConeSound's + * direction vector and the vector from the sound source position to the + * listener. Both the gain scale factor and filter applied to the sound + * source is the linear interpolation of values between the distance value + * range that includes the angular distance from the sound source axis. + *<P> + * If this is not set, no angular gain attenuation or filtering is performed + * (equivalent to using an angular gain scale factor of 1.0 and an angular + * filter of Sound.NO_FILTER for all distances). + * <P> + * If angular distance from the listener-sound-position vector and a sound's + * direction vector is less than the first distance in the array, only the first + * gain scale factor and first filter are applied to the sound source. + * This creates a conical region around the listener within which the sound + * is uniformly attenuated by first gain and first filter in the array. + * <P> + * If the distance from the listener-sound-position vector and the sound's + * direction vector is greater than the last distance in the array, the last gain + * scale factor and last filter are applied to the sound source. + * <P> + * Distance elements in this array of points is a monotonically-increasing + * set of floating point numbers measured from 0 to p radians. Gain scale + * factors elements in this list of points can be any positive floating + * point numbers. While for most applications this list of gain scale + * factors will usually be monotonically-decreasing, they do not have to be. + * The filter (for now) is a single simple frequency cutoff value. + * <P> + * The getAngularAttenuationArrayLength method returns the length of the angular + * attenuation arrays. Arrays passed into getAngularAttenuation methods + * should all be at least this size. + *</UL> + */ + +public class ConeSound extends PointSound { + // Constants + // + // These flags, when enabled using the setCapability method, allow an + // application to invoke methods that respectively read and write direction + // and angular attenuation array. These capability flags are enforced only + // when the node is part of a live or compiled scene graph. + + /** + * Specifies that this ConeSound allows access to its object's direction + * information. + */ + public static final int + ALLOW_DIRECTION_READ = CapabilityBits.CONE_SOUND_ALLOW_DIRECTION_READ; + + /** + * Specifies that this ConeSound allows writing to its object's direction + * information. + */ + public static final int + ALLOW_DIRECTION_WRITE = CapabilityBits.CONE_SOUND_ALLOW_DIRECTION_WRITE; + + /** + * Specifies that this ConeSound allows access to its object's cone params + * information. + */ + public static final int + ALLOW_ANGULAR_ATTENUATION_READ = CapabilityBits.CONE_SOUND_ALLOW_ANGULAR_ATTENUATION_READ; + + /** + * Specifies that this ConeSound allows writing to its object's cone params + * information. + */ + public static final int + ALLOW_ANGULAR_ATTENUATION_WRITE = CapabilityBits.CONE_SOUND_ALLOW_ANGULAR_ATTENUATION_WRITE; + + /** + * Constructs and initializes a new ConeSound node using default + * parameters. The following default values are used: + * <ul> + * Direction vector: (0.0, 0.0, 1.0) <br> + * Angular attenuation: + * ((0.0, 1.0, Sound.NO_FILTER),(p/2, 0.0, Sound.NO_FILTER)) <br> + * </ul> + */ + public ConeSound() { + // Uses default values defined in ConeSoundRetained.java + super(); + } + + /** + * Constructs a ConeSound node object using only the provided parameter + * values for sound, overall initial gain, position, and direction. The + * remaining fields are set to the default values above. This form uses + * Point3f as input for its position and Vector3f for direction. + * @param soundData sound source data associated with this node + * @param initialGain amplitude scale factor applied to sound + * @param position 3D location of source + * @param direction 3D vector defining cone's axis + */ + public ConeSound(MediaContainer soundData, + float initialGain, + Point3f position, + Vector3f direction) { + + super(soundData, initialGain, position ); + ((ConeSoundRetained)this.retained).setDirection(direction); + } + + /** + * Constructs a ConeSound node object using only the provided parameter + * values for sound, overall initial gain, position, and direction. The + * remaining fields are set to the default values above. This form uses + * individual float parameters for the elements of the position and + * direction vectors. + * @param soundData sound source data + * @param initialGain amplitude scale factor applied to sound + * @param posX x coordinate of location of source + * @param posY y coordinate of location of source + * @param posZ z coordinate of location of source + * @param dirX x coordinate cones' axii vector + * @param dirY y coordinate cones' axii vector + * @param dirZ z coordinate cones' axii vector + */ + public ConeSound(MediaContainer soundData, + float initialGain, + float posX, float posY, float posZ, + float dirX, float dirY, float dirZ) { + + super(soundData, initialGain, posX, posY, posZ ); + ((ConeSoundRetained)this.retained).setDirection(dirX, dirY, dirZ); + } + + /** + * Constructs a ConeSound node object using all the provided PointSound + * parameter values. This form uses points or vectors as input for its + * position, direction, and front/back distance attenuation arrays. + *<P> + * Unlike the single distance gain attenuation array for PointSounds which + * define spherical areas about the sound source between which gains are + * linearly interpolated, this directed ConeSound can have two distance gain + * attenuation arrays that define ellipsoidal attenuation areas. See the + * setDistanceGain PointSound method for details on how the separate distance + * and distanceGain arrays are interpreted. + *<P> + * The ConeSound's direction vector and angular measurements are defined in + * the local coordinate system of the node. + * @param soundData sound source data associated with this node + * @param initialGain amplitude scale factor applied to sound + * @param loopCount number of times sound is looped + * @param release flag denoting playing sound to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param position 3D location of source + * @param frontDistanceAttenuation array of (distance,gain) pairs controlling + * attenuation values along the positive direction axis + * @param backDistanceAttenuation array of (distance,gain) pairs controlling + * attenuation values along the negative direction axis + * @param direction vector defining cones' axii + */ + public ConeSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + Point3f position, + Point2f[] frontDistanceAttenuation, + Point2f[] backDistanceAttenuation, + Vector3f direction) { + + super(soundData, initialGain, loopCount, release, continuous, enable, + region, priority, position, frontDistanceAttenuation ); + ((ConeSoundRetained)this.retained).setBackDistanceGain( + backDistanceAttenuation); + ((ConeSoundRetained)this.retained).setDirection(direction); + } + + /** + * Constructs a ConeSound node object using the provided parameter values. + * This form uses individual float parameters for the elements of the + * position, direction, and two distance attenuation arrays. + * Unlike the single distance gain attenuation array for PointSounds, which + * define spherical areas about the sound source between which gains are + * linearly interpolated, this directed ConeSound can have two distance + * gain attenuation arrays that define ellipsoidal attenuation areas. + * See the setDistanceGain PointSound method for details on how the + * separate distance and distanceGain arrays are interpreted. + * The ConeSound's direction vector and angular measurements are defined + * in the local coordinate system of the node. + * @param soundData sound source data associated with this node + * @param initialGain amplitude scale factor applied to sound + * @param loopCount number of times sound is looped + * @param release flag denoting playing sound to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param posX x coordinate of location of source + * @param posY y coordinate of location of source + * @param posZ z coordinate of location of source + * @param frontDistance array of front distance values used for attenuation + * @param frontDistanceGain array of front gain scale factors used for attenuation + * @param backDistance array of back distance values used for attenuation + * @param backDistanceGain array of back gain scale factors used for attenuation + * @param dirX x coordinate cones' axii vector + * @param dirY y coordinate cones' axii vector + * @param dirZ z coordinate cones' axii vector + */ + public ConeSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + float posX, float posY, float posZ, + float[] frontDistance, + float[] frontDistanceGain, + float[] backDistance, + float[] backDistanceGain, + float dirX, float dirY, float dirZ ) { + super(soundData, initialGain, loopCount, release, continuous, enable, + region, priority, posX, posY, posZ, + frontDistance, frontDistanceGain ); + ((ConeSoundRetained)this.retained).setDirection(dirX, dirY, dirZ); + ((ConeSoundRetained)this.retained).setBackDistanceGain( + backDistance, backDistanceGain ); + } + + /** + * Constructs a ConeSound node object using all the provided PointSound + * parameter values, which include a single spherical distance attenuation + * array, but includes an angular attenuation array. + * This form uses points and vectors as input for its position, direction, + * single spherical distanceAttenuation array, and angularAttenuation array. + * It also accepts arrays of points for the distance attenuation and angular + * values. Each Point2f in the distanceAttenuation array contains a distance + * and a gain scale factor. Each Point3f in the angularAttenuation array + * contains an angular distance, a gain scale factor, and a filtering value + * (which is currently defined as a simple cutoff frequency). + * @param soundData sound source data associated with this node + * @param initialGain amplitude scale factor applied to sound + * @param loopCount number of times sound is looped + * @param release flag denoting playing sound to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param position 3D location of source + * @param distanceAttenuation array of (distance,gain) pairs controlling + * attenuation values along the positive direction axis + * @param direction vector defining cones' axii + * @param angularAttenuation array of tuples defining angular gain/filtering + */ + public ConeSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + Point3f position, + Point2f[] distanceAttenuation, + Vector3f direction, + Point3f[] angularAttenuation ) { + + super(soundData, initialGain, loopCount, release, continuous, enable, + region, priority, position, distanceAttenuation ); + ((ConeSoundRetained)this.retained).setDirection(direction); + ((ConeSoundRetained)this.retained).setAngularAttenuation( + angularAttenuation); + } + + /** + * Constructs a ConeSound node object using all the provided PointSound + * parameter values, which include a single spherical distance attenuation + * array, but includes an angular attenuation array. + * This form uses individual float parameters for elements of position, + * direction, distanceAttenuation array, and angularAttenuation array. + * It also accepts separate arrays for the distance and gain scale factors + * components of distance attenuation, and separate arrays for the angular + * distance, angular gain, and filtering components of angular attenuation. + * See the setDistanceGain ConeSound method for details on how the separate + * distance and distanceGain arrays are interpreted. See the + * setAngularAttenuation ConeSound method for details on how the separate + * angularDistance, angularGain, and filter arrays are interpreted. + * @param soundData sound source data associated with this node + * @param initialGain amplitude scale factor applied to sound + * @param loopCount number of times sound is looped + * @param release flag denoting playing sound to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param posX x coordinate of location of source + * @param posY y coordinate of location of source + * @param posZ z coordinate of location of source + * @param distance array of front distance values used for attenuation + * @param distanceGain array of front gain scale factors used for attenuation + * @param dirX x coordinate cones' axii vector + * @param dirY y coordinate cones' axii vector + * @param dirZ z coordinate cones' axii vector + * @param angle array of angle radians for angularAttenuation + * @param angularGain array of gain scale factors for angularAttenuation + * @param frequencyCutoff array of lowpass filter values in Hertz + */ + public ConeSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + float posX, float posY, float posZ, + float[] distance, + float[] distanceGain, + float dirX, float dirY, float dirZ, + float[] angle, + float[] angularGain, + float[] frequencyCutoff) { + super(soundData, initialGain, loopCount, release, continuous, enable, + region, priority, posX, posY, posZ, + distance, distanceGain ); + ((ConeSoundRetained)this.retained).setDirection(dirX, dirY, dirZ); + ((ConeSoundRetained)this.retained).setAngularAttenuation(angle, + angularGain, frequencyCutoff); + } + + /** + * Constructs and initializes a new Cone Sound node explicitly setting all + * PointSound and ConeSound fields as arguments: the PointSound position, + * front and back distance attenuation Point2f array, and ConeSound + * direction vector and Point3f angular attenuation. + * @param soundData sound source data associated with this node + * @param initialGain amplitude scale factor applied to sound + * @param loopCount number of times sound is looped + * @param release flag denoting playing sound to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param position 3D location of source + * @param frontDistanceAttenuation array of (distance,gain) pairs controlling + * attenuation values along the positive direction axis + * @param backDistanceAttenuation array of (distance,gain) pairs controlling + * attenuation values along the negative direction axis + * @param direction vector defining cones' axii + * @param angularAttenuation array of tuples defining angular gain/filtering + */ + public ConeSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + Point3f position, + Point2f[] frontDistanceAttenuation, + Point2f[] backDistanceAttenuation, + Vector3f direction, + Point3f[] angularAttenuation ) { + + super(soundData, initialGain, loopCount, release, continuous, enable, + region, priority, position, frontDistanceAttenuation ); + ((ConeSoundRetained)this.retained).setBackDistanceGain( + backDistanceAttenuation); + ((ConeSoundRetained)this.retained).setDirection(direction); + ((ConeSoundRetained)this.retained).setAngularAttenuation( + angularAttenuation); + } + + /** + * Constructs and initializes a new Cone Sound node explicitly setting all + * PointSound and ConeSound fields as arguments but all the vector and point + * arguments are broken into individual float array components. + * @param soundData sound source data associated with this node + * @param initialGain amplitude scale factor applied to sound + * @param loopCount number of times sound is looped + * @param release flag denoting playing sound to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param posX x coordinate of location of source + * @param posY y coordinate of location of source + * @param posZ z coordinate of location of source + * @param frontDistance array of front distance values used for attenuation + * @param frontDistanceGain array of front gain scale factors used for attenuation + * @param backDistance array of back distance values used for attenuation + * @param backDistanceGain array of back gain scale factors used for attenuation + * @param dirX x coordinate cones' axii vector + * @param dirY y coordinate cones' axii vector + * @param dirZ z coordinate cones' axii vector + * @param angle array of angle radians for angularAttenuation + * @param angularGain array of gain scale factors for angularAttenuation + * @param frequencyCutoff array of lowpass filter values in Hertz + */ + public ConeSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + float posX, float posY, float posZ, + float[] frontDistance, + float[] frontDistanceGain, + float[] backDistance, + float[] backDistanceGain, + float dirX, float dirY, float dirZ, + float[] angle, + float[] angularGain, + float[] frequencyCutoff) { + super(soundData, initialGain, loopCount, release, continuous, enable, + region, priority, posX, posY, posZ, + frontDistance, frontDistanceGain ); + ((ConeSoundRetained)this.retained).setBackDistanceGain( + backDistance, backDistanceGain ); + ((ConeSoundRetained)this.retained).setDirection(dirX, dirY, dirZ); + ((ConeSoundRetained)this.retained).setAngularAttenuation(angle, + angularGain, frequencyCutoff); + } + + /** + * Creates the retained mode ConeSoundRetained object that this + * ConeSound object will point to. + */ + void createRetained() { + this.retained = new ConeSoundRetained(); + this.retained.setSource(this); + } + + // + // OVERLOADED Sound methods + // + /** + * Sets this sound's distance gain elliptical attenuation - + * where gain scale factor is applied to sound based on distance listener + * is from sound source. + * @param frontAttenuation defined by pairs of (distance,gain-scale-factor) + * @param backAttenuation defined by pairs of (distance,gain-scale-factor) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDistanceGain(Point2f[] frontAttenuation, + Point2f[] backAttenuation ) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound0")); + + ((ConeSoundRetained)this.retained).setDistanceGain(frontAttenuation, + backAttenuation); + } + + /** + * Sets this sound's distance gain attenuation as an array of Point2fs. + * @param frontDistance array of monotonically-increasing floats + * @param frontGain array of non-negative scale factors + * @param backDistance array of monotonically-increasing floats + * @param backGain array of non-negative scale factors + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDistanceGain(float[] frontDistance, float[] frontGain, + float[] backDistance, float[] backGain) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound0")); + + ((ConeSoundRetained)this.retained).setDistanceGain( + frontDistance, frontGain, backDistance, backGain); + } + + /** + * Sets this sound's back distance gain attenuation - where gain scale + * factor is applied to sound based on distance listener along the negative + * sound direction axis from sound source. + * @param attenuation defined by pairs of (distance,gain-scale-factor) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setBackDistanceGain(Point2f[] attenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound0")); + + ((ConeSoundRetained)this.retained).setBackDistanceGain(attenuation); + } + + /** + * Sets this sound's back distance gain attenuation as separate arrays. + * @param distance array of monotonically-increasing floats + * @param gain array of non-negative scale factors + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setBackDistanceGain(float[] distance, float[] gain) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound0")); + + ((ConeSoundRetained)this.retained).setBackDistanceGain(distance, gain); + } + + /** + * Gets this sound's elliptical distance attenuation. The + * attenuation values are copied into the specified arrays. + * The arrays must be large enough to hold all of the + * forward distances and backward distances attenuation values. + * The individual array elements must be allocated by the + * caller. The Point2f x,y values are defined as follows: + * x is the distance, y is the gain. + * @param frontAttenuation arrays containing forward distances + * attenuation pairs + * @param backAttenuation arrays containing backward distances + * attenuation pairs + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDistanceGain(Point2f[] frontAttenuation, + Point2f[] backAttenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound2")); + + ((ConeSoundRetained)this.retained).getDistanceGain( + frontAttenuation, backAttenuation); + } + + /** + * Gets this sound's elliptical distance gain attenuation values in + * separate arrays. The arrays must be large enough to hold all + * of the values. + * @param frontDistance array of float distances along the sound axis + * @param frontGain array of non-negative scale factors associated with + * front distances + * @param backDistance array of float negative distances along the sound + * axis + * @param backGain array of non-negative scale factors associated with + * back distances + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDistanceGain(float[] frontDistance, float[] frontGain, + float[] backDistance, float[] backGain) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound10")); + + ((ConeSoundRetained)this.retained).getDistanceGain( + frontDistance, frontGain, backDistance, backGain); + } + + /** + * Sets this sound's direction from the vector provided. + * @param direction the new direction + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDirection(Vector3f direction) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound3")); + + ((ConeSoundRetained)this.retained).setDirection(direction); + } + + /** + * Sets this sound's direction from the three values provided. + * @param x the new x direction + * @param y the new y direction + * @param z the new z direction + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDirection(float x, float y, float z) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound3")); + + ((ConeSoundRetained)this.retained).setDirection(x,y,z); + } + + /** + * Retrieves this sound's direction and places it in the + * vector provided. + * @param direction axis of cones; 'direction' of sound + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDirection(Vector3f direction) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound5")); + + ((ConeSoundRetained)this.retained).getDirection(direction); + } + + /** + * Sets this sound's angular gain attenuation (not including filter). + * In this form of setAngularAttenuation, only the angular distances + * and angular gain scale factors pairs are given. The filter values for + * these tuples are implicitly set to Sound.NO_FILTER. + * @param attenuation array containing angular distance and gain + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAngularAttenuation(Point2f[] attenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANGULAR_ATTENUATION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound6")); + + ((ConeSoundRetained)this.retained).setAngularAttenuation(attenuation); + } + + /** + * In the second form of setAngularAttenuation, an array of all three values + * is supplied. + * @param attenuation array containing angular distance, gain, and filter + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAngularAttenuation(Point3f[] attenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANGULAR_ATTENUATION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound6")); + + ((ConeSoundRetained)this.retained).setAngularAttenuation(attenuation); + } + + /** + * Sets angular attenuation including gain and filter using separate arrays. + * The third form of setAngularAttenuation accepts three separate arrays for + * these angular attenuation values. These arrays should be of the same + * length. If the angularGain or filtering array length is greater than + * angularDistance array length, the array elements beyond the length of + * the angularDistance array are ignored. If the angularGain or filtering + * array is shorter than the angularDistance array, the last value of the + * short array is repeated to fill an array of length equal to + * angularDistance array. + * @param distance array containing angular distance + * @param gain array containing angular gain attenuation + * @param filter array containing angular low-pass frequency cutoff values + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAngularAttenuation(float[] distance, float[] gain, + float[] filter) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANGULAR_ATTENUATION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound6")); + + ((ConeSoundRetained)this.retained).setAngularAttenuation(distance, + gain, filter); + } + + /** + * Retrieves angular attenuation array length. + * All arrays are forced to same size. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getAngularAttenuationLength() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANGULAR_ATTENUATION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound9")); + + return (((ConeSoundRetained)this.retained).getAngularAttenuationLength()); + } + + /** + * Copies the array of attenuation values from this sound, including + * gain and filter, into the specified array. The array must be + * large enough to hold all of the points. The individual array + * elements must be allocated by the caller. The Point3f x,y,z values + * are defined as follows: x is the angular distance, y is + * the angular gain attenuation, and z is the frequency + * cutoff. + * @param attenuation the array to receive the attenuation values + * applied to gain when listener is between cones + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getAngularAttenuation(Point3f[] attenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANGULAR_ATTENUATION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound9")); + + ((ConeSoundRetained)this.retained).getAngularAttenuation(attenuation); + } + + /** + * Copies the array of attenuation values from this sound, + * including gain and filter, into the separate arrays. + * The arrays must be large enough to hold all of the values. + * @param distance array containing angular distance + * @param gain array containing angular gain attenuation + * @param filter array containing angular low-pass frequency cutoff values + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getAngularAttenuation(float[] distance, float[] gain, + float[] filter) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANGULAR_ATTENUATION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ConeSound9")); + + ((ConeSoundRetained)this.retained).getAngularAttenuation(distance, + gain, filter); + } + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + ConeSound c = new ConeSound(); + c.duplicateNode(this, forceDuplicate); + return c; + } + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @exception ClassCastException if originalNode is not an instance of + * <code>ConeSound</code> + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + checkDuplicateNode(originalNode, forceDuplicate); + } + + + /** + * Copies all ConeSound information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + ConeSoundRetained orgRetained = (ConeSoundRetained)originalNode.retained; + ConeSoundRetained thisRetained = (ConeSoundRetained)this.retained; + + // front distance gain & attenuation is set in super + // set back distance gain only + int len = orgRetained.getDistanceGainLength(); + float distance[] = new float[len]; + float gain[] = new float[len]; + orgRetained.getDistanceGain(null, null,distance, gain); + thisRetained.setBackDistanceGain(distance, gain); + + Vector3f v = new Vector3f(); + orgRetained.getDirection(v); + thisRetained.setDirection(v); + + len = orgRetained.getAngularAttenuationLength(); + distance = gain = null; + float angle[] = new float[len]; + float angularGain[] = new float[len]; + float frequencyCutoff[] = new float[len]; + + orgRetained.getAngularAttenuation(angle, angularGain, + frequencyCutoff); + + thisRetained.setAngularAttenuation(angle, angularGain, frequencyCutoff); + } + +} diff --git a/src/classes/share/javax/media/j3d/ConeSoundRetained.java b/src/classes/share/javax/media/j3d/ConeSoundRetained.java new file mode 100644 index 0000000..4b450ef --- /dev/null +++ b/src/classes/share/javax/media/j3d/ConeSoundRetained.java @@ -0,0 +1,641 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Vector3f; +import javax.vecmath.Vector3d; +import javax.vecmath.Point2f; +import javax.vecmath.Point3f; + + + +/** + * A ConeSoundRetained node defines a point sound source located at some + * location + * in space whose amplitude is constrained not only by maximum and minimum + * amplitude + * spheres but by two concentric cone volumes directed down an vector radiating + * from the sound's location. + */ + +class ConeSoundRetained extends PointSoundRetained { + /** + * The Cone Sound's direction vector. This is the cone axis. + */ + Vector3f direction = new Vector3f(0.0f, 0.0f, 1.0f); + + // The transformed direction of this sound + Vector3f xformDirection = new Vector3f(0.0f, 0.0f, 1.0f); + + // Sound's gain is attenuated for listener locations off-angle from + // the source source direction. + // This can be set of three numbers: + // angular distance in radians + // gain scale factor + // filtering (currently the only filtering supported is lowpass) + + // For now the only supported filterType will be LOW_PASS frequency cutoff. + // At some time full FIR filtering will be supported. + static final int NO_FILTERING = -1; + static final int LOW_PASS = 1; + + // Pairs of distances and gain scale factors that define piecewise linear + // gain BACK attenuation between each pair. + // These are used for defining elliptical attenuation regions. + float[] backAttenuationDistance = null; + float[] backAttenuationGain = null; + + float[] angularDistance = {0.0f, ((float)(Math.PI) * 0.5f)}; + float[] angularGain = {1.0f, 0.0f}; + int filterType = NO_FILTERING; + float[] frequencyCutoff = {Sound.NO_FILTER, Sound.NO_FILTER}; + + ConeSoundRetained() { + this.nodeType = NodeRetained.CONESOUND; + } + + // ********************* + // + // Distance Gain methods + // + // ********************* + + /** + * Sets this sound's distance gain elliptical attenuation - + * where gain scale factor is applied to sound based on distance listener + * is from sound source. + * @param frontAttenuation defined by pairs of (distance,gain-scale-factor) + * @param backAttenuation defined by pairs of (distance,gain-scale-factor) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + void setDistanceGain(Point2f[] frontAttenuation, + Point2f[] backAttenuation ) { + + this.setDistanceGain(frontAttenuation); + this.setBackDistanceGain(backAttenuation); + } + + /** + * Sets this sound's distance gain attenuation as an array of Point2fs. + * @param frontDistance array of monotonically-increasing floats + * @param frontGain array of non-negative scale factors + * @param backDistance array of monotonically-increasing floats + * @param backGain array of non-negative scale factors + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + void setDistanceGain(float[] frontDistance, float[] frontGain, + float[] backDistance, float[] backGain) { + this.setDistanceGain(frontDistance, frontGain); + this.setBackDistanceGain(backDistance, backGain); + } + + /** + * Sets this sound's back distance gain attenuation - where gain scale + * factor is applied to sound based on distance listener along the negative + * sound direction axis from sound source. + * @param attenuation defined by pairs of (distance,gain-scale-factor) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + void setBackDistanceGain(Point2f[] attenuation) + { + // if attenuation array null set both attenuation components to null + if (attenuation == null) { + this.backAttenuationDistance = null; + this.backAttenuationGain = null; + } + else { + int attenuationLength = attenuation.length; + if (attenuationLength == 0) { + this.backAttenuationDistance = null; + this.backAttenuationGain = null; + } + else { + this.backAttenuationDistance = new float[attenuationLength]; + this.backAttenuationGain = new float[attenuationLength]; + for (int i = 0; i < attenuationLength; i++) { + this.backAttenuationDistance[i] = attenuation[i].x; + this.backAttenuationGain[i] = attenuation[i].y; + } + } + } + dispatchAttribChange(BACK_DISTANCE_GAIN_DIRTY_BIT, attenuation); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Sets this sound's back distance gain attenuation as an array of Point2fs. + * @param distance array of monotonically-increasing floats + * @param gain array of non-negative scale factors + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + void setBackDistanceGain(float[] distance, float[] gain) + { + int distanceLength = 0; + // if distance or gain arrays are null then treat both as null + if (distance == null || gain == null) { + this.backAttenuationDistance = null; + this.backAttenuationGain = null; + } + else { + // now process the back attenuation values + int gainLength = gain.length; + distanceLength = distance.length; + if (distanceLength == 0 || gainLength == 0) { + this.backAttenuationDistance = null; + this.backAttenuationGain = null; + } + else { + this.backAttenuationDistance = new float[distanceLength]; + this.backAttenuationGain = new float[distanceLength]; + // Copy the distance array into nodes field + System.arraycopy(distance, 0, this.backAttenuationDistance, + 0, distanceLength); + // Copy the gain array an array of same length as the distance array + if (distanceLength <= gainLength) { + System.arraycopy(gain, 0, this.backAttenuationGain, + 0, distanceLength); + } + else { + System.arraycopy(gain, 0, this.backAttenuationGain, 0, gainLength); + // Extend gain array to length of distance array + // replicate last gain values. + for (int i=gainLength; i< distanceLength; i++) { + this.backAttenuationGain[i] = gain[gainLength - 1]; + } + } + } + } + + Point2f [] attenuation = new Point2f[distanceLength]; + for (int i=0; i<distanceLength; i++) { + attenuation[i] = new Point2f(this.backAttenuationDistance[i], + this.backAttenuationGain[i]); + } + dispatchAttribChange(BACK_DISTANCE_GAIN_DIRTY_BIT, attenuation); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Gets this sound's elliptical distance attenuation + * @param frontAttenuation arrays containing forward distances attenuation pairs + * @param backAttenuation arrays containing backward distances attenuation pairs + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + void getDistanceGain(Point2f[] frontAttenuation, + Point2f[] backAttenuation) { + this.getDistanceGain(frontAttenuation); + this.getBackDistanceGain(backAttenuation); + } + + /** + * Gets this sound's elliptical distance gain attenuation values in separate arrays + * @param frontDistance array of float distances along the sound axis + * @param fronGain array of non-negative scale factors associated with front distances + * @param backDistance array of float negative distances along the sound axis + * @param backGain array of non-negative scale factors associated with back distances + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + void getDistanceGain(float[] frontDistance, float[] frontGain, + float[] backDistance, float[] backGain) { + this.getDistanceGain( frontDistance, frontGain); + this.getBackDistanceGain( backDistance, backGain); + } + + /** + * Retieves sound's back distance attenuation + * Put the contents of the two separate distance and gain arrays into + * an array of Point2f. + * @param attenuation containing distance attenuation pairs + */ + void getBackDistanceGain(Point2f[] attenuation) { + // Write into arrays passed in, don't do a new + if (attenuation == null) + return; + if (this.backAttenuationDistance == null || + this.backAttenuationGain == null) + return; + // These two array length should be the same + // can assume lengths are non-zero + int distanceLength = this.backAttenuationDistance.length; + int attenuationLength = attenuation.length; + if (distanceLength < attenuationLength) + distanceLength = attenuationLength; + for (int i=0; i< distanceLength; i++) { + attenuation[i].x = this.backAttenuationDistance[i]; + attenuation[i].y = this.backAttenuationGain[i]; + } + } + + /** + * Retieves this sound's back attenuation distance and gain arrays, + * returned in separate arrays. + * @param distance array of monotonically-increasing floats. + * @param gain array of amplitude scale factors associated with distances. + */ + void getBackDistanceGain(float[] distance, float[] gain) { + // write into arrays passed in, don't do a new + if (distance == null || gain == null) + return; + if (this.backAttenuationDistance == null || + this.backAttenuationGain == null) + return; + // backAttenuationDistance and backAttenuationGain array length should + // be the same + // can assume length is non-zero + int attenuationLength = this.backAttenuationDistance.length; + int distanceLength = distance.length; + if (attenuationLength > distanceLength) + attenuationLength = distanceLength; + System.arraycopy(this.backAttenuationDistance, 0, distance, 0, attenuationLength); + attenuationLength = this.backAttenuationGain.length; + int gainLength = gain.length; + if (attenuationLength > gainLength) + attenuationLength = gainLength; + System.arraycopy(this.backAttenuationGain, 0, gain, 0, attenuationLength); + } + + + // ********************* + // + // Direction Methods + // + // ********************* + + /** + * Sets this sound's direction from the vector provided. + * @param direction the new direction + */ + void setDirection(Vector3f direction) { + if (staticTransform != null) { + staticTransform.transform.transform(direction, this.direction); + } else { + this.direction.set(direction); + } + dispatchAttribChange(DIRECTION_DIRTY_BIT, + (new Vector3f(this.direction))); + + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Sets this sound's direction from the three values provided. + * @param x the new x direction + * @param y the new y direction + * @param z the new z direction + */ + void setDirection(float x, float y, float z) { + direction.x = x; + direction.y = y; + direction.z = z; + if (staticTransform != null) { + staticTransform.transform.transform(direction); + } + dispatchAttribChange(DIRECTION_DIRTY_BIT, (new Vector3f(direction))); + + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + + /** + * Retrieves this sound's direction and places it in the + * vector provided. + * @return direction vector (axis of cones) + */ + void getDirection(Vector3f direction) + { + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + invTransform.transform(this.direction, direction); + } else { + direction.set(this.direction); + } + } + + void getXformDirection(Vector3f direction) + { + direction.set(this.xformDirection); + } + + + // *************************** + // + // Angular Attenuation + // + // *************************** + + /** + * Sets this sound's angular gain attenuation (not including filter) + * @param attenuation array containing angular distance and gain + */ + void setAngularAttenuation(Point2f[] attenuation) { + int attenuationLength = 0; + this.filterType = NO_FILTERING; + if (attenuation == null) { + this.angularDistance = null; + this.angularGain = null; + } + else { + attenuationLength = attenuation.length; + if (attenuationLength == 0) { + this.angularDistance = null; + this.angularGain = null; + } + else { + this.angularDistance = new float[attenuationLength]; + this.angularGain = new float[attenuationLength]; + for (int i = 0; i < attenuationLength; i++) { + this.angularDistance[i] = attenuation[i].x; + this.angularGain[i] = attenuation[i].y; + } + } // lengths not zero + } // arrays not null + Point3f [] attenuation3f = new Point3f[attenuationLength]; + for (int i=0; i<attenuationLength; i++) { + attenuation3f[i] = new Point3f(this.angularDistance[i], + this.angularGain[i], + Sound.NO_FILTER); + } + dispatchAttribChange(ANGULAR_ATTENUATION_DIRTY_BIT, attenuation3f); + + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Sets this sound's angular attenuation including both gain and filter. + * @param attenuation array containing angular distance, gain and filter + */ + void setAngularAttenuation(Point3f[] attenuation) { + if (attenuation == null) { + this.angularDistance = null; + this.angularGain = null; + this.frequencyCutoff = null; + this.filterType = NO_FILTERING; + } + else { + int attenuationLength = attenuation.length; + if (attenuationLength == 0) { + this.angularDistance = null; + this.angularGain = null; + this.frequencyCutoff = null; + this.filterType = NO_FILTERING; + } + else { + this.angularDistance = new float[attenuationLength]; + this.angularGain = new float[attenuationLength]; + this.frequencyCutoff = new float[attenuationLength]; + this.filterType = LOW_PASS; + for (int i = 0; i < attenuationLength; i++) { + this.angularDistance[i] = attenuation[i].x; + this.angularGain[i] = attenuation[i].y; + this.frequencyCutoff[i] = attenuation[i].z; + } + } // lengths not zero + } // arrays not null + dispatchAttribChange(ANGULAR_ATTENUATION_DIRTY_BIT, attenuation); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Sets angular attenuation including gain and filter using separate arrays + * @param distance array containing angular distance + * @param filter array containing angular low-pass frequency cutoff values + */ + void setAngularAttenuation(float[] distance, float[] gain, float[] filter) { + int distanceLength = 0; + if (distance == null || gain == null || filter == null) { + this.angularDistance = null; + this.angularGain = null; + this.frequencyCutoff = null; + this.filterType = NO_FILTERING; + } + else { + distanceLength = distance.length; + int gainLength = gain.length; + if (distanceLength == 0 || gainLength == 0) { + this.angularDistance = null; + this.angularGain = null; + this.frequencyCutoff = null; + this.filterType = NO_FILTERING; + } + else { + int filterLength = filter.length; + this.angularDistance = new float[distanceLength]; + this.angularGain = new float[distanceLength]; + this.frequencyCutoff = new float[distanceLength]; + // Copy the distance array into nodes field + System.arraycopy(distance, 0, this.angularDistance, 0, distanceLength); + // Copy the gain array an array of same length as the distance array + if (distanceLength <= gainLength) { + System.arraycopy(gain, 0, this.angularGain, 0, distanceLength); + } + else { + System.arraycopy(gain, 0, this.angularGain, 0, gainLength); + /** + * Extend gain array to length of distance array by + * replicate last gain values. + */ + for (int i=gainLength; i< distanceLength; i++) { + this.angularGain[i] = gain[gainLength - 1]; + } + } + // Copy the filter array an array of same length as the distance array + if (filterLength == 0) + this.filterType = NO_FILTERING; + else { + this.filterType = LOW_PASS; + if (distanceLength <= filterLength) { + System.arraycopy(filter, 0, this.frequencyCutoff,0, distanceLength); + } + else { + System.arraycopy(filter, 0, this.frequencyCutoff, 0, filterLength); + // Extend filter array to length of distance array by + // replicate last filter values. + for (int i=filterLength; i< distanceLength; i++) { + this.frequencyCutoff[i] = filter[filterLength - 1]; + } + } + } + } // length not zero + } // arrays not null + Point3f [] attenuation = new Point3f[distanceLength]; + for (int i=0; i<distanceLength; i++) { + if (this.filterType != NO_FILTERING) { + attenuation[i] = new Point3f(this.angularDistance[i], + this.angularGain[i], + this.frequencyCutoff[i]); + } + else { + attenuation[i] = new Point3f(this.angularDistance[i], + this.angularGain[i], + Sound.NO_FILTER); + } + } + dispatchAttribChange(ANGULAR_ATTENUATION_DIRTY_BIT, attenuation); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves angular attenuation array length. + * All arrays are forced to same size + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + int getAngularAttenuationLength() { + + if (angularDistance == null) + return 0; + else + return (this.angularDistance.length); + } + + /** + * Retrieves angular attenuation including gain and filter in a single array + * @param attenuation applied to gain when listener is between cones + */ + void getAngularAttenuation(Point3f[] attenuation) { + /// use attenuation array allocated by user - don't new it + // The three angular attenuation arrays length should be the same + if (this.angularDistance == null || this.angularGain == null) + return; + if (attenuation == null) + return; + int distanceLength = this.angularDistance.length; + if (attenuation.length < distanceLength) + distanceLength = attenuation.length; + for (int i=0; i< distanceLength; i++) { + attenuation[i].x = this.angularDistance[i]; + attenuation[i].y = this.angularGain[i]; + if (filterType == NO_FILTERING || this.frequencyCutoff == null) + attenuation[i].z = Sound.NO_FILTER; + else if (filterType == LOW_PASS) + attenuation[i].z = this.frequencyCutoff[i]; + } + } + + /** + * Retrieves angular attenuation including gain and filter + * returned as separate arrays + * @param distance array containing angular distance + * @param gain array containing angular gain attenuation + * @param filter array containing angular low-pass frequency cutoff values + */ + void getAngularAttenuation(float[] distance, float[] gain, float[] filter) { + // use attenuation array allocated by user - don't new it + if (distance == null || gain == null || filter == null) + return; + if (this.angularDistance == null || this.angularGain == null) + return; + int distanceLength = this.angularDistance.length; + if (distance.length < distanceLength) + distanceLength = distance.length; + System.arraycopy(this.angularDistance, 0, distance, 0, distanceLength); + + int gainLength = this.angularGain.length; + if (gain.length < gainLength) + gainLength = gain.length; + System.arraycopy(this.angularGain, 0, gain, 0, gainLength); + + int filterLength = 0; + if (this.frequencyCutoff == null || filterType == NO_FILTERING) + filterLength = filter.length; + else { + filterLength = this.frequencyCutoff.length; + if (filter.length < filterLength) + filterLength = filter.length; + } + if (filterType == NO_FILTERING || this.frequencyCutoff == null) { + for (int i=0; i< filterLength; i++) + filter[i] = Sound.NO_FILTER; + } + if (filterType == LOW_PASS) { + System.arraycopy(this.frequencyCutoff, 0, filter,0, filterLength); + } + } + + + /** + * This updates the Direction fields of cone sound. + * + * Neither Angular gain Attenuation and Filtering fields, nor + * back distance gain not maintained in mirror object + */ + void updateMirrorObject(Object[] objs) { + if (debugFlag) + debugPrint("PointSoundRetained:updateMirrorObj()"); + Transform3D trans = null; + int component = ((Integer)objs[1]).intValue(); + int numSnds = ((Integer)objs[2]).intValue(); + SoundRetained[] mSnds = (SoundRetained[]) objs[3]; + if (component == -1) { + // update every field + initMirrorObject(((ConeSoundRetained)objs[2])); + return; + } + if ((component & DIRECTION_DIRTY_BIT) != 0) { + for (int i = 0; i < numSnds; i++) { + ConeSoundRetained cone = (ConeSoundRetained)mSnds[i]; + cone.direction = (Vector3f)objs[4]; + cone.getLastLocalToVworld().transform(cone.direction, + cone.xformDirection); + cone.xformDirection.normalize(); + } + } + // call the parent's mirror object update routine + super.updateMirrorObject(objs); + } + + synchronized void initMirrorObject(ConeSoundRetained ms) { + super.initMirrorObject(ms); + ms.direction.set(this.direction); + ms.xformDirection.set(this.xformDirection); + } + + // Called on the mirror object + void updateTransformChange() { + Transform3D lastLocalToVworld = getLastLocalToVworld(); + + super.updateTransformChange(); + lastLocalToVworld.transform(direction, xformDirection); + xformDirection.normalize(); + // set flag looked at by Scheduler to denote Transform change + // this flag will force resneding transformed direction to AudioDevice + if (debugFlag) + debugPrint("ConeSound xformDirection is (" + xformDirection.x + ", " + + xformDirection.y + ", "+ xformDirection.z + ")"); + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + xform.transform.transform(direction); + } +} diff --git a/src/classes/share/javax/media/j3d/DanglingReferenceException.java b/src/classes/share/javax/media/j3d/DanglingReferenceException.java new file mode 100644 index 0000000..3f24868 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DanglingReferenceException.java @@ -0,0 +1,44 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * During a <code>cloneTree</code> call an updated reference was requested + * for a node that did not get cloned. This happens when a sub-graph is + * duplicated via <code>cloneTree</code> and has at least one Leaf node + * that contains a reference to a Node that has no corresponding node in + * the cloned sub-graph. This results in two Leaf nodes wanting to share + * access to the same Node. + * <P> + * If dangling references are to be allowed during the cloneTree call, + * <code>cloneTree</code> should be called with the + * <code>allowDanglingReferences</code> parameter set to <code>true</code>. + * @see Node#cloneTree + */ +public class DanglingReferenceException extends RuntimeException { + + /** + * Create the exception object with default values. + */ + public DanglingReferenceException() { + } + + /** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public DanglingReferenceException(String str) { + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/DecalGroup.java b/src/classes/share/javax/media/j3d/DecalGroup.java new file mode 100644 index 0000000..2bd6150 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DecalGroup.java @@ -0,0 +1,78 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The DecalGroup node is an ordered group node used for defining decal + * geometry on top of other geometry. The DecalGroup node specifies that + * its children should be rendered in index order and that they generate + * coplanar objects. Examples of this include: painted decals or text on + * surfaces, a checkerboard layered on top of a table, etc. + * <p> + * The first child, at index 0, defines the surface on top of which all + * other children are rendered. The geometry of this child must encompass + * all other children, otherwise incorrect rendering may result. The + * polygons contained within each of the children must be facing the same + * way. If the polygons defined by the first child are front facing, then + * all other surfaces should be front facing. In this case, the polygons + * are rendered in order. The renderer can use knowledge of the coplanar + * nature of the surfaces to avoid + * Z-buffer collisions. If the main surface is back facing then all other + * surfaces should be back facing, and need not be rendered (even if back + * face culling is disabled). + * <p> + * Note that using the DecalGroup node does not guarantee that Z-buffer + * collisions are avoided. An implementation of Java 3D may fall back to + * treating DecalGroup node as an OrderedGroup node. + */ +public class DecalGroup extends OrderedGroup { + + /** + * Constructs and initializes a new DecalGroup node object. + */ + public DecalGroup() { + } + + + /** + * Creates the retained mode DecalGroupRetained object that this + * DecalGroup component object will point to. + */ + void createRetained() { + this.retained = new DecalGroupRetained(); + this.retained.setSource(this); + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + DecalGroup dg = new DecalGroup(); + dg.duplicateNode(this, forceDuplicate); + return dg; + } + +} diff --git a/src/classes/share/javax/media/j3d/DecalGroupRetained.java b/src/classes/share/javax/media/j3d/DecalGroupRetained.java new file mode 100644 index 0000000..3b899d4 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DecalGroupRetained.java @@ -0,0 +1,20 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class DecalGroupRetained extends OrderedGroupRetained { + + DecalGroupRetained() { + this.nodeType = NodeRetained.DECALGROUP; + } +} diff --git a/src/classes/share/javax/media/j3d/DefaultRenderMethod.java b/src/classes/share/javax/media/j3d/DefaultRenderMethod.java new file mode 100644 index 0000000..6a6f95b --- /dev/null +++ b/src/classes/share/javax/media/j3d/DefaultRenderMethod.java @@ -0,0 +1,74 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The RenderMethod interface is used to create various ways to render + * different geometries. + */ + +class DefaultRenderMethod implements RenderMethod { + + boolean geometryIsLocked = false; + + /** + * The actual rendering code for this RenderMethod + */ + public boolean render(RenderMolecule rm, Canvas3D cv, int pass, + RenderAtomListInfo ra, int dirtyBits) { + + boolean isVisible = false; // True if any of the RAs is visible. + + while (ra != null) { + if (cv.ra == ra.renderAtom) { + if (cv.raIsVisible) { + cv.updateState(pass, dirtyBits); + ra.geometry().execute(cv, ra.renderAtom, + rm.isNonUniformScale, + rm.useAlpha, rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin. + ignoreVertexColors, + pass); + isVisible = true; + } + } + else { + if (ra.renderAtom.localeVwcBounds.intersect(cv.viewFrustum)) { + cv.raIsVisible = true; + cv.updateState(pass, dirtyBits); + ra.geometry().execute(cv, ra.renderAtom, rm.isNonUniformScale, + rm.useAlpha, rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin. + ignoreVertexColors, + pass); + isVisible = true; + } + else { + cv.raIsVisible = false; + } + cv.ra = ra.renderAtom; + } + ra = ra.next; + } + + return isVisible; + + } + + + +} diff --git a/src/classes/share/javax/media/j3d/DepthComponent.java b/src/classes/share/javax/media/j3d/DepthComponent.java new file mode 100644 index 0000000..cc68c47 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DepthComponent.java @@ -0,0 +1,67 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Abstract base class that defines a 2D array of depth (Z) values. + */ + +public abstract class DepthComponent extends NodeComponent { + /** + * Specifies that this DepthComponent object allows reading its + * size component information (width and height). + */ + public static final int + ALLOW_SIZE_READ = CapabilityBits.DEPTH_COMPONENT_ALLOW_SIZE_READ; + + /** + * Specifies that this DepthComponent object allows reading its + * depth data component information. + */ + public static final int + ALLOW_DATA_READ = CapabilityBits.DEPTH_COMPONENT_ALLOW_DATA_READ; + + /** + * default constructor + */ + DepthComponent() { + } + + /** + * Retrieves the width of this depth component object. + * @return the width of the array of depth values + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getWidth() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("DepthComponent0")); + return ((DepthComponentRetained)this.retained).getWidth(); + } + + /** + * Retrieves the height of this depth component object. + * @return the height of the array of depth values + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getHeight() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("DepthComponent0")); + return ((DepthComponentRetained)this.retained).getHeight(); + } + + +} diff --git a/src/classes/share/javax/media/j3d/DepthComponentFloat.java b/src/classes/share/javax/media/j3d/DepthComponentFloat.java new file mode 100644 index 0000000..3e79efc --- /dev/null +++ b/src/classes/share/javax/media/j3d/DepthComponentFloat.java @@ -0,0 +1,117 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A 2D array of depth (Z) values in floating point format in the range [0,1]. + * A value of 0.0 indicates the closest Z value to the user while a value of + * 1.0 indicates the farthest Z value. + */ + +public class DepthComponentFloat extends DepthComponent { + + /** + * Package scope defualt constructor used by cloneNodeComponent + */ + DepthComponentFloat() { + } + + /** + * Constructs a new floating-point depth (z-buffer) component object with + * the specified width and height. + * @param width the width of the array of depth values + * @param height the height of the array of depth values + */ + public DepthComponentFloat(int width, int height) { + ((DepthComponentFloatRetained)this.retained).initialize(width, height); + } + + /** + * Copies the specified depth data to this object. + * @param depthData array of floats containing the depth data + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + public void setDepthData(float[] depthData) { + checkForLiveOrCompiled(); + ((DepthComponentFloatRetained)this.retained).setDepthData(depthData); + } + + /** + * Copies the depth data from this object to the specified array. + * The array must be large enough to hold all of the floats. + * @param depthData array of floats that will receive a copy of + * the depth data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDepthData(float[] depthData) { + if (isLiveOrCompiled()) + if (!this.getCapability(DepthComponent.ALLOW_DATA_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("DepthComponentFloat0")); + ((DepthComponentFloatRetained)this.retained).getDepthData(depthData); + } + + /** + * Creates a retained mode DepthComponentFloatRetained object that this + * DepthComponentFloat component object will point to. + */ + void createRetained() { + this.retained = new DepthComponentFloatRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + DepthComponentFloatRetained rt = (DepthComponentFloatRetained) retained; + DepthComponentFloat d = new DepthComponentFloat(rt.width, + rt.height); + d.duplicateNodeComponent(this); + return d; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, + forceDuplicate); + // width, height is copied in cloneNode before + int len = getWidth()*getHeight(); + float f[] = new float[len]; + + ((DepthComponentFloatRetained) originalNodeComponent.retained).getDepthData(f); + ((DepthComponentFloatRetained) retained).setDepthData(f); + } + + +} diff --git a/src/classes/share/javax/media/j3d/DepthComponentFloatRetained.java b/src/classes/share/javax/media/j3d/DepthComponentFloatRetained.java new file mode 100644 index 0000000..c985414 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DepthComponentFloatRetained.java @@ -0,0 +1,74 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * A 2D array of depth (Z) values in floating point format in the range [0,1]. + * A value of 0.0 indicates the closest Z value to the user while a value of + * 1.0 indicates the farthest Z value. + */ + +class DepthComponentFloatRetained extends DepthComponentRetained { + float depthData[]; + + /** + * Constructs a new floating-point depth (z-buffer) component object with + * the specified width and height. + * @param width the width of the array of depth values + * @param height the height of the array of depth values + */ + void initialize(int width, int height) { + type = DEPTH_COMPONENT_TYPE_FLOAT; + depthData = new float[width * height]; + this.width = width; + this.height = height; + } + + /** + * Copies the specified depth data to this object. + * @param depthData array of floats containing the depth data + */ + void setDepthData(float[] depthData) { + int i; + for (i = 0; i < depthData.length; i++) + this.depthData[i] = depthData[i]; + } + + + /** + * Copies the depth data from this object to the specified array. + * @param depthData array of floats that will receive a copy of + * the depth data + */ + void getDepthData(float[] depthData) { + int i; + for (i = 0; i < this.depthData.length; i++) + depthData[i] = this.depthData[i]; + } + + /* + * retrieve depth data from input buffer + */ + final void retrieveDepth(float[] buf, int wRead, int hRead) { + int srcOffset, dstOffset, i; + + // Yup -> Ydown + for (srcOffset = (hRead - 1) * wRead, dstOffset = 0, + i = 0; i < hRead; i++, + srcOffset -= wRead, dstOffset += width) { + + System.arraycopy(buf, srcOffset, depthData, dstOffset, wRead); + } + } +} diff --git a/src/classes/share/javax/media/j3d/DepthComponentInt.java b/src/classes/share/javax/media/j3d/DepthComponentInt.java new file mode 100644 index 0000000..e30b977 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DepthComponentInt.java @@ -0,0 +1,115 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A 2D array of depth (Z) values in integer format. Values are in the + * range [0,(2**N)-1], where N is the pixel depth of the Z buffer. + */ + +public class DepthComponentInt extends DepthComponent { + + /** + * Package scope default constructor + */ + DepthComponentInt() { + } + + /** + * Constructs a new integer depth (z-buffer) component object with the + * specified width and height. + * @param width the width of the array of depth values + * @param height the height of the array of depth values + */ + public DepthComponentInt(int width, int height) { + ((DepthComponentIntRetained)this.retained).initialize(width, height); + } + + /** + * Copies the specified depth data to this object. + * @param depthData array of ints containing the depth data + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + public void setDepthData(int[] depthData) { + checkForLiveOrCompiled(); + ((DepthComponentIntRetained)this.retained).setDepthData(depthData); + } + + /** + * Copies the depth data from this object to the specified array. + * The array must be large enough to hold all of the ints. + * @param depthData array of ints that will receive a copy of + * the depth data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDepthData(int[] depthData) { + if (isLiveOrCompiled()) + if (!this.getCapability(DepthComponent.ALLOW_DATA_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("DepthComponentInt0")); + ((DepthComponentIntRetained)this.retained).getDepthData(depthData); + } + + /** + * Creates a retained mode DepthComponentIntRetained object that this + * DepthComponentInt component object will point to. + */ + void createRetained() { + this.retained = new DepthComponentIntRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + DepthComponentIntRetained rt = (DepthComponentIntRetained) retained; + DepthComponentInt d = new DepthComponentInt(rt.width, + rt.height); + d.duplicateNodeComponent(this); + return d; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + // width, height is copied in cloneNode before + int len = getWidth()*getHeight(); + int d[] = new int[len]; + ((DepthComponentIntRetained) originalNodeComponent.retained).getDepthData(d); + ((DepthComponentIntRetained) retained).setDepthData(d); + } + + +} diff --git a/src/classes/share/javax/media/j3d/DepthComponentIntRetained.java b/src/classes/share/javax/media/j3d/DepthComponentIntRetained.java new file mode 100644 index 0000000..8873691 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DepthComponentIntRetained.java @@ -0,0 +1,74 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * A 2D array of depth (Z) values in integer format. Values are in the + * range [0,(2**N)-1], where N is the pixel depth of the Z buffer. + */ + +class DepthComponentIntRetained extends DepthComponentRetained { + int depthData[]; + + /** + * Constructs a new integer depth (z-buffer) component object with the + * specified width and height. + * @param width the width of the array of depth values + * @param height the height of the array of depth values + */ + void initialize(int width, int height) { + type = DEPTH_COMPONENT_TYPE_INT; + depthData = new int[width * height]; + this.width = width; + this.height = height; + } + + /** + * Copies the specified depth data to this object. + * @param depthData array of ints containing the depth data + */ + void setDepthData(int[] depthData) { + int i; + for (i = 0; i < depthData.length; i++) + this.depthData[i] = depthData[i]; + } + + + /** + * Copies the depth data from this object to the specified array. + * @param depthData array of ints that will receive a copy of + * the depth data + */ + void getDepthData(int[] depthData) { + int i; + + for (i = 0; i < this.depthData.length; i++) + depthData[i] = this.depthData[i]; + } + + /** + * retrieve depth data from input buffer + */ + final void retrieveDepth(int[] buf, int wRead, int hRead) { + int srcOffset, dstOffset, i; + + // Yup -> Ydown + for (srcOffset = (hRead - 1) * wRead, dstOffset = 0, + i = 0; i < hRead; i++, + srcOffset -= wRead, dstOffset += width) { + + System.arraycopy(buf, srcOffset, depthData, dstOffset, wRead); + } + } +} diff --git a/src/classes/share/javax/media/j3d/DepthComponentNative.java b/src/classes/share/javax/media/j3d/DepthComponentNative.java new file mode 100644 index 0000000..005495f --- /dev/null +++ b/src/classes/share/javax/media/j3d/DepthComponentNative.java @@ -0,0 +1,107 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A 2D array of depth (Z) values stored in the most efficient format for a + * particular device. Values are not accessible by the user and may only be + * used to read the Z values and subsequently write them back. + */ + +public class DepthComponentNative extends DepthComponent { + /** + * Package scope defualt constructor for use by cloneNodeComponent + */ + DepthComponentNative() { + } + + /** + * Constructs a new native depth (z-buffer) component object with the + * specified width and height. + * @param width the width of the array of depth values + * @param height the height of the array of depth values + */ + public DepthComponentNative(int width, int height) { + ((DepthComponentNativeRetained)this.retained).initialize(width, height); + } + + /** + * Copies the depth data from this object to the specified array. + * @param depthData array of ints that will receive a copy of + * the depth data + */ + void getDepthData(int[] depthData) { + ((DepthComponentNativeRetained)this.retained).getDepthData(depthData); + } + + /** + * Creates a retained mode DepthComponentIntRetained object that this + * DepthComponentInt component object will point to. + */ + void createRetained() { + this.retained = new DepthComponentNativeRetained(); + this.retained.setSource(this); + } + + /** + * Creates a new DepthComponentNative object. Called from a Leaf node's + * <code>duplicateNode</code> method. + * + * @return a duplicate of the DepthComponentNative object. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + public NodeComponent cloneNodeComponent() { + DepthComponentNativeRetained rt = (DepthComponentNativeRetained) retained; + DepthComponentNative d = new DepthComponentNative(rt.width, + rt.height); + d.duplicateNodeComponent(this); + return d; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + int originalData[] = ((DepthComponentNativeRetained) + originalNodeComponent.retained).depthData; + + int currentData[] = ((DepthComponentNativeRetained) retained).depthData; + + if (originalData != null) { + for (int i=0; i < originalData.length; i++) + currentData[i] = originalData[i]; + } + } +} diff --git a/src/classes/share/javax/media/j3d/DepthComponentNativeRetained.java b/src/classes/share/javax/media/j3d/DepthComponentNativeRetained.java new file mode 100644 index 0000000..9c29afa --- /dev/null +++ b/src/classes/share/javax/media/j3d/DepthComponentNativeRetained.java @@ -0,0 +1,63 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A 2D array of depth (Z) values stored in the most efficient format for a + * particular device. Values are not accessible by the user and may only be + * used to read the Z values and subsequently write them back. + */ + +class DepthComponentNativeRetained extends DepthComponentRetained { + // Change this to whatever native format is best... + int depthData[]; + + /** + * Constructs a new native depth (z-buffer) component object with the + * specified width and height. + * @param width the width of the array of depth values + * @param height the height of the array of depth values + */ + void initialize(int width, int height) { + type = DEPTH_COMPONENT_TYPE_NATIVE; + depthData = new int[width * height]; + this.width = width; + this.height = height; + } + + /** + * Copies the depth data from this object to the specified array. + * @param depthData array of ints that will receive a copy of + * the depth data + */ + void getDepthData(int[] depthData) { + int i; + for (i = 0; i < this.depthData.length; i++) + depthData[i] = this.depthData[i]; + } + + /** + * retrieve depth data from input buffer + */ + final void retrieveDepth(int[] buf, int wRead, int hRead) { + int srcOffset, dstOffset, i; + + // Yup -> Ydown + for (srcOffset = (hRead - 1) * wRead, dstOffset = 0, + i = 0; i < hRead; i++, + srcOffset -= wRead, dstOffset += width) { + + System.arraycopy(buf, srcOffset, depthData, dstOffset, wRead); + } + } +} diff --git a/src/classes/share/javax/media/j3d/DepthComponentRetained.java b/src/classes/share/javax/media/j3d/DepthComponentRetained.java new file mode 100644 index 0000000..3bb5965 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DepthComponentRetained.java @@ -0,0 +1,48 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Abstract base class that defines a 2D array of depth (Z) values. + */ + + +abstract class DepthComponentRetained extends NodeComponentRetained { + // depth component types + static final int DEPTH_COMPONENT_TYPE_INT = 1; + static final int DEPTH_COMPONENT_TYPE_FLOAT = 2; + static final int DEPTH_COMPONENT_TYPE_NATIVE = DEPTH_COMPONENT_TYPE_INT; + + + // Width and height of DepthComponent---set by subclasses + int width; + int height; + int type; + + /** + * Retrieves the width of this depth component object + * @return the width of the array of depth values + */ + int getWidth() { + return width; + } + + /** + * Retrieves the height of this depth component object + * @return the height of the array of depth values + */ + int getHeight() { + return height; + } + +} diff --git a/src/classes/share/javax/media/j3d/DetailTextureImage.java b/src/classes/share/javax/media/j3d/DetailTextureImage.java new file mode 100644 index 0000000..46aae3b --- /dev/null +++ b/src/classes/share/javax/media/j3d/DetailTextureImage.java @@ -0,0 +1,219 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.*; + + +class DetailTextureImage extends Object { + + final static int NFORMAT = 7; // number of texture format + + int objectIds[]; // texture object id, one per format + int refCount[]; // texture bin reference count, + // to keep track of if the texture + // object id is still being referenced + // by an active texture. If it + // goes to 0 for a particular format, + // the associated texture object + // will be destroyed. + + int resourceCreationMask[]; // one creation mask per format + + ImageComponent2DRetained image = null; // the image itself + + Object resourceLock = new Object(); + + DetailTextureImage(ImageComponent2DRetained img) { + image = img; + } + + native void bindTexture(long ctx, int objectId); + + native void updateTextureImage(long ctx, + int numLevels, int level, + int format, int storedFormat, + int width, int height, + int boundaryWidth, byte[] data); + + + synchronized void incTextureBinRefCount(int format, TextureBin tb) { + if (refCount == null) { + refCount = new int[NFORMAT]; + } + if (resourceCreationMask == null) { + resourceCreationMask = new int[NFORMAT]; + } + refCount[format]++; + + if (image != null && + (image.isByReference() || + image.source.getCapability(ImageComponent.ALLOW_IMAGE_WRITE))) { + tb.renderBin.addNodeComponent(image); + } + } + + synchronized void decTextureBinRefCount(int format, TextureBin tb) { + + if (refCount != null) { + refCount[format]--; + } + if (image != null && + (image.isByReference() || + image.source.getCapability(ImageComponent.ALLOW_IMAGE_WRITE))) { + tb.renderBin.removeNodeComponent(image); + } + } + + + synchronized void freeDetailTextureId(int id, int bitMask) { + synchronized(resourceLock) { + if (objectIds != null) { + for (int i=0; i < resourceCreationMask.length; i++) { + resourceCreationMask[i] &= ~bitMask; + if (resourceCreationMask[i] == 0) { + if (objectIds[i] == id) { + objectIds[i] = -1; + VirtualUniverse.mc.freeTexture2DId(id); + break; + } + } + } + } + + } + } + + synchronized void freeTextureId(int format, int id) { + synchronized(resourceLock) { + if ((objectIds != null) && (objectIds[format] == id)) { + objectIds[format] = -1; + VirtualUniverse.mc.freeTexture2DId(objectIds[format]); + } + } + } + + protected void finalize() { + if (objectIds != null) { + // memory not yet free + // send a message to the request renderer + synchronized (VirtualUniverse.mc.contextCreationLock) { + boolean found = false; + for (int i=0; i < objectIds.length; i++) { + if (objectIds[i] > 0) { + for (Enumeration e = Screen3D.deviceRendererMap.elements(); + e.hasMoreElements(); ) { + Renderer rdr = (Renderer) e.nextElement(); + J3dMessage renderMessage = VirtualUniverse.mc.getMessage(); + renderMessage.threads = J3dThread.RENDER_THREAD; + renderMessage.type = J3dMessage.RENDER_IMMEDIATE; + renderMessage.universe = null; + renderMessage.view = null; + renderMessage.args[0] = null; + renderMessage.args[1] = new Integer(objectIds[i]); + renderMessage.args[2] = "2D"; + rdr.rendererStructure.addMessage(renderMessage); + } + objectIds[i] = -1; + found = true; + } + } + if (found) { + VirtualUniverse.mc.setWorkForRequestRenderer(); + } + } + } + } + + void notifyImageComponentImageChanged(ImageComponentRetained image, + Object value) { + if (resourceCreationMask != null) { + synchronized(resourceLock) { + for (int i = 0; i < NFORMAT; i++) { + resourceCreationMask[i] = 0; + } + } + } + } + + void bindTexture(Canvas3D cv, int format) { + synchronized(resourceLock) { + if (objectIds == null) { + objectIds = new int[NFORMAT]; + for (int i = 0; i < NFORMAT; i++) { + objectIds[i] = -1; + } + } + + if (objectIds[format] == -1) { + objectIds[format] = VirtualUniverse.mc.getTexture2DId(); + } + cv.addTextureResource(objectIds[format], this); + } + + bindTexture(cv.ctx, objectIds[format]); + } + + + void updateNative(Canvas3D cv, int format) { + if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_DETAIL) == 0) { + return; + } + + boolean reloadTexture = false; + + // bind the detail texture + + bindTexture(cv, format); + + if (cv.useSharedCtx && cv.screen.renderer.sharedCtx != 0) { + if ((resourceCreationMask[format] & cv.screen.renderer.rendererBit) + == 0) { + reloadTexture = true; + cv.makeCtxCurrent(cv.screen.renderer.sharedCtx); + bindTexture(cv, format); + } + } else { + if ((resourceCreationMask[format] & cv.canvasBit) == 0) { + reloadTexture = true; + } + } + + // No D3D support yet + + if (reloadTexture) { + + updateTextureImage(cv.ctx, 1, 0, format, image.storedYupFormat, + image.width, image.height, 0, image.imageYup); + + + // Rendered image + + } + + if (cv.useSharedCtx) { + cv.makeCtxCurrent(cv.ctx); + synchronized(resourceLock) { + resourceCreationMask[format] |= cv.screen.renderer.rendererBit; + } + } else { + synchronized(resourceLock) { + resourceCreationMask[format] |= cv.canvasBit; + } + } + } +} + + + diff --git a/src/classes/share/javax/media/j3d/DirectionalLight.java b/src/classes/share/javax/media/j3d/DirectionalLight.java new file mode 100644 index 0000000..60fd994 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DirectionalLight.java @@ -0,0 +1,188 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; +import javax.vecmath.Vector3f; + +/** + * A DirectionalLight node defines an oriented light with an origin at + * infinity. It has the same attributes as a Light node, with the + * addition of a directional vector to specify the direction in which the + * light shines. A directional light has parallel light rays that travel + * in one direction along the specified vector. Directional light contributes + * to diffuse and specular reflections, which in turn depend on the + * orientation of an object's surface but not its position. A directional + * light does not contribute to ambient reflections. + */ + +public class DirectionalLight extends Light { + /** + * Specifies that the Node allows access to its object's direction + * information. + */ + public static final int + ALLOW_DIRECTION_READ = CapabilityBits.DIRECTIONAL_LIGHT_ALLOW_DIRECTION_READ; + + /** + * Specifies that the Node allows writing to its object's direction + * information. + */ + public static final int + ALLOW_DIRECTION_WRITE = CapabilityBits.DIRECTIONAL_LIGHT_ALLOW_DIRECTION_WRITE; + + /** + * Constructs a DirectionalLight node with default parameters. + * The default values are as follows: + * <ul> + * direction : (0,0,-1)<br> + * </ul> + */ + public DirectionalLight() { + } + + /** + * Constructs and initializes a directional light. + * @param color the color of the light source + * @param direction the direction vector pointing from the light + * to the object + */ + public DirectionalLight(Color3f color, Vector3f direction) { + super(color); + ((DirectionalLightRetained)this.retained).initDirection(direction); + } + + /** + * Constructs and initializes a directional light. + * @param lightOn flag indicating whether this light is on or off + * @param color the color of the light source + * @param direction the direction vector pointing from the light + * to the object + */ + public DirectionalLight(boolean lightOn, Color3f color, Vector3f direction) { + super(lightOn, color); + ((DirectionalLightRetained)this.retained).initDirection(direction); + } + + /** + * Creates the retained mode DirectionalLightRetained object that this + * DirectionalLight component object will point to. + */ + void createRetained() { + this.retained = new DirectionalLightRetained(); + this.retained.setSource(this); + } + + /** + * Set light direction. + * @param direction the new direction + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDirection(Vector3f direction) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_WRITE)) + throw new CapabilityNotSetException( + J3dI18N.getString("DirectionalLight0")); + + if (isLive()) + ((DirectionalLightRetained)this.retained).setDirection(direction); + else + ((DirectionalLightRetained)this.retained).initDirection(direction); + } + + /** + * Set light direction. + * @param x the new X direction + * @param y the new Y direction + * @param z the new Z direction + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDirection(float x, float y, float z) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_WRITE)) + throw new CapabilityNotSetException( + J3dI18N.getString("DirectionalLight1")); + + if (isLive()) + ((DirectionalLightRetained)this.retained).setDirection(x,y,z); + else + ((DirectionalLightRetained)this.retained).initDirection(x,y,z); + } + + /** + * Gets this Light's current direction and places it in the parameter specified. + * @param direction the vector that will receive this node's direction + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDirection(Vector3f direction) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_READ)) + throw new CapabilityNotSetException( + J3dI18N.getString("DirectionalLight2")); + + ((DirectionalLightRetained)this.retained).getDirection(direction); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + DirectionalLight d = new DirectionalLight(); + d.duplicateNode(this, forceDuplicate); + return d; + } + + + /** + * Copies all DirectionalLight information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + Vector3f v = new Vector3f(); + ((DirectionalLightRetained) originalNode.retained).getDirection(v); + ((DirectionalLightRetained) retained).initDirection(v); + } +} diff --git a/src/classes/share/javax/media/j3d/DirectionalLightRetained.java b/src/classes/share/javax/media/j3d/DirectionalLightRetained.java new file mode 100644 index 0000000..a9a17d0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DirectionalLightRetained.java @@ -0,0 +1,203 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * An infinite directional light source object. + */ + +class DirectionalLightRetained extends LightRetained +{ + static final int DIRECTION_CHANGED = LAST_DEFINED_BIT << 1; + + // The direction in which this light source is pointing. + Vector3f direction = new Vector3f(0.0f, 0.0f, -1.0f); + + // The transformed direction + Vector3f xformDirection = new Vector3f(0.0f, 0.0f, -1.0f); + + DirectionalLightRetained() { + this.nodeType = NodeRetained.DIRECTIONALLIGHT; + lightType = 2; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + /** + * Initializes this light's direction from the vector provided. + * @param direction the new direction + */ + void initDirection(Vector3f direction) { + this.direction.set(direction); + if (staticTransform != null) { + staticTransform.transform.transform( + this.direction, this.direction); + } + } + + /** + * Sets this light's direction from the vector provided. + * and sends a message + * @param direction the new direction + */ + void setDirection(Vector3f direction) { + initDirection(direction); + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.LIGHT_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(DIRECTION_CHANGED); + if (inSharedGroup) + createMessage.args[2] = new Integer(numMirrorLights); + else + createMessage.args[2] = new Integer(1); + createMessage.args[3] = mirrorLights.clone(); + createMessage.args[4] = new Vector3f(direction); + VirtualUniverse.mc.processMessage(createMessage); + + } + + + /** + * Initializes this light's direction from the three values provided. + * @param x the new x direction + * @param y the new y direction + * @param z the new z direction + */ + void initDirection(float x, float y, float z) { + this.direction.x = x; + this.direction.y = y; + this.direction.z = z; + + if (staticTransform != null) { + staticTransform.transform.transform( + this.direction, this.direction); + } + } + + /** + * Sets this light's direction from the three values provided. + * @param x the new x direction + * @param y the new y direction + * @param z the new z direction + */ + void setDirection(float x, float y, float z) { + setDirection(new Vector3f(x, y, z)); + } + + + /** + * Retrieves this light's direction and places it in the + * vector provided. + * @param direction the variable to receive the direction vector + */ + void getDirection(Vector3f direction) { + direction.set(this.direction); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + invTransform.transform(direction, direction); + } + } + + + + void setLive(SetLiveState s) { + super.setLive(s); + J3dMessage createMessage = super.initMessage(8); + Object[] objs = (Object[])createMessage.args[4]; + objs[7] = new Vector3f(direction); + VirtualUniverse.mc.processMessage(createMessage); + + } + + /** + * This update function, and its native counterpart, + * updates a directional light. This includes its + * color and its transformed direction. + */ + // Note : if you add any more fields here , you need to update + // updateLight() in RenderingEnvironmentStructure + void updateMirrorObject(Object[] objs) { + int i; + int component = ((Integer)objs[1]).intValue(); + Transform3D trans; + int numLgts = ((Integer)objs[2]).intValue(); + + LightRetained[] mLgts = (LightRetained[]) objs[3]; + DirectionalLightRetained ml; + if ((component & DIRECTION_CHANGED) != 0) { + + for (i = 0; i < numLgts; i++) { + if (mLgts[i].nodeType == NodeRetained.DIRECTIONALLIGHT) { + ml = (DirectionalLightRetained) mLgts[i]; + ml.direction = (Vector3f)objs[4]; + ml.getLastLocalToVworld().transform(ml.direction, + ml.xformDirection); + ml.xformDirection.normalize(); + } + } + } + + if ((component & INIT_MIRROR) != 0) { + for (i = 0; i < numLgts; i++) { + if (mLgts[i].nodeType == NodeRetained.DIRECTIONALLIGHT) { + ml = (DirectionalLightRetained) mLgts[i]; + ml.direction = (Vector3f)((Object[])objs[4])[7]; + ml.getLastLocalToVworld().transform(ml.direction, + ml.xformDirection); + ml.xformDirection.normalize(); + } + } + } + // call the parent's mirror object update routine + super.updateMirrorObject(objs); + } + + + native void updateLight(long ctx, + int lightSlot, float red, float green, + float blue, float x, float y, float z); + void update(long ctx, int lightSlot, double scale) { + updateLight(ctx, lightSlot, color.x, color.y, color.z, + xformDirection.x, xformDirection.y, + xformDirection.z); + } + + // Clones only the retained side, internal use only + protected Object clone() { + DirectionalLightRetained dr = + (DirectionalLightRetained)super.clone(); + dr.direction = new Vector3f(direction); + dr.xformDirection = new Vector3f(0.0f, 0.0f, -1.0f); + return dr; + } + + + // Called on the mirror object + void updateTransformChange() { + super.updateTransformChange(); + + getLastLocalToVworld().transform(direction, xformDirection); + xformDirection.normalize(); + + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + xform.transform.transform(direction, direction); + } +} diff --git a/src/classes/share/javax/media/j3d/DisplayListRenderMethod.java b/src/classes/share/javax/media/j3d/DisplayListRenderMethod.java new file mode 100644 index 0000000..c593567 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DisplayListRenderMethod.java @@ -0,0 +1,256 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The RenderMethod interface is used to create various ways to render + * different geometries. + */ + +class DisplayListRenderMethod implements RenderMethod { + + /** + * display list buffer size + */ + final int bufferSize = 128; + + /** + * display list buffer + */ + int[] buffer = new int[bufferSize]; + + native void callDisplayLists(int size, int[] buffer); + + /** + * The actual rendering code for this RenderMethod + */ + public boolean render(RenderMolecule rm, Canvas3D cv, int pass, + RenderAtomListInfo ra, + int dirtyBits) { + + if (rm.doInfinite || rm.vwcBounds.intersect(cv.viewFrustum)) { + cv.updateState(pass, dirtyBits); + cv.callDisplayList(cv.ctx, rm.displayListId, + rm.isNonUniformScale); + return true; + } + return false; + } + + public boolean renderSeparateDlists(RenderMolecule rm, + Canvas3D cv, + int pass, + RenderAtomListInfo r, int dirtyBits) { + + if (rm.doInfinite) { + cv.updateState(pass, dirtyBits); + while (r != null) { + cv.callDisplayList(cv.ctx, + ((GeometryArrayRetained)r.geometry()).dlistId, + rm.isNonUniformScale); + r = r.next; + } + + return true; + } + + boolean isVisible = false; // True if any of the RAs is visible. + while (r != null) { + if (cv.ra == r.renderAtom) { + if (cv.raIsVisible) { + cv.updateState(pass, dirtyBits); + cv.callDisplayList(cv.ctx, + ((GeometryArrayRetained)r.geometry()).dlistId, + rm.isNonUniformScale); + isVisible = true; + } + } + else { + if (r.renderAtom.localeVwcBounds.intersect(cv.viewFrustum)) { + cv.updateState(pass, dirtyBits); + cv.raIsVisible = true; + cv.callDisplayList(cv.ctx, + ((GeometryArrayRetained)r.geometry()).dlistId, + rm.isNonUniformScale); + isVisible = true; + } + else { + cv.raIsVisible = false; + } + cv.ra = r.renderAtom; + } + r = r.next; + } + + return isVisible; + } + + + + public boolean renderSeparateDlistPerRinfo(RenderMolecule rm, + Canvas3D cv, + int pass, + RenderAtomListInfo r, + int dirtyBits) { + + if (rm.doInfinite) { + cv.updateState(pass, dirtyBits); + while (r != null) { + cv.callDisplayList(cv.ctx,r.renderAtom.dlistIds[r.index], + rm.isNonUniformScale); + r = r.next; + } + return true; + } + boolean isVisible = false; // True if any of the RAs is visible. + while (r != null) { + if (cv.ra == r.renderAtom) { + if (cv.raIsVisible) { + cv.updateState(pass, dirtyBits); + cv.callDisplayList(cv.ctx, r.renderAtom.dlistIds[r.index], + rm.isNonUniformScale); + isVisible = true; + } + } + else { + if (r.renderAtom.localeVwcBounds.intersect(cv.viewFrustum)) { + cv.updateState(pass, dirtyBits); + cv.raIsVisible = true; + cv.callDisplayList(cv.ctx, r.renderAtom.dlistIds[r.index], + rm.isNonUniformScale); + isVisible = true; + } + else { + cv.raIsVisible = false; + } + cv.ra = r.renderAtom; + } + r = r.next; + } + return isVisible; + + } + + + + + void buildDisplayList(RenderMolecule rm, Canvas3D cv) { + RenderAtomListInfo ra; + boolean useAlpha; + GeometryArrayRetained geo; + useAlpha = rm.useAlpha; + Transform3D staticTransform; + Transform3D staticNormalTransform; + + if ((rm.primaryRenderAtomList != null) && + (rm.texCoordSetMapLen <= cv.numTexCoordSupported)) { + + cv.newDisplayList(cv.ctx, rm.displayListId); + + ra = rm.primaryRenderAtomList; + + while (ra != null) { + geo = (GeometryArrayRetained)ra.geometry(); + if (ra.renderAtom.geometryAtom.source.staticTransform == null) { + staticTransform = null; + staticNormalTransform = null; + } else { + staticTransform = + ra.renderAtom.geometryAtom.source.staticTransform.transform; + if ((geo.vertexFormat & GeometryArray.NORMALS) != 0) { + staticNormalTransform = + ra.renderAtom.geometryAtom.source.staticTransform.getNormalTransform(); + } else { + staticNormalTransform = null; + } + } + geo.buildGA(cv, ra.renderAtom, false, + (useAlpha && + ((geo.vertexFormat & GeometryArray.COLOR) != 0)), + rm.alpha, + rm.textureBin.attributeBin.ignoreVertexColors, + staticTransform, + staticNormalTransform); + ra = ra.next; + } + cv.endDisplayList(cv.ctx); + } + } + + void buildIndividualDisplayList(RenderAtomListInfo ra, Canvas3D cv, + long ctx) { + GeometryArrayRetained geo; + + geo = (GeometryArrayRetained)ra.geometry(); + if ((geo.texCoordSetMap != null) && + (geo.texCoordSetMap.length > cv.numTexCoordSupported)) { + return; + } + + // Note, the dlistId does not change when renderer is building + cv.newDisplayList(ctx, geo.dlistId); + + // No need to lock when it is indexed geometry since we have + // our own copy + // Note individual dlist is only created if alpha is not modifiable + // so, we don't need any renderMolecule specific information + geo.buildGA(cv, ra.renderAtom, false, + false, + 1.0f, + false, + null, null); + cv.endDisplayList(ctx); + } + + void buildDlistPerRinfo(RenderAtomListInfo ra, RenderMolecule rm, Canvas3D cv) { + boolean useAlpha; + GeometryArrayRetained geo; + useAlpha = rm.useAlpha; + Transform3D staticTransform; + Transform3D staticNormalTransform; + int id; + + geo = (GeometryArrayRetained)ra.geometry(); + if ((rm.primaryRenderAtomList != null) && + (rm.texCoordSetMapLen <= cv.numTexCoordSupported)) { + + id = ra.renderAtom.dlistIds[ra.index]; + cv.newDisplayList(cv.ctx, id); + geo = (GeometryArrayRetained)ra.geometry(); + if (ra.renderAtom.geometryAtom.source.staticTransform == null) { + staticTransform = null; + staticNormalTransform = null; + } else { + staticTransform = + ra.renderAtom.geometryAtom.source.staticTransform.transform; + if ((geo.vertexFormat & GeometryArray.NORMALS) != 0) { + staticNormalTransform = + ra.renderAtom.geometryAtom.source.staticTransform.getNormalTransform(); + } else { + staticNormalTransform = null; + } + } + + geo.buildGA(cv, ra.renderAtom, false, + (useAlpha && + ((geo.vertexFormat & GeometryArray.COLOR) != 0)), + rm.alpha, + rm.textureBin.attributeBin.ignoreVertexColors, + staticTransform, + staticNormalTransform); + cv.endDisplayList(cv.ctx); + } + + } + +} diff --git a/src/classes/share/javax/media/j3d/DistanceLOD.java b/src/classes/share/javax/media/j3d/DistanceLOD.java new file mode 100644 index 0000000..3148997 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DistanceLOD.java @@ -0,0 +1,294 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Point3f; +import java.util.Enumeration; + +/** + * This class defines a distance-based LOD behavior node that operates on + * a Switch group node to select one of the children of that Switch node + * based on the distance of this LOD node from the viewer. + * An array of <i>n</i> monotonically increasing distance values is + * specified, such that distances[0] is associated with the highest level of + * detail and distances[<i>n</i>-1] is associated with the lowest level of + * detail. Based on the actual distance from the viewer to + * this DistanceLOD node, these <i>n</i> + * distance values [0, <i>n</i>-1] select from among <i>n</i>+1 + * levels of detail [0, <i>n</i>]. If <i>d</i> is the distance from + * the viewer to the LOD node, then the equation for determining + * which level of detail (child of the Switch node) is selected is: + * <p> + * <ul> + * 0, if <i>d</i> <= distances[0] + * <br> + * <i>i</i>, if distances[<i>i</i>-1] < <i>d</i> <= distances[<i>i</i>] + * <br> + * <i>n</i>, if d > distances[<i>n</i>-1] + * </ul> + * <p> + * Note that both the position and the array of distances are + * specified in the local coordinate system of this node. + */ +public class DistanceLOD extends LOD { + + private double distances[]; + private Point3f position = new Point3f(0.0f, 0.0f, 0.0f); + + // variables for processStimulus + private Point3f center = new Point3f(); + private Point3f viewPosition = new Point3f(); + + /** + * Constructs and initializes a DistanceLOD node with default values. + * Note that the default constructor creates a DistanceLOD object with + * a single distance value set to 0.0 and is, therefore, not useful. + */ + public DistanceLOD() { + distances = new double[1]; + distances[0] = 0.0; + } + + /** + * Constructs and initializes a DistanceLOD node with the specified + * array of distances and a default position of (0,0,0). + * @param distances an array of values representing LOD cutoff distances + */ + public DistanceLOD(float[] distances) { + this.distances = new double[distances.length]; + + for(int i=0;i<distances.length;i++) { + this.distances[i] = (double)distances[i]; + } + } + + /** + * Constructs and initializes a DistanceLOD node with the specified + * array of distances and the specified position. + * @param distances an array of values representing LOD cutoff distances + * @param position the position of this LOD node + */ + public DistanceLOD(float[] distances, Point3f position) { + this.distances = new double[distances.length]; + + for(int i=0;i<distances.length;i++) { + this.distances[i] = (double)distances[i]; + } + this.position.set(position); + } + + /** + * Sets the position of this LOD node. This position is specified in + * the local coordinates of this node, and is + * the position from which the distance to the viewer is computed. + * @param position the new position + */ + public void setPosition(Point3f position) { + if (((NodeRetained)retained).staticTransform != null) { + ((NodeRetained)retained).staticTransform.transform.transform( + position, this.position); + } else { + this.position.set(position); + } + } + + /** + * Retrieves the current position of this LOD node. This position is + * in the local coordinates of this node. + * @param position the object that will receive the current position + */ + public void getPosition(Point3f position) { + if (((NodeRetained)retained).staticTransform != null) { + Transform3D invTransform = + ((NodeRetained)retained).staticTransform.getInvTransform(); + invTransform.transform(this.position, position); + } else { + position.set(this.position); + } + } + + /** + * Returns a count of the number of LOD distance cut-off parameters. + * Note that the number of levels of detail (children of the Switch node) + * is one greater than the number of distance values. + * @return a count of the LOD cut-off distances + */ + public int numDistances() { + return distances.length; + } + + /** + * Returns a particular LOD cut-off distance. + * @param whichDistance an index specifying which LOD distance to return + * @return the cut-off distance value associated with the index provided + */ + public double getDistance(int whichDistance) { + return distances[whichDistance]; + } + + /** + * Sets a particular LOD cut-off distance. + * @param whichDistance an index specifying which LOD distance to modify + * @param distance the cut-off distance associated with the index provided + */ + public void setDistance(int whichDistance, double distance) { + distances[whichDistance] = distance; + } + + /** + * Initialize method that sets up initial wakeup criteria. + */ + public void initialize() { + // Insert wakeup condition into queue + wakeupOn(wakeupFrame); + } + + /** + * Process stimulus method that computes appropriate level of detail. + * @param criteria an enumeration of the criteria that caused the + * stimulus + */ + public void processStimulus(Enumeration criteria) { + + + // compute distance in virtual world + View v = this.getView(); + if( v == null ) { + wakeupOn(wakeupFrame); + return; + } + + ViewPlatform vp = v.getViewPlatform(); + if (vp == null) { + return; + } + + // Handle stimulus + double viewDistance = 0.0; + int nSwitches,i,index=0; + + Transform3D localToWorldTrans = VirtualUniverse.mc.getTransform3D(null); + + localToWorldTrans.set(((NodeRetained)this.retained).getCurrentLocalToVworld()); + + + // DistanceLOD's location in virutal world + localToWorldTrans.transform( position, center); + + + viewPosition.x = (float)((ViewPlatformRetained)vp.retained).schedSphere.center.x; + viewPosition.y = (float)((ViewPlatformRetained)vp.retained).schedSphere.center.y; + viewPosition.z = (float)((ViewPlatformRetained)vp.retained).schedSphere.center.z; + viewDistance = center.distance( viewPosition); + + + // convert distance into local coordinates + viewDistance = viewDistance/localToWorldTrans.getDistanceScale(); + + nSwitches = numSwitches(); + + index = distances.length; // viewDistance > distances[n-1] + + if( viewDistance <= distances[0] ) { + index = 0; + } else { + for (i=1; i < distances.length; i++) { + if ((viewDistance > distances[i-1]) && + (viewDistance <= distances[i])) { + index = i; + break; + } + } + } + + for(i=nSwitches-1; i>=0; i--) { + Switch sw = getSwitch(i); + // Optimize, this behavior is passive + // Note that we skip the capability check for getWhichChild() + if (((SwitchRetained) sw.retained).getWhichChild() != + index) { + sw.setWhichChild(index); + } + } + + VirtualUniverse.mc.addToTransformFreeList(localToWorldTrans); + + // Insert wakeup condition into queue + wakeupOn(wakeupFrame); + + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + DistanceLOD d = new DistanceLOD(); + d.duplicateNode(this, forceDuplicate); + return d; + } + + + /** + * Copies all DistanceLOD information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + DistanceLOD lod = (DistanceLOD) originalNode; + + int numD = lod.numDistances(); + + // No API available to set the size of this array after initialize + this.distances = new double[numD]; + + for (int i = 0; i < numD; i++) + setDistance(i, lod.getDistance(i)); + + Point3f p = new Point3f(); + lod.getPosition(p); + setPosition(p); + } + + void mergeTransform(TransformGroupRetained xform) { + xform.transform.transform(position, position); + } +} diff --git a/src/classes/share/javax/media/j3d/DrawingSurfaceObject.java b/src/classes/share/javax/media/j3d/DrawingSurfaceObject.java new file mode 100644 index 0000000..5a7444f --- /dev/null +++ b/src/classes/share/javax/media/j3d/DrawingSurfaceObject.java @@ -0,0 +1,42 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The DrawingSurfaceObject class is used to manage native drawing surface + */ + +abstract class DrawingSurfaceObject extends Object { + + Canvas3D canvas; + boolean gotDsiLock = false; + boolean onScreen; + + abstract boolean renderLock(); + abstract void unLock(); + abstract void getDrawingSurfaceObjectInfo(); + abstract void invalidate(); + + DrawingSurfaceObject(Canvas3D cv) { + canvas = cv; + onScreen = !cv.offScreen; + } + + synchronized boolean isLocked() { + return gotDsiLock; + } + + synchronized void contextValidated() { + canvas.validCtx = true; + } +} diff --git a/src/classes/share/javax/media/j3d/DrawingSurfaceObjectAWT.java b/src/classes/share/javax/media/j3d/DrawingSurfaceObjectAWT.java new file mode 100644 index 0000000..66c38b5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/DrawingSurfaceObjectAWT.java @@ -0,0 +1,142 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.Point; + +/** + * The DrawingSurfaceObject class is used to manage native drawing surface + */ + +class DrawingSurfaceObjectAWT extends DrawingSurfaceObject { + + // drawing surface + long nativeDS = 0; + long dsi = 0; + + boolean doLastUnlock = false; + boolean xineramaDisabled = false; + + long display = 0; + int screenID = 0; + + static long nativeAWT = 0; + + native boolean lockAWT(long ds); + native void unlockAWT(long ds); + static native void lockGlobal(long awt); + static native void unlockGlobal(long awt); + native long getDrawingSurfaceAWT(Canvas3D cv, long awt); + native long getDrawingSurfaceInfo(long ds); + static native void freeResource(long awt, long ds, long dsi); + native int getDrawingSurfaceWindowIdAWT(Canvas3D cv, long ds, long dsi, + long display, int screenID, + boolean xineramaDisabled); + + DrawingSurfaceObjectAWT(Canvas3D cv, long awt, + long display, int screenID, + boolean xineramaDisabled) { + super(cv); + nativeAWT = awt; + + this.display = display; + this.screenID = screenID; + this.xineramaDisabled = xineramaDisabled; + } + + synchronized boolean renderLock() { + + if (onScreen) { + if (nativeDS == 0) { + return false; + } else { + if (lockAWT(nativeDS)) { + gotDsiLock = true; + return true; + } else { + return false; + } + } + } else { + gotDsiLock = true; + lockGlobal(nativeAWT); + } + return true; + } + + synchronized void unLock() { + + if (gotDsiLock) { + if (onScreen) { + if (nativeDS != 0) { + unlockAWT(nativeDS); + gotDsiLock = false; + if (doLastUnlock) { + nativeDS = 0; + dsi = 0; + doLastUnlock = false; + } + } + } else { + unlockGlobal(nativeAWT); + gotDsiLock = false; + } + } + } + + + synchronized void getDrawingSurfaceObjectInfo() { + // get native drawing surface - ds + nativeDS = getDrawingSurfaceAWT(canvas, nativeAWT); + + // get window id + if (nativeDS != 0) { + dsi = getDrawingSurfaceInfo(nativeDS); + if (dsi != 0) { + canvas.window = getDrawingSurfaceWindowIdAWT + (canvas, nativeDS, dsi, display, screenID, + xineramaDisabled); + } + } + + } + + + synchronized void invalidate() { + if (gotDsiLock && (nativeDS != 0)) { + // Should not call unlock in AWT thread + // Otherwise IllegalMonitorException will throw + // unlockAWT(nativeDS); + // We don't reset nativeDS & dsi to 0 here. + // This allow Renderer to continue unLock. + doLastUnlock = true; + } else { + nativeDS = 0; + dsi = 0; + } + } + + static void freeDrawingSurface(Object obj) { + long p[] = (long[]) obj; + freeResource(nativeAWT, p[0], p[1]); + } + + long getDSI() { + return dsi; + } + + long getDS() { + return nativeDS; + } + +} diff --git a/src/classes/share/javax/media/j3d/EnvironmentSet.java b/src/classes/share/javax/media/j3d/EnvironmentSet.java new file mode 100644 index 0000000..2083927 --- /dev/null +++ b/src/classes/share/javax/media/j3d/EnvironmentSet.java @@ -0,0 +1,563 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.*; + +/** + * The LightBin manages a collection of EnvironmentSet objects. + * The number of objects managed depends upon the number of Lights + * in each EnvironmentSet and the number of lights supported by + * the underlying rendering layer. + */ + +class EnvironmentSet extends Object implements ObjectUpdate{ + // A list of pre-defined bits to indicate which component + // of the rendermolecule changed + static final int LIGHTENABLE_CHANGED = 0x01; + static final int AMBIENT_CHANGED = 0x02; + static final int FOG_CHANGED = 0x04; + static final int MODELCLIP_CHANGED = 0x08; + + /** + * The ArrayList of Lights in this EnvironmentSet + */ + ArrayList lights = new ArrayList(); + + /** + * The position of the light in the lightbin that the + * lights in this environment set corresponds to + */ + int[] ltPos = null; + + + /** + * The arraylist of ambient lights in this env list + */ + ArrayList ambLights = new ArrayList(); + + /** + * The LightBin that this EnvironmentSet resides + */ + LightBin lightBin = null; + + /** + * The bitmask of light slots that need to be enabled for this + */ + long enableMask = 0; + + /** + * The cached scene ambient component for this EnvirionmentSet + */ + Color3f sceneAmbient = new Color3f(); + + /** + * The RenderBin for this EnvirionmentSet + */ + RenderBin renderBin = null; + + /** + * The fog for this EnvironmentSet + */ + FogRetained fog = null; + + + /** + * The model clip for this EnvironmentSet + */ + ModelClipRetained modelClip = null; + + /** + * enable mask for the model clip planes in this environment set + */ + int enableMCMask = 0; // enable mask used in modelClip.update() + int enableMCMaskCache = 0; // enable mask computed in renderBin that + // is copied into enableMCMask in updateObject + + /** + * The references to the next and previous LightBins in the + * list. + */ + EnvironmentSet next = null; + EnvironmentSet prev = null; + + /** + * List of attrributeBins to be added next Frame + */ + ArrayList addAttributeBins = new ArrayList(); + + + /** + * Canvas Dirty Mask for + */ + int canvasDirty = 0; + + /** + * cached value of enable mask + */ + long enableMaskCache = 0; + + /** + * + */ + boolean onUpdateList = false; + + /** + * The list of AttributeBins in this EnvironmentSet + */ + AttributeBin attributeBinList = null; + + EnvironmentSet(RenderAtom ra, LightRetained[] lightList, FogRetained fog, + ModelClipRetained modelClip, RenderBin rb) { + renderBin = rb; + reset(ra, lightList, fog, modelClip); + } + + void reset(RenderAtom ra, LightRetained[] lightList, FogRetained fog, + ModelClipRetained modelClip) { + int i; + LightRetained light; + + prev = null; + next = null; + onUpdateList = false; + attributeBinList = null; + lights.clear(); + ambLights.clear(); + sceneAmbient.x = 0.0f; + sceneAmbient.y = 0.0f; + sceneAmbient.z = 0.0f; + if (lightList != null) { + for (i=0; i<lightList.length; i++) { + light = lightList[i]; + if (light.nodeType == NodeRetained.AMBIENTLIGHT) { + ambLights.add(light); + sceneAmbient.x += light.color.x; + sceneAmbient.y += light.color.y; + sceneAmbient.z += light.color.z; + } + else { + lights.add(light); + } + + light.environmentSets.add(this); + } + if (sceneAmbient.x > 1.0f) { + sceneAmbient.x = 1.0f; + } + if (sceneAmbient.y > 1.0f) { + sceneAmbient.y = 1.0f; + } + if (sceneAmbient.z > 1.0f) { + sceneAmbient.z = 1.0f; + } + } + this.fog = fog; + if (fog != null) { + fog.environmentSets.add(this); + } + + this.modelClip = modelClip; + enableMCMaskCache = 0; + if (modelClip != null) { + modelClip.environmentSets.add(this); + + for (i = 0; i < 6; i++) { + if (modelClip.enables[i]) + enableMCMaskCache |= 1 << i; + } + enableMCMask = enableMCMaskCache; + } + + // Allocate the ltPos array + ltPos = new int[lights.size()]; + enableMask = 0; + } + + /** + * This tests if the qiven lights and fog match this EnviornmentSet + */ + boolean equals(RenderAtom ra, LightRetained[] lights, FogRetained fog, + ModelClipRetained modelClip) { + int i; + + + // First see if the lights match. + if (lights == null && ambLights == null) { + if (this.lights.size() == 0) { + if (this.fog == fog) { + return (true); + } else { + return (false); + } + } else { + return (false); + } + } + + if ((this.lights.size() + this.ambLights.size())!= lights.length) { + return (false); + } + + for (i=0; i<lights.length; i++) { + if (lights[i].nodeType == LightRetained.AMBIENTLIGHT) { + if (!this.ambLights.contains(lights[i])) { + return (false); + } + } + else { + if (!this.lights.contains(lights[i])) { + return (false); + } + } + } + + // Now check fog + if (this.fog != fog) { + return (false); + } + + // Now check model clip + if (this.modelClip != modelClip) { + return (false); + } + + return (true); + } + + + /** + * This tests if the qiven lights match this EnviornmentSet + */ + boolean equalLights(LightRetained[] lights) { + int i; + + // First see if the lights match. + if (lights == null && ambLights == null) { + if (this.lights.size() == 0) { + return (true); + } + } + + if ((this.lights.size() + this.ambLights.size())!= lights.length) { + return (false); + } + + for (i=0; i<lights.length; i++) { + if (lights[i].nodeType == LightRetained.AMBIENTLIGHT) { + if (!this.ambLights.contains(lights[i])) { + return (false); + } + } + else { + if (!this.lights.contains(lights[i])) { + return (false); + } + } + } + + return (true); + } + + + public void updateObject() { + int i; + AttributeBin a; + + if (addAttributeBins.size() > 0) { + a = (AttributeBin)addAttributeBins.get(0); + if (attributeBinList == null) { + attributeBinList = a; + + } + else { + a.next = attributeBinList; + attributeBinList.prev = a; + attributeBinList = a; + } + for (i = 1; i < addAttributeBins.size() ; i++) { + a = (AttributeBin) addAttributeBins.get(i); + a.next = attributeBinList; + attributeBinList.prev = a; + attributeBinList = a; + } + } + + addAttributeBins.clear(); + + if (canvasDirty != 0) { + Canvas3D canvases[] = renderBin.view.getCanvases(); + + for (i = 0; i < canvases.length; i++) { + canvases[i].canvasDirty |= canvasDirty; + } + + if ((canvasDirty & Canvas3D.AMBIENTLIGHT_DIRTY) != 0) { + updateSceneAmbient(); + } + + if ((canvasDirty & Canvas3D.LIGHTENABLES_DIRTY) != 0) { + enableMask = enableMaskCache; + } + + if ((canvasDirty & Canvas3D.MODELCLIP_DIRTY) != 0) { + enableMCMask = enableMCMaskCache; + } + + canvasDirty = 0; + } + onUpdateList = false; + } + + /** + * Adds the given AttributeBin to this EnvironmentSet. + */ + void addAttributeBin(AttributeBin a, RenderBin rb) { + a.environmentSet = this; + addAttributeBins.add(a); + if (!onUpdateList) { + rb.objUpdateList.add(this); + onUpdateList = true; + } + } + + /** + * Removes the given AttributeBin from this EnvironmentSet. + */ + void removeAttributeBin(AttributeBin a) { + LightRetained light; + int i; + + a.environmentSet = null; + // If the attributeBin being remove is contained in addAttributeBins, then + // remove the attributeBin from the addList + if (addAttributeBins.contains(a)) { + addAttributeBins.remove(addAttributeBins.indexOf(a)); + } + else { + if (a.prev == null) { // At the head of the list + attributeBinList = a.next; + if (a.next != null) { + a.next.prev = null; + } + } else { // In the middle or at the end. + a.prev.next = a.next; + if (a.next != null) { + a.next.prev = a.prev; + } + } + } + a.prev = null; + a.next = null; + + if (a.definingRenderingAttributes != null && + (a.definingRenderingAttributes.changedFrequent != 0)) + a.definingRenderingAttributes = null; + a.onUpdateList &= ~AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST; + + // Add this attributebin to the free list + renderBin.attrBinFreelist.add(a); + + if (attributeBinList == null && addAttributeBins.size() == 0) { + // Now remove this environment set from all the lights and fogs + // that use this + int sz = lights.size(); + for (i=0; i < sz; i++) { + ((LightRetained) lights.get(i)).environmentSets.remove(this); + } + sz = ambLights.size(); + for (i = 0; i < sz; i++) { + ((LightRetained) ambLights.get(i)).environmentSets.remove(this); + } + if (fog != null) { + fog.environmentSets.remove(this); + } + lightBin.removeEnvironmentSet(this); + } + } + + void updateSceneAmbient() + { + int i; + sceneAmbient.x = 0.0f; + sceneAmbient.y = 0.0f; + sceneAmbient.z = 0.0f; + for (i=0; i<ambLights.size(); i++) { + LightRetained aL = (LightRetained) ambLights.get(i); + if (aL.lightOn) { + sceneAmbient.x += aL.color.x; + sceneAmbient.y += aL.color.y; + sceneAmbient.z += aL.color.z; + } + } + if (sceneAmbient.x > 1.0f) { + sceneAmbient.x = 1.0f; + } + if (sceneAmbient.y > 1.0f) { + sceneAmbient.y = 1.0f; + } + if (sceneAmbient.z > 1.0f) { + sceneAmbient.z = 1.0f; + } + } + + /** + * Renders this EnvironmentSet + */ + void render(Canvas3D cv) { + AttributeBin a; + + // include this EnvironmentSet to the to-be-updated list in Canvas + cv.setStateToUpdate(Canvas3D.ENVIRONMENTSET_BIT, this); + + a = attributeBinList; + while (a != null) { + a.render(cv); + a = a.next; + } + } + + + void updateAttributes(Canvas3D cv) { + LightRetained light; + int i, numLights; + float red, green, blue; + double scale; + + // within frame + if (cv.environmentSet != this ) { + if (cv.enableMask != enableMask) { + cv.setLightEnables(cv.ctx, enableMask, renderBin.maxLights); + cv.enableMask = enableMask; + } + + if (cv.sceneAmbient.x != sceneAmbient.x || + cv.sceneAmbient.y != sceneAmbient.y || + cv.sceneAmbient.z != sceneAmbient.z ) { + + cv.setSceneAmbient(cv.ctx, + sceneAmbient.x, sceneAmbient.y, sceneAmbient.z); + + cv.sceneAmbient.x = sceneAmbient.x; + cv.sceneAmbient.y = sceneAmbient.y; + cv.sceneAmbient.z = sceneAmbient.z; + } + + + if (cv.fog != fog) { + if (fog != null) { + scale = lightBin.geometryBackground == null? + cv.canvasViewCache.getVworldToCoexistenceScale(): + cv.canvasViewCache.getInfVworldToCoexistenceScale(); + fog.update(cv.ctx, scale); + } else { + cv.disableFog(cv.ctx); + } + cv.fog = fog; + } + + if (cv.modelClip != modelClip) { + if (modelClip != null) { + modelClip.update(cv, enableMCMask); + } else { + cv.disableModelClip(cv.ctx); + } + cv.modelClip = modelClip; + } + + cv.environmentSet = this; + cv.canvasDirty &= ~(Canvas3D.LIGHTENABLES_DIRTY| + Canvas3D.AMBIENTLIGHT_DIRTY | + Canvas3D.FOG_DIRTY | + Canvas3D.MODELCLIP_DIRTY); + + } + // across frames + else if ((cv.canvasDirty & (Canvas3D.LIGHTENABLES_DIRTY| + Canvas3D.AMBIENTLIGHT_DIRTY| + Canvas3D.FOG_DIRTY| + Canvas3D.MODELCLIP_DIRTY)) != 0) { + + if ((cv.canvasDirty & Canvas3D.LIGHTENABLES_DIRTY) != 0) { + cv.setLightEnables(cv.ctx, enableMask, renderBin.maxLights); + cv.enableMask = enableMask; + } + + if ((cv.canvasDirty & Canvas3D.AMBIENTLIGHT_DIRTY) != 0) { + cv.setSceneAmbient(cv.ctx, sceneAmbient.x, + sceneAmbient.y, + sceneAmbient.z); + cv.sceneAmbient.x = sceneAmbient.x; + cv.sceneAmbient.y = sceneAmbient.y; + cv.sceneAmbient.z = sceneAmbient.z; + } + + if ((cv.canvasDirty & Canvas3D.FOG_DIRTY) != 0) { + if (fog != null) { + scale = lightBin.geometryBackground == null? + cv.canvasViewCache.getVworldToCoexistenceScale(): + cv.canvasViewCache.getInfVworldToCoexistenceScale(); + fog.update(cv.ctx, scale); + } else { + cv.disableFog(cv.ctx); + } + cv.fog = fog; + } + + if ((cv.canvasDirty & Canvas3D.MODELCLIP_DIRTY) != 0) { + if (modelClip != null) { + modelClip.update(cv, enableMCMask); + } else { + cv.disableModelClip(cv.ctx); + } + cv.modelClip = modelClip; + } + + cv.canvasDirty &= ~(Canvas3D.LIGHTENABLES_DIRTY| + Canvas3D.AMBIENTLIGHT_DIRTY | + Canvas3D.FOG_DIRTY | + Canvas3D.MODELCLIP_DIRTY); + } + else if ((cv.canvasDirty & Canvas3D.VWORLD_SCALE_DIRTY) != 0) { + if (fog instanceof LinearFogRetained) { + if (fog != null) { + scale = lightBin.geometryBackground == null? + cv.canvasViewCache.getVworldToCoexistenceScale(): + cv.canvasViewCache.getInfVworldToCoexistenceScale(); + fog.update(cv.ctx, scale); + } else { + cv.disableFog(cv.ctx); + } + cv.fog = fog; + } + + if (modelClip != null) { + modelClip.update(cv, enableMCMask); + cv.modelClip = modelClip; + } + } + else if (cv.useStereo) { + + // if using stereo, the vworldToEc matrix will be different + // for each stereo pass, in this case, we will need to + // update modelClip + + if (modelClip != null) { + modelClip.update(cv, enableMCMask); + cv.modelClip = modelClip; + } + } + } + +} diff --git a/src/classes/share/javax/media/j3d/EventCatcher.java b/src/classes/share/javax/media/j3d/EventCatcher.java new file mode 100644 index 0000000..5fb9d39 --- /dev/null +++ b/src/classes/share/javax/media/j3d/EventCatcher.java @@ -0,0 +1,388 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.awt.*; +import java.awt.event.*; + + +/** + * The EventCatcher class is used to track events on a Canvas3D using the + * 1.1 event model. Most events are sent to the canvas for processing. + */ +class EventCatcher extends Object implements ComponentListener, FocusListener, + KeyListener, MouseListener, MouseMotionListener, WindowListener { + + // The canvas associated with this event catcher + Canvas3D canvas; + static final boolean DEBUG = false; + boolean stopped = false; + + /** + * flags for event listeners + */ + boolean componentEvents = false; + boolean focusEvents = false; + boolean keyEvents = false; + boolean mouseEvents = false; + boolean mouseMotionEvents = false; + boolean mouseListenerAdded = false; + + EventCatcher(Canvas3D c) { + canvas = c; + + if (VirtualUniverse.mc.isD3D()) { + enableComponentEvents(); + enableKeyEvents(); + } + } + + + void enableComponentEvents() { + + if (!componentEvents) { + canvas.addComponentListener(this); + componentEvents = true; + } + } + + /* + void disableComponentEvents() { + if (componentEvents) { + canvas.removeComponentListener(this); + componentEvents = false; + } + } + */ + + void enableFocusEvents() { + if (!focusEvents) { + canvas.addFocusListener(this); + focusEvents = true; + } + } + + + void disableFocusEvents() { + if (focusEvents) { + canvas.removeFocusListener(this); + focusEvents = false; + } + } + + void enableKeyEvents() { + if (!keyEvents) { + canvas.addKeyListener(this); + keyEvents = true; + // listen for mouseEntered events for keyboard focusing + if (!mouseListenerAdded) { + canvas.addMouseListener(this); + mouseListenerAdded = true; + } + } + } + + void disableKeyEvents() { + if (keyEvents) { + canvas.removeKeyListener(this); + keyEvents = false; + // listen for mouseEntered events for keyboard focusing + if (!mouseEvents) { + if (mouseListenerAdded) { + canvas.removeMouseListener(this); + mouseListenerAdded = false; + } + } + } + } + + + + void enableMouseEvents() { + if (!mouseEvents) { + mouseEvents = true; + if (!mouseListenerAdded) { + canvas.addMouseListener(this); + mouseListenerAdded = true; + } + } + } + + void disableMouseEvents() { + if (mouseEvents) { + mouseEvents = false; + if (!keyEvents) { + if (mouseListenerAdded) { + canvas.removeMouseListener(this); + mouseListenerAdded = false; + } + } + } + } + + void enableMouseMotionEvents() { + if (!mouseMotionEvents) { + canvas.addMouseMotionListener(this); + mouseMotionEvents = true; + } + } + + + void disableMouseMotionEvents() { + if (mouseMotionEvents) { + canvas.removeMouseMotionListener(this); + mouseMotionEvents = false; + } + } + + + public void componentResized(ComponentEvent e) { + if (e.getSource() == canvas) { + canvas.sendEventToBehaviorScheduler(e); + canvas.visible = true; + if (VirtualUniverse.mc.isD3D()) { + canvas.notifyD3DPeer(Canvas3D.RESIZE); + } + canvas.evaluateActive(); + repaint(); + if (DEBUG) { + System.out.println(e); + } + } + } + + public void componentHidden(ComponentEvent e) { + canvas.sendEventToBehaviorScheduler(e); + canvas.visible = false; + if (DEBUG) { + System.out.println(e); + } + } + + public void componentMoved(ComponentEvent e) { + canvas.sendEventToBehaviorScheduler(e); + if (VirtualUniverse.mc.isD3D()) { + canvas.notifyD3DPeer(Canvas3D.RESIZE); + } + repaint(); + if (DEBUG) { + System.out.println(e); + } + } + + public void componentShown(ComponentEvent e) { + canvas.sendEventToBehaviorScheduler(e); + canvas.visible = true; + canvas.evaluateActive(); + repaint(); + if (DEBUG) { + System.out.println(e); + } + } + + public void focusGained(FocusEvent e) { + canvas.sendEventToBehaviorScheduler(e); + if (DEBUG) { + System.out.println(e); + } + } + + public void focusLost(FocusEvent e) { + canvas.sendEventToBehaviorScheduler(e); + if (DEBUG) { + System.out.println(e); + } + } + + public void keyTyped(KeyEvent e) { + canvas.sendEventToBehaviorScheduler(e); + if (DEBUG) { + System.out.println(e); + } + } + + public void keyPressed(KeyEvent e) { + canvas.sendEventToBehaviorScheduler(e); + + if (VirtualUniverse.mc.isD3D() && + e.isAltDown() && + (e.getKeyCode() == KeyEvent.VK_ENTER)) { + canvas.notifyD3DPeer(Canvas3D.TOGGLEFULLSCREEN); + } + + if (DEBUG) { + System.out.println(e); + } + } + + public void keyReleased(KeyEvent e) { + canvas.sendEventToBehaviorScheduler(e); + if (stopped) { + stopped = false; + } else { + stopped = true; + } + if (DEBUG) { + System.out.println(e); + } + } + + public void mouseClicked(MouseEvent e) { +// if (keyEvents && +// (VirtualUniverse.mc.getRenderingAPI() != +// MasterControl.RENDER_OPENGL_SOLARIS)) { +// // bug 4362074 +// canvas.requestFocus(); +// } + + if (mouseEvents) { + canvas.sendEventToBehaviorScheduler(e); + } + if (DEBUG) { + System.out.println(e); + } + } + + public void mouseEntered(MouseEvent e) { +// if (keyEvents && +// (VirtualUniverse.mc.getRenderingAPI() == +// MasterControl.RENDER_OPENGL_SOLARIS)) { +// // bug 4362074 +// canvas.requestFocus(); +// } + if (mouseEvents) { + canvas.sendEventToBehaviorScheduler(e); + } + if (DEBUG) { + System.out.println(e); + } + } + + public void mouseExited(MouseEvent e) { + if (mouseEvents) + canvas.sendEventToBehaviorScheduler(e); + if (DEBUG) { + System.out.println(e); + } + } + + public void mousePressed(MouseEvent e) { + if (mouseEvents) + canvas.sendEventToBehaviorScheduler(e); + if (DEBUG) { + System.out.println(e); + } + } + + public void mouseReleased(MouseEvent e) { + if (mouseEvents) + canvas.sendEventToBehaviorScheduler(e); + if (DEBUG) { + System.out.println(e); + } + } + + public void mouseDragged(MouseEvent e) { + canvas.sendEventToBehaviorScheduler(e); + if (DEBUG) { + System.out.println(e); + } + } + + public void mouseMoved(MouseEvent e) { + canvas.sendEventToBehaviorScheduler(e); + if (DEBUG) { + System.out.println(e); + } + } + + public void windowActivated(WindowEvent e) { + windowOpened(e); + } + + public void windowClosed(WindowEvent e) { + if (DEBUG) { + System.out.println(e); + } + canvas.sendEventToBehaviorScheduler(e); + canvas.visible = false; + canvas.evaluateActive(); + } + + public void windowClosing(WindowEvent e) { + if (DEBUG) { + System.out.println(e); + } + canvas.sendEventToBehaviorScheduler(e); + canvas.visible = false; + canvas.evaluateActive(); + } + + public void windowDeactivated(WindowEvent e) { + if (DEBUG) { + System.out.println(e); + } + canvas.sendEventToBehaviorScheduler(e); + } + + public void windowDeiconified(WindowEvent e) { + if (DEBUG) { + System.out.println(e); + } + canvas.sendEventToBehaviorScheduler(e); + canvas.visible = true; + if (canvas.view != null) + canvas.view.sendEventToSoundScheduler(e); + canvas.evaluateActive(); + repaint(); + } + + public void windowIconified(WindowEvent e) { + if (DEBUG) { + System.out.println(e); + } + canvas.sendEventToBehaviorScheduler(e); + canvas.visible = false; + if (canvas.view != null) + canvas.view.sendEventToSoundScheduler(e); + canvas.evaluateActive(); + } + + public void windowOpened(WindowEvent e) { + if (DEBUG) { + System.out.println(e); + } + canvas.sendEventToBehaviorScheduler(e); + canvas.visible = true; + canvas.evaluateActive(); + repaint(); + } + + void repaint() { + if (canvas.view != null) { + canvas.view.repaint(); + } + } + + void reset() { + focusEvents = false; + keyEvents = false; + componentEvents = false; + mouseEvents = false; + mouseMotionEvents = false; + mouseListenerAdded = false; + stopped = false; + } +} + diff --git a/src/classes/share/javax/media/j3d/ExceptionStrings.properties b/src/classes/share/javax/media/j3d/ExceptionStrings.properties new file mode 100644 index 0000000..a556194 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ExceptionStrings.properties @@ -0,0 +1,892 @@ +Alpha0=Alpha: time <= 0 +Appearance0=Appearance: no capability to set material +Appearance1=Appearance: no capability to get material +Appearance2=Appearance: no capability to set texture +Appearance3=Appearance: no capability to get texture +Appearance4=Appearance: no capability to set textureAttributes +Appearance5=Appearance: no capability to get textureAttributes +Appearance6=Appearance: no capability to set coloringAttributes +Appearance7=Appearance: no capability to get coloringAttributes +Appearance8=Appearance: no capability to set transparencyAttributes +Appearance9=Appearance: no capability to get transparencyAttributes +Appearance10=Appearance: no capability to set renderingAttributes +Appearance11=Appearance: no capability to get renderingAttributes +Appearance12=Appearance: no capability to set polygonAttributes +Appearance13=Appearance: no capability to get polygonAttributes +Appearance14=Appearance: no capability to set lineAttributes +Appearance15=Appearance: no capability to get lineAttributes +Appearance16=Appearance: no capability to set pointAttributes +Appearance17=Appearance: no capability to get pointAttributes +Appearance18=Appearance: no capability to set TexCoordGeneraion +Appearance19=Appearance: no capability to get TexGen +Appearance20=Appearance: no capability to set TextureUnitState +Appearance21=Appearance: no capability to get TextureUnitState +BoundingSphere0=BoundingSphere( Bounds ) unrecognized bounds object +BoundingSphere2=set( Bounds) unrecognized bounds type +BoundingSphere3=BoundingSphere.combine( Bounds) unrecognized bounds type +BoundingSphere4=BoundingSphere.combine( Bounds[]) unrecognized bounds type +BoundingSphere5=transform( Bounds, trans) unrecognized bounds type +BoundingSphere6=sphere.intersect(Bounds ) bounds type not recognized= +BoundingSphere7=sphere.intersect(Bounds[]) bounds type not recognized= +BoundingSphere8=BoundingSphere.intersect(Bounds, newBoundingSphere) bounds type not recognized= +BoundingSphere9=BoundingSphere.intersect(Bounds[], newBoundingSphere) bounds type not recognized= +BoundingSphere10=sphere.closestIntersection(Bounds[]) unrecognized bounds type +Background0=Background: no capability to set color +Background2=Background: no capability to get color +Background3=Background: no capability to set image +Background4=Background: no capability to get image +Background5=Background: no capability to set background geometry +Background6=Background: no capability to get background geometry +Background7=Background: no capability to set application bounds +Background8=Background: no capability to get application bounds +Background9=Background: no capability to set image scale mode +Background10=Background: no capability to get image scale mode +Background11=Background: illegal image scale mode +AlternateAppearance0=AlternateAppearance: no capability to write appearance +AlternateAppearance2=AlternateAppearance: no capability to read appearance +AlternateAppearance3=AlternateAppearance: no capability to write influencing bounds +AlternateAppearance4=AlternateAppearance: no capability to read influencing bounds +AlternateAppearance7=AlternateAppearance: no capability to write scope +AlternateAppearance8=AlternateAppearance: no capability to read scope +AlternateAppearance9=AlternateAppearance: no capability to insert scope +AlternateAppearance10=AlternateAppearance: no capability to remove scope +AlternateAppearance11=AlternateAppearance: no capability to read scopes +AlternateAppearance12=AlternateAppearance: no capability to append scope +AlternateAppearanceRetained13=AlternateAppearance: Immediate mode alternate appearance may not be in scene graph +AlternateAppearanceRetained14=AlternateAppearance: Immediate mode appearance may not be in scene graph +AlternateAppearanceRetained15=AlternateAppearance: illegal node under SharedGroup Branch +AlternateAppearanceRetained16=AlternateAppearance: illegal node under Background geometry Branch +AudioDeviceEnumerator0=No more audio devices +AuralAttributes0=AuralAttributes: no capability to set attribute gain +AuralAttributes1=AuralAttributes: no capability to get attribute gain +AuralAttributes2=AuralAttributes: no capability to set rolloff +AuralAttributes3=AuralAttributes: no capability to get rolloff +AuralAttributes4=AuralAttributes: no capability to set reflection coefficient +AuralAttributes5=AuralAttributes: no capability to set reverberation delay +AuralAttributes7=AuralAttributes: no capability to get reverberation delay +AuralAttributes8=AuralAttributes: no capability to set reverberation order +AuralAttributes9=AuralAttributes: no capability to get reverberation order +AuralAttributes10=AuralAttributes: no capability to set distance filter +AuralAttributes12=AuralAttributes: no capability to get distance filter +AuralAttributes15=AuralAttributes: no capability to set Doppler scale factor +AuralAttributes17=AuralAttributes: no capability to get Doppler scale factor +AuralAttributes19=AuralAttributes: no capability to set Doppler velocity +AuralAttributes20=AuralAttributes: no capability to get Doppler velocity +AuralAttributes21=AuralAttributes: no capability to get reflection coefficient +AuralAttributes22=AuralAttributes: no capability to set reflection delay +AuralAttributes23=AuralAttributes: no capability to get reflection delay +AuralAttributes24=AuralAttributes: no capability to set reverberation coefficient +AuralAttributes25=AuralAttributes: no capability to get reverberation coefficient +AuralAttributes26=AuralAttributes: no capability to set reverberation bounds +AuralAttributes27=AuralAttributes: no capability to get reverberation bounds +AuralAttributes28=AuralAttributes: no capability to set decay time +AuralAttributes29=AuralAttributes: no capability to get decay time +AuralAttributes30=AuralAttributes: no capability to set decay filter +AuralAttributes31=AuralAttributes: no capability to get decay filter +AuralAttributes32=AuralAttributes: no capability to set diffusion +AuralAttributes33=AuralAttributes: no capability to get diffusion +AuralAttributes34=AuralAttributes: no capability to set density +AuralAttributes35=AuralAttributes: no capability to get density +Behavior0=wakeupOn must be called from initialize or processStimulus +Behavior1=illegal schedulingInterval value +BehaviorRetained0=Behavior: illegal node under Background geometry Branch +BehaviorRetained1=Behavior: illegal node under SharedGroup Branch +BehaviorRetained2=Behavior: wakeupCondition criteria cannot be null +ConeSound0=ConeSound: no capability to set distance attenuation +ConeSound2=ConeSound: no capability to get distance attenuation +ConeSound3=ConeSound: no capability to set direction +ConeSound5=ConeSound: no capability to get direction +ConeSound6=ConeSound: no capability to set cone attenuation +ConeSound9=ConeSound: no capability to get cone attenuation +ConeSound10=ConeSound: no capability to get max distance attenuation +BoundingBox0=BoundingBox( Bounds) unrecognized bounds type +BoundingBox1=BoundingBox( Bounds[]) unrecognized bounds type +BoundingBox3=BoundingBox.combine( Bounds) unrecognized bounds type +BoundingBox4=BoundingBox.combine( Bounds[]) unrecognized bounds type +BoundingBox5=transform( Bounds, trans) unrecognized bounds type +BoundingBox6=intersect(Bounds[]) unrecognized bounds type +BoundingBox7=BoundingBox.intersect(Bounds, newBoundingBox) unrecognized bounds type +BoundingBox9=box.closestIntersection(Bounds[]) unrecognized bounds type +BoundingLeaf0=BoundingLeaf: no capability to write bounding region +BoundingLeaf1=BoundingLeaf: no capability to read bounding region +BoundingLeafRetained0=BoundingLeaf: illegal node under Background geometry Branch +BoundingLeafRetained1=BoundingLeaf: illegal node under SharedGroup Branch +BackgroundRetained0=Background: Background geometry BranchGroup cannot be referenced by multiple Background node +BackgroundRetained1=Background: Immediate mode background may not be in scene graph +BackgroundRetained3=Background: Background geometry BranchGroup is not at the root of a branch graph +BackgroundRetained4=Background: Background geometry BranchGroup cannot be attached to a locale +BackgroundRetained5=Background: illegal node under Background geometry Branch +BackgroundRetained6=Background: illegal node under SharedGroup Branch +Canvas3D0=Canvas3D: Cannot swap buffers when the renderer is running +Canvas3D1=Canvas3D: Not in off-screen mode +Canvas3D2=Canvas3D: Off-screening rendering is in progress +Canvas3D3=Canvas3D: The specified ImageComponent2D is used by more than on Canvas3D +Canvas3D7=*** ERROR: Canvas3D constructed with a null GraphicsConfiguration +Canvas3D8=Canvas3D: The width of the associated Screen3D's size is <= 0 +Canvas3D9=Canvas3D: The height of the associated Screen3D's size is <= 0 +Canvas3D10=Canvas3D: Off-screen buffer is null +Canvas3D11=Canvas3D: Java3D renderer is stopped +Canvas3D12=Canvas3D: The physical width of the associated Screen3D is <= 0 +Canvas3D13=Canvas3D: The physical height of the associated Screen3D is <= 0 +Canvas3D14=Canvas3D: Illegal operation in off-screen mode +Canvas3D15=Canvas3D: For offscreen rendering, byReference image should be an instance of BufferedImage +Canvas3D16=Canvas3D: Offscreen rendering does not support FORMAT_CHANNEL8 +Canvas3D17=Canvas3D: GraphicsConfiguration is not compatible with Canvas3D +Canvas3D18=*** This will cause a NullPointerException in a subsequent release +Canvas3D19=Canvas3D: null GraphicsConfiguration +Canvas3D20=Canvas3D does not support serialization +Canvas3D21=*** ERROR: GraphicsConfiguration not created with GraphicsConfigTemplate3D +Canvas3D22=*** This will cause an IllegalArgumentException in a subsequent release +BoundingPolytope0=BoundingPolytope( Bounds) unrecognized bounds object +BoundingPolytope1=BoundingPolytope( Bounds) unrecognized bounds type +BoundingPolytope2=set( Bounds) unrecognized bounds type +BoundingPolytope3=combine( Bounds) unrecognized bounds type +BoundingPolytope4=BoundingPolytope.combine( Bounds ) unrecognized bounds type +BoundingPolytope5=BoundingPolytope.transform( Bounds, transform ) unrecognized bounds type +BoundingPolytope6=intersect(Bounds[]) unrecognized bounds type +BoundingPolytope7=BoundingPolytope.intersect(Bounds[]) unrecognized bounds type +BoundingPolytope8=intersect(Bounds, BoundingPolytope) bounds type not recognized= +BoundingPolytope10=sphere.closestIntersection(Bounds[]) unrecognized bounds object +BoundingPolytope11=Must specify at least 4 planes +BranchGroup0=Cannot compile a live BranchGroup +BranchGroup1=BranchGroup: no capability to detach +BranchGroup2=Group: no capability to write children +BranchGroup3=Picking can only work if BranchGroup is alive +CachedFrustum0=Frustum must have aleast 6 planes +CachedFrustum1=Frustum must have 6 planes +Clip0=Clip: no capability to set back distance +Clip1=Clip: no capability to get back distance +Clip2=Clip: no capability to set application bounds +Clip3=Clip: no capability to get application bounds +ColoringAttributes0=ColoringAttributes: no capability to set color +ColoringAttributes2=ColoringAttributes: no capability to get Color +ColoringAttributes3=ColoringAttributes: no capability to set shademodel +ColoringAttributes4=ColoringAttributes: no capability to get shademodel +CompressedGeometry0=CompressedGeometry: start+size exceeds geometry length +CompressedGeometry1=CompressedGeometry: no capability to get byte count +CompressedGeometry2=CompressedGeometry: no capability to get geometry header +CompressedGeometry3=CompressedGeometry: no capability to get geometry +CompressedGeometry4=CompressedGeometry: target buffer is too small +CompressedGeometry5=CompressedGeometry: no capability to get geometry +CompressedGeometry6=CompressedGeometry: no capability to get data reference +CompressedGeometry7=CompressedGeometry: cannot directly access data in byReference mode +CompressedGeometry8=CompressedGeometry: must be in byReference mode to use this method +CompressedGeometry9=CompressedGeometry: NIO buffer support is not currently implemented +ClipRetained0=Clip: Immediate mode clip may not be in scene graph +ClipRetained1=Clip: illegal node under Background geometry Branch +ClipRetained2=Clip: illegal node under SharedGroup Branch +DepthComponentInt0=DepthComponentInt: no capability to get data +DepthComponent0=DepthComponent: no capability to get size +ImageComponentRetained0=ImageComponent: illegal width value +ImageComponentRetained1=ImageComponent: illegal height value +ImageComponentRetained2=ImageComponent: illegal depth value +ImageComponentRetained3=ImageComponent: illegal format value +ExponentialFog0=ExponentialFog: no capability to write density +ExponentialFog1=ExponentialFog: no capability to read density +Fog0=Fog: no capability to write color +Fog2=Fog: no capability to read color +Fog3=Fog: no capability to write influencing bounds +Fog4=Fog: no capability to read influencing bounds +Fog7=Fog: no capability to write fog's scope +Fog8=Fog: no capability to read fog's scope +Fog9=Fog: no capability to insert scope +Fog10=Fog: no capability to remove scope +Fog11=Fog: no capability to read scopes +Fog12=Fog: no capability to append scope +DirectionalLight0=Light: no capability to set light's state +DirectionalLight1=Light: no capability to set light's direction +DirectionalLight2=Light: no capability to read light's direction +FogRetained0=Fog: Immediate mode fog may not be in scene graph +FogRetained1=Fog: illegal node under SharedGroup Branch +DepthComponentFloat0=DepthComponentFloat: no capability to get data +FontExtrusion0=FontExtrusion:invalid shape- non-monotonic +FontExtrusion1=FontExtrusion: invalid shape- shape must start or end at x = 0.0f +FontExtrusion2=FontExtrusion:method not implemented +FontExtrusion3=FontExtrusion:invalid shape- multiple contours +Group0=Group: no capability to set bounds +Group1=Group: no capability to read user bounds +Group2=SharedGroup must be referenced through a link node +Group3=Group: only BranchGroup nodes may be set +Group4=Group: no capability to detach BranchGroup +Group6=Group: only a BranchGroup node may be inserted +Group7=Group: only a BranchGroup node may be removed +Group9=Group: no capability to read children +Group12=Group: only a BranchGroup node may be added +Group13=Group: no capability to set children +Group14=Group: no capability to insert children +Group15=Group: no capability to remove children +Group16=Group: no capability to append children +GeneralizedStrip0=GeneralizedStrip: strip ended incompletely +GeometryArray0=GeometryArray: vertexFormat must include COORDINATES +GeometryArray1=GeometryArray: no capability to get vertex count +GeometryArray2=GeometryArray: no capability to get vertex format +GeometryArray3=GeometryArray: no capability to set coordinate +GeometryArray7=GeometryArray: no capability to set coordinates +GeometryArray15=GeometryArray: no capability to set color +GeometryArray21=GeometryArray: no capability to set colors +GeometryArray33=GeometryArray: no capability to set normal +GeometryArray35=GeometryArray: no capability to set normals +GeometryArray39=GeometryArray: no capability to set texture coordinate +GeometryArray42=GeometryArray: no capability to set texture coordinates +GeometryArray48=GeometryArray: no capability to read coordinate +GeometryArray52=GeometryArray: no capability to read coordinates +GeometryArray56=GeometryArray: no capability to read color +GeometryArray62=GeometryArray: no capability to read colors +GeometryArray68=GeometryArray: no capability to read normal +GeometryArray70=GeometryArray: no capability to read normals +GeometryArray72=GeometryArray: no capability to read texture coordinate +GeometryArray75=GeometryArray: no capability to read texture coordinates +GeometryArray76=GeometryArray: has no colors +GeometryArray77=GeometryArray: has no normals +GeometryArray78=GeometryArray: has no normals +GeometryArray79=GeometryArray: has no texture coordinates +GeometryArray80=GeometryArray: INTERLEAVED flag set without setting BY_REFERENCE flag +GeometryArray81=GeometryArray: no capability to update geometry data +GeometryArray82=GeometryArray: cannot directly access data in BY_REFERENCE mode +GeometryArray83=GeometryArray: must be in BY_REFERENCE mode to use this method +GeometryArray84=GeometryArray: cannot access individual array references in INTERLEAVED mode +GeometryArray85=GeometryArray: must be in INTERLEAVED mode to use this method +GeometryArray86=GeometryArray: no capability to write data reference +GeometryArray87=GeometryArray: no capability to read data reference +GeometryArray88=GeometryArray: no capability to set valid vertex count +GeometryArray89=GeometryArray: no capability to get valid vertex count +GeometryArray90=GeometryArray: no capability to set initial index +GeometryArray91=GeometryArray: no capability to get initial index +GeometryArray92=GeometryArray: must be in COLOR_3 mode to use this method +GeometryArray93=GeometryArray: must be in COLOR_4 mode to use this method +GeometryArray94=GeometryArray: must be in TEXTURE_COORDINATE_2 mode to use this method +GeometryArray95=GeometryArray: must be in TEXTURE_COORDINATE_3 mode to use this method +GeometryArray96=GeometryArray: vertex count < 0 +GeometryArray97=GeometryArray: initial index < 0 +GeometryArray98=GeometryArray: array reference is already non-null +GeometryArray99=GeometryArray: vertex array length is incorrect +GeometryArray100=GeometryArray: initial vertex index + valid vertex count > vertex count +GeometryArray101=GeometryArray: initial color index + valid vertex count > vertex count +GeometryArray102=GeometryArray: initial normal index + valid vertex count > vertex count +GeometryArray103=GeometryArray: initial tex coord index + valid vertex count > vertex count +GeometryArray104=GeometryArray: initial coord index + valid vertex count > vertex count +GeometryArray105=GeometryArray: must not be in BY_REFERENCE mode to use this method +GeometryArray106=GeometryArray: texCoord set mapping is not specified +GeometryArray107=GeometryArray: must specify at least one set of tex coord +GeometryArray108=GeometryArray: invalid texCoord set mapping +GeometryArray109=GeometryArray: must be in TEXTURE_COORDINATE_4 mode to use this method +GeometryArray110=GeometryArray: validVertexCount should be greater than or equal to zero +GeometryArray111=GeometryArray: normal array length is incorrect +GeometryArray112=GeometryArray: color array length is incorrect +GeometryArray113=GeometryArray: texture coord array length is incorrect +GeometryArray114=GeometryArray: interleaved array length is incorrect +GeometryArray115=GeometryArray: NIO buffer is null +GeometryArray116=GeometryArray: Illegal NIO buffer type +GeometryArray117=GeometryArray: USE_NIO_BUFFER flag set without setting BY_REFERENCE flag +GeometryArray118=GeometryArray: must be in USE_NIO_BUFFER mode to use this method +GeometryArray119=GeometryArray: must not be in USE_NIO_BUFFER mode to use this method +GeometryArray120=GeometryArray: must be direct nio buffer +GeometryArray121=GeometryArray: None of the TEXTURE_COORDINATE bits are set in vertexFormat +GeometryArray122=GeometryArray: NORMALS bit is not set in vertexFormat +GeometryArray123=GeometryArray: None of the COLOR bits are set in vertexFormat +GeometryDecompressor0=GeometryDecompressor: start+length > data array size +GeometryDecompressor1=GeometryDecompressor: bad delta normal in compressed buffer +GeometryDecompressorRetained0=GeometryDecompressorRetained: bad buffer data type +GeometryDecompressorRetained1=GeometryDecompressorRetained: unexpected vertexFormat/SetState in compressed buffer +GeometryDecompressorRetained2=GeometryDecompressorRetained: unexpected color in compressed buffer +GeometryDecompressorRetained3=GeometryDecompressorRetained: unexpected normal in compressed buffer +GeometryDecompressorRetained4=GeometryDecompressorRetained: bad buffer data type +GeometryDecompressorShape3D0=GeometryDecompressorShape3D: bad triangle output type +GeometryDecompressorShape3D1=GeometryDecompressorShape3D: bad buffer data type +GroupRetained0=Group.setChild: child already has a parent +GroupRetained1=Group.insertChild: child already has a parent +GroupRetained2=Group.addChild: child already has a parent +GeometryRetained1=Geometry - intersect : Sorry! This method is not supported at present +Light0=Light: no capability to set light's state +Light1=Light: no capability to read light's state +Light2=Light: no capability to write light's color +Light3=Light: no capability to read light's color +Light4=Light: no capability to write light's scope +Light5=Light: no capability to read light's scope +Light6=Light: no capability to insert scope +Light7=Light: no capability to remove scope +Light8=Light: no capability to read scopes +Light9=Light: no capability to append scope +Light11=Light: no capability to write influencing bounds +Light12=Light: no capability to read influencing bounds +GeometryStripArray0=GeometryStripArray: no capability to get number of strips +GeometryStripArray1=GeometryStripArray: no capability to get strip vertex counts +GeometryStripArray2=GeometryStripArray: no capability to set strip vertex counts +GeometryStripArray3=GeometryStripArray: initial vertex index + valid vertex count > vertex count +GeometryStripArray4=GeometryStripArray: initial color index + valid vertex count > vertex count +GeometryStripArray5=GeometryStripArray: initial normal index + valid vertex count > vertex count +GeometryStripArray6=GeometryStripArray: initial tex coord index + valid vertex count > vertex count +GeometryStripArray7=GeometryStripArray: initial coord index + valid vertex count > vertex count +GraphicsContext3D11=Background: Scene Graph background may not be in immediate mode +GraphicsContext3D12=Fog: Scene Graph fog may not be in immediate mode +GraphicsContext3D13=GraphicsContext3D: Light object is null +GraphicsContext3D14=Light: Scene Graph light may not be in immediate mode +GraphicsContext3D17=GraphicsContext3D: setSound object is null +GraphicsContext3D21=readRaster: Scene Graph Raster may not be in immediate mode +GraphicsContext3D22=Background: Background geometry can not be used in immediate mode context +GraphicsContext3D23=Sound: Scene Graph sound may not be in immediate mode +GraphicsContext3D25=ModelClip: Scene Graph ModelClip may not be in immediate mode +GraphicsContext3D26=Shape3D: Scene Graph Shape3D may not be in immediate mode +GraphicsContext3D27=ImageComponent2D size is smaller than read Raster size +GraphicsContext3D28=DepthComponent size is smaller than read Raster size +ImageComponent0=ImageComponent: no capability to get width +ImageComponent1=ImageComponent: no capability to get height +ImageComponent2=ImageComponent: no capability to get format +GeometryStripArrayRetained0=Illegal stripVertexCounts +ImageComponent2D0=ImageComponent2D: no capability to get image +ImageComponent2D1=ImageComponent2D: no capability to set image +ImageComponent2D2=ImageComponent2D: must be in BY_REFERENCE mode to use this method +ImageComponent2D3=ImageComponent2D: illegal dimension +ImageComponent2D4=ImageComponent2D: must be in BY_COPY mode to use this method +ImageComponent2D5=ImageComponent2D: image is not an instanceof of BufferedImage +ImageComponent3D0=ImageComponent3D: no capability to get depth +ImageComponent3D1=ImageComponent3D - incompatible depth +ImageComponent3D2=ImageComponent3D - incompatible width +ImageComponent3D3=ImageComponent3D: no capability to get image +ImageComponent3D4=ImageComponent3D - incompatible height +ImageComponent3D5=ImageComponent3D: no capability to set image +ImageComponent3D6=ImageComponent3D: must be in BY_REFERENCE mode to use this method +ImageComponent3D7=ImageComponent3D: illegal dimension +ImageComponent3D8=ImageComponent3D: must be in BY_COPY mode to use this method +ImageComponent3D9=ImageComponent3D: image is not an instanceof of BufferedImage +ImageComponent2DRetained0=ImageComponent2D - incompatible width +ImageComponent2DRetained1=ImageComponent2D - incompatible height +ImageComponent2DRetained2=Raster does not support FORMAT_CHANNEL8 +ImageComponent3DRetained0=ImageComponent3D: image is not an instanceof of BufferedImage +Locale0=Locale.addBranchGraph: Branch Group already has a parent +Locale1=Locale: no capability to detach BranchGroup +Locale3=Locale.replaceBranchGraph: Branch Group already has a parent +Locale4=Locale has been removed from its VirtualUniverse +IndexedLineStripArray0=IndexedLineStripArray: illegal vertexCount +IndexedLineStripArray1=IndexedLineStripArray: illegal indexCount +IndexedGeometryArray0=IndexedGeometryArray: no capability to get index count +IndexedGeometryArray1=IndexedGeometryArray: no capability to set coordinate index +IndexedGeometryArray3=IndexedGeometryArray: no capability to set color index +IndexedGeometryArray5=IndexedGeometryArray: no capability to set normal index +IndexedGeometryArray7=IndexedGeometryArray: no capability to set texture coordinate index +IndexedGeometryArray9=IndexedGeometryArray: no capability to get coordinate index +IndexedGeometryArray11=IndexedGeometryArray: no capability to get color index +IndexedGeometryArray13=IndexedGeometryArray: no capability to get normal index +IndexedGeometryArray15=IndexedGeometryArray: no capability to get texture coordinate index +IndexedGeometryArray16=IndexedGeometryArray: no capability to set valid index count +IndexedGeometryArray17=IndexedGeometryArray: no capability to get valid index count +IndexedGeometryArray18=IndexedGeometryArray: no capability to set initial index index +IndexedGeometryArray19=IndexedGeometryArray: no capability to get initial index index +IndexedGeometryArray20=IndexedGeometryArray: initial index < 0 +IndexedGeometryArray21=IndexedGeometryArray: valid index < 0 +IndexedGeometryArray22=IndexedGeometryArray: initial index Index +valid index count > index count +IndexedGeometryArray23=IndexedGeometryArray: index coord value greater than the array length +IndexedGeometryArray24=IndexedGeometryArray: index color value greater than the array length +IndexedGeometryArray25=IndexedGeometryArray: index texcoord value greater than the array length +IndexedGeometryArray26=IndexedGeometryArray: index normal value greater than the array length +IndexedGeometryArray27=IndexedGeometryArray: index value less than zero +IndexedLineArray0=IndexedLineArray: illegal vertexCount +IndexedLineArray1=IndexedLineArray: illegal indexCount +IndexedGeometryArrayRetained0=execute() called on indexed geometry +IndexedGeometryStripArray0=IndexedGeometryStripArray: no capability to get number of strips +IndexedGeometryStripArray1=IndexedGeometryStripArray: no capability to get strip index counts +IndexedGeometryStripArray2=IndexedGeometryStripArray: no capability to set strip index counts +LineAttributes0=LineAttributes: illegal line pattern +LineAttributes1=LineAttributes: no capability to set line width +LineAttributes2=LineAttributes: no capability to get line width +LineAttributes3=LineAttributes: no capability to set line pattern +LineAttributes4=setLinePattern: illegal line pattern +LineAttributes5=LineAttributes: no capability to get line pattern +LineAttributes6=LineAttributes: no capability to set line antialiasing +LineAttributes7=LineAttributes: no capability to get line antialiasing +LineAttributes8=LineAttributes: no capability to set line pattern mask +LineAttributes9=LineAttributes: no capability to get line pattern mask +LineAttributes10=LineAttributes: no capability to set line pattern scale factor +LineAttributes11=LineAttributes: no capability to get line pattern scale factor +LineArray0=LineArray: illegal vertexCount +IndexedGeometryStripArrayRetained0=Illegal stripIndexCounts +IndexedLineArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +IndexedLineStripArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +IndexedLineStripArrayRetained1=IndexedLineStripArray: stripVertexCounts element less than 2 +IndexedPointArray0=IndexedPointArray: illegal vertexCount +IndexedPointArray1=IndexedPointArray: illegal indexCount +IndexedPointArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +IndexedQuadArray0=IndexedQuadArray: illegal vertexCount +IndexedQuadArray1=IndexedQuadArray: illegal indexCount +IndexedQuadArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +IndexedTriangleArray0=IndexedTriangleArray: illegal vertexCount +IndexedTriangleArray1=IndexedTriangleArray: illegal indexCount +IndexedTriangleArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +IndexedTriangleFanArray0=IndexedTriangleFanArray: illegal vertexCount +IndexedTriangleFanArray1=IndexedTriangleFanArray: illegal indexCount +IndexedTriangleFanArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +IndexedTriangleFanArrayRetained1=IndexedTriangleFanArray: stripVertexCounts element less than 3 +IndexedTriangleStripArray0=IndexedTriangleStripArray: illegal vertexCount +IndexedTriangleStripArray1=IndexedTriangleStripArray: illegal indexCount +IndexedTriangleStripArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +IndexedTriangleStripArrayRetained1=IndexedTriangleStripArray: stripVertexCounts element less than 3 +LightRetained0=Light: Immediate mode light may not be in scene graph +LineStripArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +LineStripArrayRetained1=stripVertexCounts element less than 2 +LineArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +LineStripArray0=LineStripArray: illegal vertexCount +Link0=Link: no capability to set SharedGroup +Link1=Link: no capability to get SharedGroup +LinkRetained0=Link: illegal node under Background geometry Branch +LinkRetained1=Link: Scene Graph are not directed acyclic graphs +LinearFog0=LinearFog: no capability to write distance +LinearFog1=LinearFog: no capability to read distance +PointArray0=PointArray: illegal vertexCount +Material0=Material: no capability to set component +Material2=Material: no capability to get component +Material3=Material: no capability to set color target +Material4=Material: no capability to get color target +Material15=Material: no capability to set lighting +Material16=Material: no capability to get lighting +Morph0=Group: no capability to set bounds +Morph1=Group: no capability to read user bounds +Morph2=Morph: no capability to set geometryArrays +Morph3=Morph: no capability to get geometryArrays +Morph4=Morph: no capability to set appearance +Morph5=Morph: no capability to get appearance +Morph6=Morph: no capability to allow intersect +Morph8=Morph: no capability to set morph weight vector +Morph9=Morph: no capability to get morph weight vector +Morph10=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +Morph11=Morph: no capability to set appearance override enable +Morph12=Morph: no capability to get appearance override enable +MediaContainer0=MediaContainer: setURL - bad URL +MediaContainer1=MediaContainer: no capability to set cached flag +MediaContainer2=MediaContainer: no capability to get cached flag +MediaContainer3=MediaContainer: no capability to set URL +MediaContainer4=MediaContainer: no capability to get URL +MediaContainer5=MediaContainer: only one type of sound data may be simultaneously set non-null +MorphRetained0=Morph: Incorrect number of GeometryArrays +MorphRetained1=Morph: All GeometryArrays must have same vertexFormat, same vertexCount and same texCoordSetCount +MorphRetained2=Morph: All GeometryArrays must be of same type +MorphRetained5=Invalid SceneGraphPath encountered : localToVworld is null. +MorphRetained7=Morph: number of weights not same as number of GeometryArrays +MorphRetained8=Morph: sum of all weights is NOT 1.0 +Node0=Cannot get the parent of a live or compiled node +Node1=Node: no capability to set bounds +Node2=Node: no capability to read user bounds +Node3=Node: no capability to read Pickable +Node4=Node: no capability to set Collidable +Node5=Node: no capability to set user auto compute bounds +Node6=Node: no capability to read user auto compute bounds +Node7=Node: local to vworld transform is undefined for a node that is not part of a live scene graph +Node8=Node: no capability to read local to vworld transform +Node9=Node: Invalid geometric bounds +Node11=cloneTree: should be overridden in child +Node12=cloneNode must be defined in subclass +Node13=Node: Cannot clone a live or compiled scenegraph +Node14=Node: no capability to set Pickable +Node15=Node: Cannot compile, clone or getBounds on a scene graph that contains a cycle. +Node16=Node: no capability to read Collidable +Picking0=Cannot call picking under a SharedGroup node +Picking2=Picking: Node has no parent and locale. This is illegal! +NodeComponent0=NodeComponent:cloneNodeComponent must be defined in subclass +NodeComponent1=Cannot duplicate a Compiled NodeComponent object +NodeRetained0=Not supported in a Shared Graph +NodeRetained1=Only supported in a Shared Graph +NodeRetained2=invalid scene graph path +NodeRetained3=No node object may exist in more than one virtual universe +NodeRetained4=SharedGroup has no parent. This is illegal! +NodeRetained5=Node has no parent and locale. This is illegal! +OrderedGroup0=OrderedGroup: childIndexOrder.length != number of children +OrderedGroup1=OrderedGroup: childIndexOrder[i] must be >= 0, for i in [0, numChildren-1] +OrderedGroup2=OrderedGroup: childIndexOrder[i] must be < numChildren, for i in [0, numChildren-1] +OrderedGroup3=OrderedGroup: childIndexOrder[i] must not equal to childIndexOrder[j], for i,j in [0,numChildren-1] and i != j +OrderedGroup4=OrderedGroup: no capability to write child index order +OrderedGroup5=OrderedGroup: no capability to read child index order +OrderedGroup6=OrderedGroup: insertChild illegal when childIndexOrder != null +OrientedShape3D0=OrientedShape3D: no capability to set alignment mode +OrientedShape3D1=OrientedShape3D: no capability to get alignment mode +OrientedShape3D2=OrientedShape3D: no capability to set alignment axis +OrientedShape3D3=OrientedShape3D: no capability to get alignment axis +OrientedShape3D4=OrientedShape3D: no capability to set rotation point +OrientedShape3D5=OrientedShape3D: no capability to get rotation point +OrientedShape3D6=OrientedShape3D: no capability to set constant scale enable +OrientedShape3D7=OrientedShape3D: no capability to get constant scale enable +OrientedShape3D8=OrientedShape3D: no capability to set scale +OrientedShape3D9=OrientedShape3D: no capability to get scale +PathInterpolator0=PathInterpolator: first knot is not 0.0 +PathInterpolator1=PathInterpolator: last knot is not 1.0 +PathInterpolator2=PathInterpolator: invalid knot value +PhysicalBody0=non-rigid transform +PointLight0=PointLight: no capability to set light's state +PointLight1=PointLight: no capability to set light's position +PointLight2=PointLight: no capability to read light's position +PointLight3=PointLight: no capability to set light's attenuation +PointLight5=PointLight: no capability to read light's attenuation +PointSound0=PointSound: no capability to set position +PointSound2=PointSound: no capability to get position +PointSound3=PointSound: no capability to set distance attenuation +PointSound4=PointSound: no capability to get max distance attenuation +RotPosPathInterpolator0=RotPosPathInterpolator: length of knots, positions, and quats must be equal +PhysicalEnvironment0=addInputDevice: InputDevice.getProcessingMode must return one of BLOCKING, NON_BLOCKING, or DEMAND_DRIVEN +PhysicalEnvironment1=non-rigid transform +PhysicalEnvironment2=Illegal policy value +Raster0=Raster: no capability to set position +Raster1=Raster: no capability to get position +Raster2=Raster: no capability to get type +Raster3=Raster: no capability to set image +Raster4=Raster: no capability to get image +Raster5=Raster: no capability to set depth component +Raster6=Raster: no capability to get depth component +Raster7=Raster: no capability to set offset +Raster8=Raster: no capability to get offset +Raster9=Raster: no capability to set size +Raster10=Raster: no capability to set clip mode +Raster11=Raster: no capability to get clip mode +PointArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +PointAttributes0=PointAttributes: no capability to set point size +PointAttributes1=PointAttributes: no capability to get point size +PointAttributes2=PointAttributes: no capability to set point antialiasing +PointAttributes3=PointAttributes: no capability to get point antialiasing +PolygonAttributes0=PolygonAttributes: illegal polygon mode +PolygonAttributes2=PolygonAttributes: no capability to set polygon cull face +PolygonAttributes3=setCullFace: illegal cull face +PolygonAttributes4=PolygonAttributes: no capability to get polygon cull face +PolygonAttributes5=PolygonAttributes: no capability to set back face normal flip flag +PolygonAttributes6=PolygonAttributes: no capability to get back face normal flip flag +PolygonAttributes7=PolygonAttributes: no capability to set polygon mode +PolygonAttributes8=setPolygonMode: illegal polygon mode +PolygonAttributes9=PolygonAttributes: no capability to get polygon mode +PolygonAttributes10=PolygonAttributes: no capability to set polygon offset +PolygonAttributes11=PolygonAttributes: no capability to get polygon offset +PolygonAttributes12=PolygonAttributes: illegal cull face +QuadArray0=QuadArray: illegal vertexCount +QuadArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +PositionPathInterpolator0=PositionPathInterpolator: length of knots and positions must be equal +RenderingAttributes0=RenderingAttributes: no capability to set depth buffer mode +RenderingAttributes1=RenderingAttributes: no capability to get depth buffer mode +RenderingAttributes2=RenderingAttributes: no capability to set depth buffer write mode +RenderingAttributes3=RenderingAttributes: no capability to get depth buffer write mode +RenderingAttributes4=RenderingAttributes: no capability to set alpha test value +RenderingAttributes5=RenderingAttributes: no capability to get alpha test value +RenderingAttributes6=RenderingAttributes: no capability to set alpha test function +RenderingAttributes7=RenderingAttributes: no capability to get alpha test function +RenderingAttributes8=RenderingAttributes: no capability to set visibility +RenderingAttributes9=RenderingAttributes: no capability to get visibility +RenderingAttributes10=RenderingAttributes: no capability to set raster op +RenderingAttributes11=RenderingAttributes: no capability to get raster op +RenderingAttributes12=RenderingAttributes: no capability to set ignore vertex colors flag +RenderingAttributes13=RenderingAttributes: no capability to get ignore vertex colors flag +TriangleStripArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +TriangleStripArrayRetained1=stripVertexCounts element less than 3 +RotationPathInterpolator0=RotationPathInterpolator: length of knots and quats must be of the same length +RotPosScalePathInterpolator0=RotPosScalePathInterpolator: length of knots and positions must be of the same length +RotPosScalePathInterpolator1=PositionPathInterpolator: length of knots and quats must be equal +RotPosScalePathInterpolator2=PositionPathInterpolator: length of knots and scales must be equal +SceneGraphObject0=Cannot modify capability bits on a live or compiled object +SceneGraphObject1=Cannot modify capability isFrequent bits on a compiled object +SceneGraphObject2=Object is either live or compiled +SceneGraphObjectRetained0=CloneNotSupportedException +SceneGraphPath0=SceneGraphPath : Node array pointer is null. +SceneGraphPath1=SceneGraphPath : Node array bounds exceeded. +SceneGraphPath2=Invalid SceneGraphPath: a Locale is not specified. +SceneGraphPath3=Invalid SceneGraphPath: A member is not live. +SceneGraphPath5=Invalid SceneGraphPath: A Link node has been excluded. +SceneGraphPath9=Invalid SceneGraphPath: Locale and path are not associated. +SceneGraphPath10=Invalid SceneGraphPath: a Node is not specified. +SceneGraphPath11=Invalid SceneGraphPath: Not all nodes are on the same path or there is an ordering problem. +Screen3D0=Screen3D: non-rigid transform +Screen3D1=Screen3D: Cannot set screen size, screen is not in off-screen mode +Sensor0=Sensor.setPredictor: Must use PREDICT_NONE or PREDICT_NEXT_FRAME_TIME +Sensor1=Sensor.setPredictionPolicy: Illegal policy +Sensor2=getRead(read, deltaT) must have value >= 0 for deltaT +Sensor3=Sensor.lastRead(transform,kth); kth can't be bigger than the sensor read count +Sensor4=Sensor.lastTime(k); k can't be bigger than the sensor read count +Sensor5=Sensor.lastButtons(k, values); k can't be bigger than the sensor read count +Sensor6=Sensor.lastButtons(k); k can't be bigger than the sensor read count +SensorRead0=SensorRead: Array of button values is not long enough +SensorRead1=SensorRead: Trying to set button values when this SensorRead object has no buttons +Shape3D0=Group: no capability to set bounds +Shape3D1=Group: no capability to read user bounds +Shape3D2=Shape3D: no capability to set geometry +Shape3D3=Shape3D: no capability to get geometry +Shape3D4=Shape3D: no capability to set appearance +Shape3D5=Shape3D: no capability to get appearance +Shape3D6=Shape3D: no capability to allow intersect +Shape3D7=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +Shape3D8=Shape3D: no capability to set appearance override enable +Shape3D9=Shape3D: no capability to get appearance override enable +Sound0=Sound: no capability to set sound data +Sound1=Sound: no capability to get sound data +Sound2=Sound: no capability to set initial gain +Sound3=Sound: no capability to get initial gain +Sound4=Sound: no capability to set loop +Sound5=Sound: no capability to get loop +Sound6=Sound: no capability to set release flag +Sound7=Sound: no capability to get release flag +Sound8=Sound: no capability to set continuous play flag +Sound9=Sound: no capability to get continuous play flag +Sound10=Sound: no capability set sound state +Sound11=Sound: no capability to set scheduling bounds +Sound12=Sound: no capability to get scheduling bounds +Sound15=Sound: no capability set sound priority +Sound16=Sound: no capability to get sound on priority +Sound17=Sound: no capability to get duration +Sound18=Sound: no capability to get playing state +Sound20=Sound: no capability to get to get channels used for sound +Sound21=Sound: no capability to get sound on flag +Sound22=Sound: no capability to get ready state +Sound23=Sound: no capability to set mute flag +Sound24=Sound: no capability to get mute flag +Sound25=Sound: no capability to set pause flag +Sound26=Sound: no capability to get pause flag +Sound27=Sound: no capability to set rate scale factor +Sound28=Sound: no capability to get rate scale factor +SoundRetained1=Sound source data could not be loaded +SoundRetained2=Sound: Immediate mode sound may not be in scene graph +SoundRetained3=Sound: illegal node under Background geometry Branch +Switch0=Switch: no capability to set children +Switch1=Switch: no capability to read switch index +Switch2=Switch: no capability to set childMask +Switch3=Switch: no capability to read switch childMask +Switch4=Switch: no capability to read children +Text3D0=Text3D: no capability to get Font3D +Text3D1=Text3D: no capability to set Font3D +Text3D2=Text3D: no capability to get string +Text3D3=Text3D: no capability to set string +Text3D4=Text3D: no capability to get position +Text3D5=Text3D: no capability to set position +Text3D6=Text3D: no capability to get allignment +Text3D7=Text3D: no capability to set allignment +Text3D8=Text3D: no capability to get path +Text3D9=Text3D: no capability to set path +Text3D10=Text3D: no capability to get bounding box +Text3D11=Text3D: no capability to get character spacing +Text3D12=Text3D: no capability to set character spacing +Shape3DRetained3=Invalid SceneGraphPath encountered : localToVworld is null. +Shape3DRetained5=Shape3D: the new geometry component is not of the same equivalence class as the existing geometry components. +SharedGroup0=Cannot compile a live SharedGroup +SharedGroup1=SharedGroup: No capability to get Links +Soundscape0=Soundscape: no capability to set application bounds +Soundscape1=Soundscape: no capability to get application bounds +Soundscape4=Soundscape: no capability to set aural attributes +Soundscape5=Soundscape: no capability to get an attribute set +SoundscapeRetained0=Soundscape: illegal node under Background geometry Branch +SoundscapeRetained1=Soundscape: illegal node under SharedGroup Branch +SpotLight0=SpotLight: no capability to set light's spreadAngle +SpotLight1=SpotLight: no capability to read light's spreadAngle +SpotLight2=Light: no capability to set light's concentration +SpotLight3=SpotLight: no capability to read light's concentration +SpotLight4=SpotLight: no capability to set light's direction +SpotLight6=SpotLight: no capability to read light's direction +SharedGroupRetained0=SharedGroup: Illegal leaf nodes +Text3DRetained0=execute() called on Text3D +Text3DRetained1=Text3D - intersect : Sorry! Geometry type not supported. +TexCoordGeneration0=TexCoordGeneration: no capability to set enable +TexCoordGeneration1=TexCoordGeneration: no capability to get enable +TexCoordGeneration2=TexCoordGeneration: no capability to get format +TexCoordGeneration3=TexCoordGeneration: no capability to get mode +TexCoordGeneration4=TexCoordGeneration: no capability to get plane +TexCoordGeneration5=TexCoordGeneration: illegal texture generation mode +TexCoordGeneration6=TexCoordGeneration: no capability to set plane +Texture0=Texture: Illegal mipmapMode value +Texture1=Texture: Illegal format value +Texture2=Texture: width NOT power of 2 +Texture3=Texture: height NOT power of 2 +Texture4=Texture: no capability to get boundry mode +Texture6=Texture: no capability to get filter +Texture8=Texture: cannot use ImageComponent3D in Texture2D +Texture9=Texture: no capability to get image +Texture10=Texture: no capability to get mipmap mode +Texture11=Texture: no capability to set enable +Texture12=Texture: no capability to get enable +Texture13=Texture: no capability to get boundry color +Texture14=Texture: cannot use ImageComponent2D in Texture3D +Texture15=Texture: no capability to set image +Texture16=Texture: no capability to get width +Texture17=Texture: no capability to get height +Texture18=Texture: no capability to get number of mipmap levels +Texture19=Texture: no capability to get format +Texture20=Texture: number of images != number of mipmap levels +Texture21=Texture: no capability to get sharpen texture information +Texture22=Texture: the length of lod does not match the length of pts +Texture23=Texture: no capability to get texture filter4 information +Texture24=Texture: the length of weights < 4 +Texture25=Texture: Illegal anisotropic filter mode value +Texture26=Texture: no capability to get anisotropic filter information +Texture27=Texture: Illegal anisotropic filter degree +Texture28=Texture: Illegal minification filter +Texture29=Texture: Illegal magnification filter +Texture30=Texture: boundary width < 0 +Texture31=Texture: illegal boundary mode value +Texture32=Texture: no capability to set base level +Texture33=Texture: no capability to set maximum level +Texture34=Texture: no capability to get base level +Texture35=Texture: no capability to get maximum level +Texture36=Texture: baseLevel < 0 or baseLevel > maximum Level +Texture37=Texture: maximumLevel < baseLevel or maximum Level > 2 powerof(max(width,height)) +Texture38=Texture: no capability to set minimum lod +Texture39=Texture: no capability to set maximum lod +Texture40=Texture: no capability to get minimum lod +Texture41=Texture: no capability to get maximum lod +Texture42=Texture: minimumLOD > maximumLOD +Texture43=Texture: maximumLOD < minimumLOD +Texture44=Texture: no capability to set lod offset +Texture45=Texture: no capability to get lod offset +Texture2D0=Texture: no capability to get detail texture information +Texture2D1=Texture: Illegal detail texture mode value +Texture2D2=Texture: Illegal detail texture level +Texture2D3=Texture: the length of lod does not match the length of pts +Texture3D0=Texture: no capability to get boundry mode +Texture3D1=Texture: depth NOT power of 2 +Texture3D2=Texture: no capability to get depth +TextureAttributes0=TextureAttributes: no capability to set TextureMode +TextureAttributes1=TextureAttributes: no capability to get TextureMode +TextureAttributes2=TextureAttributes: no capability to set TexEnv cplor +TextureAttributes3=TextureAttributes: no capability to set TexEnv color +TextureAttributes4=TextureAttributes: no capability to get TexEnv color +TextureAttributes5=TextureAttributes: no capability to set texture coord transform +TextureAttributes6=TextureAttributes: no capability to get texture coord transform +TextureAttributes7=TextureAttributes: no capability to set perspective correction mode +TextureAttributes8=TextureAttributes: no capability to get perspective correction mode +TextureAttributes9=TextureAttributes: illegal perspective correction mode +TextureAttributes10=TextureAttributes: illegal texture mode +TextureAttributes11=TextureAttributes: no capability to set texture color table +TextureAttributes12=TextureAttributes: no capability to get texture color table +TextureAttributes13=TextureAttributes: table.length is not 3 or 4 +TextureAttributes14=TextureAttributes: component array length NOT power of 2 +TextureAttributes15=TextureAttributes: component array do not have same length +TextureAttributes16=TextureAttributes: no capability to set CombineRgbMode +TextureAttributes17=TextureAttributes: no capability to get CombineRgbMode +TextureAttributes18=TextureAttributes: no capability to set CombineAlphaMode +TextureAttributes19=TextureAttributes: no capability to get CombineAlphaMode +TextureAttributes20=TextureAttributes: illegal combine mode +TextureAttributes21=TextureAttributes: no capability to set CombineRgbSource +TextureAttributes22=TextureAttributes: no capability to get CombineRgbSource +TextureAttributes23=TextureAttributes: no capability to set CombineAlphaSource +TextureAttributes24=TextureAttributes: no capability to get CombineAlphaSource +TextureAttributes25=TextureAttributes: index out of range +TextureAttributes26=TextureAttributes: illegal combine source +TextureAttributes27=TextureAttributes: no capability to set CombineRgbFunction +TextureAttributes28=TextureAttributes: no capability to get CombineRgbFunction +TextureAttributes29=TextureAttributes: no capability to set CombineAlphaFunction +TextureAttributes30=TextureAttributes: no capability to get CombineAlphaFunction +TextureAttributes31=TextureAttributes: illegal combine function +TextureAttributes32=TextureAttributes: no capability to set CombineRgbScale +TextureAttributes33=TextureAttributes: no capability to get CombineRgbScale +TextureAttributes34=TextureAttributes: no capability to set CombineAlphaScale +TextureAttributes35=TextureAttributes: no capability to get CombineAlphaScale +TextureAttributes36=TextureAttributes: value other than 1, 2, or 4 +TextureCubeMap1=TextureCubeMap: no capability set images +TextureCubeMap2=TextureCubeMap: no capability get images +TextureCubeMap3=TextureCubeMap: cannot use ImageComponent3D in TextureCubeMap +TextureCubeMap4=TextureCubeMap: illegal cube map face +TextureRetained0=cannot set image in default texture +TextureRetained1=Texture:illegal image size +TextureRetained3=Texture: mipmap image not set at level +TextureUnitState0=TextureUnitState: no capability to set Texture +TextureUnitState1=TextureUnitState: no capability to get Texture +TextureUnitState2=TextureUnitState: no capability to set TextureAttributes +TextureUnitState3=TextureUnitState: no capability to get TextureAttributes +TextureUnitState4=TextureUnitState: no capability to set TexCoordGeneration +TextureUnitState5=TextureUnitState: no capability to get TexCoordGeneration +Transform3D0=Transform3D add +Transform3D1=cannot invert matrix +Transform3D4=Logic error: imax < 0 +TransformGroup0=TransformGroup: non-affine transform +TransformGroup1=Group: no capability to set transform +TransformGroup2=Group: no capability to get transform +TransparencyAttributes0=Transparency: no capability to set transparency mode +TransparencyAttributes1=Transparency: no capability to get transparency mode +TransparencyAttributes2=Transparency: no capability to set component +TransparencyAttributes3=Transparency: no capability to get component +TransparencyAttributes4=Transparency: no capability to set blend function +TransparencyAttributes5=Transparency: no capability to get blend function +TransparencyAttributes6=Transparency: illegal transparency mode +TransparencyAttributes7=Transparency: illegal source blend function +TransparencyAttributes8=Transparency: illegal destination blend function +Traverser0=Cycle found in SharedGroup +TriangleArray0=TriangleArray: illegal vertexCount +TriangleArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +TriangleFanArray0=TriangleFanArray: illegal vertexCount +View0=setViewPolicy: invalid value +View1=setProjectionPolicy: invalid value +View2=Cannot set projection when compatibility mode is disabled +View4=Cannot get projection when compatibility mode is disabled +View6=Cannot set VpcToEc when compatibility mode is disabled +View7=non-affine viewing transform +View8=Cannot get VpcToEc when compatibility mode is disabled +View9=Cannot get transform when userHeadToVworldEnable is disabled +View10=Sharing canvas with multiple views +View13=PhysicalBody is null +View14=PhysicalEnvironment is null +View15=View.stopBehaviorScheduler: can't call stopBehaviorScheduler() in a canvas callback. +View16=View.stopBehaviorScheduler: can't call stopBehaviorScheduler() in a behavior method. +View17=View.startBehaviorScheduler: can't call startBehaviorScheduler() in a canvas callback. +View18=View.startBehaviorScheduler: can't call startBehaviorScheduler() in a behavior method. +View19=View.stopView: can't call stopView() in a canvas callback. +View20=View.stopView: can't call stopView() in a behavior method. +View21=View.startView: can't call startView() in a canvas callback. +View22=View.startView: can't call startView() in a behavior method. +View23=Can't add an input device when the PhysicalEnvironment object is null +View24=Can't enumerate input devices when the PhysicalEnvironment object is null +View25=Can't add an audio device when the PhysicalEnvironment object is null +View26=Can't enumerate audio devices when the PhysicalEnvironment object is null +View27=Minimum time cannot be less than 0 +View28=View.renderOnce: can't call renderOnce() in a canvas callback. +View29=View.renderOnce: can't call renderOnce() in a behavior method. +View30=View.renderOnce: can't call renderOnce() when view is currently running. +View31=HMD mode not supported in CYCLOPEAN_EYE_VIEW mode. +TriangleStripArray0=TriangleStripArray: illegal vertexCount. +TriangleFanArrayRetained0=PickPoint doesn't make sense for geometry-based picking. Java 3D doesn't have spatial information of the surface. Should use PickBounds with BoundingSphere and set radius to a epsilon tolerance. +TriangleFanArrayRetained1=stripVertexCounts element less than 3 +ViewPlatform0=ViewPlatform: no capability to write policy +ViewPlatform1=Illegal policy value +ViewPlatform2=ViewPlatform: no capability to read policy +ViewSpecificGroup1=ViewSpecificGroup: no capability to write view +ViewSpecificGroup2=ViewSpecificGroup: no capability to read view +ViewSpecificGroup3=ViewSpecificGroup: illegal node under Background geometry Branch +WakeupOnCollisionEntry0=For collision, only Group, Shape3D, Morph, or BoundingLeaf nodes are permitted. +WakeupOnCollisionEntry1=WakeupOnCollisionEntry: cannot use object in a background geometry branch to arm a collision +WakeupOnCollisionEntry4=WakeupOnCollisionEntry: Illegal value for speed hint +WakeupOnCollisionEntry5=WakeupOnCollisionEntry: Can only call getTriggeringPath from within a Behavior's processStimulus method +WakeupOnCollisionEntry6=WakeupOnCollisionEntry: Can only call getTriggeringBounds from within a Behavior's processStimulus method +WakeupOnCollisionEntry7=WakeupOnCollisionEntry: SceneGraphPath is not unique or Object is under a SharedGroup node +WakeupOnSensorEntry0=WakeupOnSensorEntry: Can only call from within a Behavior's processStimulus method +WakeupOnSensorExit0=WakeupOnSensorExit: Can only call from within a Behavior's processStimulus method +WakeupOnViewPlatformEntry0=WakeupOnViewPlatformEntry: Can only call from within a Behavior's processStimulus method +WakeupOnViewPlatformExit0=WakeupOnViewPlatformExit: Can only call from within a Behavior's processStimulus method +ViewPlatformRetained0=non-congruent transform above ViewPlatform +ViewPlatformRetained1=ViewPlatform: illegal node under Background geometry Branch +ViewPlatformRetained2=ViewPlatform: illegal node under SharedGroup Branch +ViewPlatformRetained3=ViewPlatform: illegal node under ViewSpecificGroup Branch +VirtualUniverse0=Locale not attached to this VirtualUniverse +WakeupOnCollisionExit0=For collision, only Group, Shape3D, Morph, or BoundingLeaf nodes are permitted. +WakeupOnCollisionExit1=WakeupOnCollisionEntry: cannot use object in a background geometry branch to arm a collision +WakeupOnCollisionExit3=WakeupOnCollisionEntry: cannot use object in a background geometry branch to arm a collision +WakeupOnCollisionExit4=WakeupOnCollisionExit: Illegal value for speed hint +WakeupOnCollisionExit5=WakeupOnCollisionExit: Can only call getTriggeringPath from within a Behavior's processStimulus method +WakeupOnCollisionExit6=WakeupOnCollisionExit: Can only call getTriggeringBounds from within a Behavior's processStimulus method +WakeupOnCollisionExit7=WakeupOnCollisionExit: SceneGraphPath is not unique or Object is under a SharedGroup node +WakeupOnElapsedFrames0=WakeupOnElapsedFrames(int) requires an argument >= 0 +WakeupOnCollisionMovement0=For collision, only Group, Shape3D, Morph, or BoundingLeaf nodes are permitted. +WakeupOnCollisionMovement1=WakeupOnCollisionEntry: cannot use object in a background geometry branch to arm a collision +WakeupOnCollisionMovement4=WakeupOnCollisionMovement: Illegal value for speed hint +WakeupOnCollisionMovement5=WakeupOnCollisionMovement: Can only call getTriggeringPath from within a Behavior's processStimulus method +WakeupOnCollisionMovement6=WakeupOnCollisionMovement: Can only call getTriggeringBounds from within a Behavior's processStimulus method +WakeupOnCollisionMovement7=WakeupOnCollisionMovement: SceneGraphPath is not unique or Object is under a SharedGroup node +WakeupOnCollisionMovement8=WakeupOnCollisionEntry: cannot use object in a background geometry branch to arm a collision +WakeupCriteriaEnumerator0=No more criterion +WakeupOnElapsedTime0=WakeupOnElapsedTime(int) requires an argument > 0L +ModelClip0=ModelClip: no capability to write influencing bounds +ModelClip1=ModelClip: no capability to read influencing bounds +ModelClip2=ModelClip: no capability to write plane +ModelClip3=ModelClip: no capability to read plane +ModelClip4=ModelClip: no capability to write enable +ModelClip5=ModelClip: no capability to read enable +ModelClip6=ModelClip: illegal plane num value +ModelClip7=ModelClip: no capability to write scope +ModelClip8=ModelClip: no capability to read scope +ModelClip9=ModelClip: no capability to insert scope +ModelClip10=ModelClip: no capability to remove scope +ModelClip11=ModelClip: no capability to read scopes +ModelClip12=ModelClip: no capability to append scope +ModelClip13=ModelClip: no capability to write influencing bounding leaf +ModelClip14=ModelClip: no capability to read influencing bounding leaf +ModelClipRetained1=ModelClip: illegal node under SharedGroup Branch +MasterControl0=OpenGL is not MT safe +MasterControl1=Green threads are not supported +J3DBuffer0=Native access to NIO buffer not supported +J3DBuffer1=NIO buffer must be a direct buffer +J3DBuffer2=NIO buffer must match native byte order of underlying platform diff --git a/src/classes/share/javax/media/j3d/ExponentialFog.java b/src/classes/share/javax/media/j3d/ExponentialFog.java new file mode 100644 index 0000000..9d6a9fc --- /dev/null +++ b/src/classes/share/javax/media/j3d/ExponentialFog.java @@ -0,0 +1,190 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; + +/** + * The ExponentialFog leaf node extends the Fog leaf node by adding a + * fog density that is used as the exponent of the fog equation. The + * density is defined in the local coordinate system of the node, but + * the actual fog equation will ideally take place in eye coordinates. + * <P> + * The fog blending factor, f, is computed as follows: + * <P><UL> + * f = e<sup>-(density * z)</sup><P> + * where + * <ul>z is the distance from the viewpoint.<br> + * density is the density of the fog.<P></ul></UL> + * + * In addition to specifying the fog density, ExponentialFog lets you + * specify the fog color, which is represented by R, G, and B + * color values, where a color of (0,0,0) represents black. + */ +public class ExponentialFog extends Fog { + /** + * Specifies that this ExponentialFog node allows read access to its + * density information. + */ + public static final int + ALLOW_DENSITY_READ = CapabilityBits.EXPONENTIAL_FOG_ALLOW_DENSITY_READ; + + /** + * Specifies that this ExponentialFog node allows write access to its + * density information. + */ + public static final int + ALLOW_DENSITY_WRITE = CapabilityBits.EXPONENTIAL_FOG_ALLOW_DENSITY_WRITE; + + /** + * Constructs an ExponentialFog node with default parameters. + * The default values are as follows: + * <ul> + * density : 1.0<br> + * </ul> + */ + public ExponentialFog() { + // Just use the defaults + } + + /** + * Constructs an ExponentialFog node with the specified fog color. + * @param color the fog color + */ + public ExponentialFog(Color3f color) { + super(color); + } + + /** + * Constructs an ExponentialFog node with the specified fog color + * and density. + * @param color the fog color + * @param density the density of the fog + */ + public ExponentialFog(Color3f color, float density) { + super(color); + ((ExponentialFogRetained)this.retained).initDensity(density); + } + + /** + * Constructs an ExponentialFog node with the specified fog color. + * @param r the red component of the fog color + * @param g the green component of the fog color + * @param b the blue component of the fog color + */ + public ExponentialFog(float r, float g, float b) { + super(r, g, b); + } + + /** + * Constructs an ExponentialFog node with the specified fog color + * and density. + * @param r the red component of the fog color + * @param g the green component of the fog color + * @param b the blue component of the fog color + * @param density the density of the fog + */ + public ExponentialFog(float r, float g, float b, float density) { + super(r, g, b); + ((ExponentialFogRetained)this.retained).initDensity(density); + } + + /** + * Sets fog density. + * @param density the new density of this fog + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDensity(float density) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DENSITY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ExponentialFog0")); + + if (isLive()) + ((ExponentialFogRetained)this.retained).setDensity(density); + else + ((ExponentialFogRetained)this.retained).initDensity(density); + } + + /** + * Gets fog density. + * @return the density of this fog + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getDensity() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DENSITY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ExponentialFog1")); + + return ((ExponentialFogRetained)this.retained).getDensity(); + } + + /** + * Creates the retained mode ExponentialFogRetained object that this + * ExponentialFog node will point to. + */ + void createRetained() { + this.retained = new ExponentialFogRetained(); + this.retained.setSource(this); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + ExponentialFog ef = new ExponentialFog(); + ef.duplicateNode(this, forceDuplicate); + return ef; + } + + + /** + * Copies all ExponentialFog information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + ((ExponentialFogRetained) retained).initDensity( + ((ExponentialFogRetained) originalNode.retained).getDensity()); + } +} diff --git a/src/classes/share/javax/media/j3d/ExponentialFogRetained.java b/src/classes/share/javax/media/j3d/ExponentialFogRetained.java new file mode 100644 index 0000000..63bee5a --- /dev/null +++ b/src/classes/share/javax/media/j3d/ExponentialFogRetained.java @@ -0,0 +1,147 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; +import java.util.ArrayList; + +/** + * The ExponentialFog leaf node defines distance parameters for + * exponential fog. + */ +class ExponentialFogRetained extends FogRetained { + // Fog density + float density = 1.0f; + + // dirty bits for ExponentialFog + static final int DENSITY_CHANGED = FogRetained.LAST_DEFINED_BIT << 1; + + + ExponentialFogRetained() { + this.nodeType = NodeRetained.EXPONENTIALFOG; + } + + /** + * initializes fog density + */ + void initDensity(float density){ + this.density = density; + } + + /** + * Sets fog density and send a message + */ + void setDensity(float density){ + this.density = density; + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.FOG_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(DENSITY_CHANGED); + createMessage.args[2] = new Float(density); + VirtualUniverse.mc.processMessage(createMessage); + } + + /** + * Gets fog density + */ + float getDensity(){ + return this.density; + } + + + void setLive(SetLiveState s) { + super.setLive(s); + GroupRetained group; + + // Initialize the mirror object, this needs to be done, when + // renderBin is not accessing any of the fields + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.FOG_CHANGED; + createMessage.args[0] = this; + // a snapshot of all attributes that needs to be initialized + // in the mirror object + createMessage.args[1]= new Integer(INIT_MIRROR); + ArrayList addScopeList = new ArrayList(); + for (int i = 0; i < scopes.size(); i++) { + group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.addAllNodesForScopedFog(mirrorFog, addScopeList, tempKey); + } + Object[] scopeInfo = new Object[2]; + scopeInfo[0] = ((scopes.size() > 0) ? Boolean.TRUE:Boolean.FALSE); + scopeInfo[1] = addScopeList; + createMessage.args[2] = scopeInfo; + Color3f clr = new Color3f(color); + createMessage.args[3] = clr; + + Object[] obj = new Object[5]; + obj[0] = boundingLeaf; + obj[1] = (regionOfInfluence != null?regionOfInfluence.clone():null); + obj[2] = (inBackgroundGroup? Boolean.TRUE:Boolean.FALSE); + obj[3] = geometryBackground; + obj[4] = new Float(density); + + createMessage.args[4] = obj; + VirtualUniverse.mc.processMessage(createMessage); + + } + + + /** + * This method and its native counterpart update the native context + * fog values. + */ + native void update(long ctx, float red, float green, float blue, float density); + + void update(long ctx, double scale) { + update(ctx, color.x, color.y, color.z, density); + } + + + + // The update Object function. + // Note : if you add any more fields here , you need to update + // updateFog() in RenderingEnvironmentStructure + void updateMirrorObject(Object[] objs) { + + int component = ((Integer)objs[1]).intValue(); + + + if ((component & DENSITY_CHANGED) != 0) + ((ExponentialFogRetained)mirrorFog).density = ((Float)objs[2]).floatValue(); + + if ((component & INIT_MIRROR) != 0) { + ((ExponentialFogRetained)mirrorFog).density = ((Float)((Object[])objs[4])[4]).floatValue(); + + } + + super.updateMirrorObject(objs); + } + + + // Clone the retained side only, internal use only + protected Object clone() { + ExponentialFogRetained efr = + (ExponentialFogRetained)super.clone(); + + efr.initDensity(getDensity()); + + return efr; + } + + +} diff --git a/src/classes/share/javax/media/j3d/Fog.java b/src/classes/share/javax/media/j3d/Fog.java new file mode 100644 index 0000000..4ca055d --- /dev/null +++ b/src/classes/share/javax/media/j3d/Fog.java @@ -0,0 +1,536 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; +import javax.vecmath.Color3f; + +/** + * The Fog leaf node defines a set of fog parameters common to all + * types of fog. These parameters include the fog color and a region + * of influence in which this Fog node is active. + * A Fog node also contains a list of Group nodes that specifies the + * hierarchical scope of this Fog. If the scope list is empty, then + * the Fog node has universe scope: all nodes within the region of + * influence are affected by this Fog node. If the scope list is + * non-empty, then only those Leaf nodes under the Group nodes in the + * scope list are affected by this Fog node (subject to the + * influencing bounds). + * <p> + * If the regions of influence of multiple Fog nodes overlap, the + * Java 3D system will choose a single set of fog parameters for those + * objects that lie in the intersection. This is done in an + * implementation-dependent manner, but in general, the Fog node that + * is "closest" to the object is chosen. + */ + +public abstract class Fog extends Leaf { + /** + * Specifies that this Fog node allows read access to its + * influencing bounds and bounds leaf information. + */ + public static final int + ALLOW_INFLUENCING_BOUNDS_READ = CapabilityBits.FOG_ALLOW_INFLUENCING_BOUNDS_READ; + + /** + * Specifies that this Fog node allows write access to its + * influencing bounds and bounds leaf information. + */ + public static final int + ALLOW_INFLUENCING_BOUNDS_WRITE = CapabilityBits.FOG_ALLOW_INFLUENCING_BOUNDS_WRITE; + + /** + * Specifies that this Fog node allows read access to its color + * information. + */ + public static final int + ALLOW_COLOR_READ = CapabilityBits.FOG_ALLOW_COLOR_READ; + + /** + * Specifies that this Fog node allows write access to its color + * information. + */ + public static final int + ALLOW_COLOR_WRITE = CapabilityBits.FOG_ALLOW_COLOR_WRITE; + + /** + * Specifies that this Fog node allows read access to its scope + * information at runtime. + */ + public static final int + ALLOW_SCOPE_READ = CapabilityBits.FOG_ALLOW_SCOPE_READ; + + /** + * Specifies that this Fog node allows write access to its scope + * information at runtime. + */ + public static final int + ALLOW_SCOPE_WRITE = CapabilityBits.FOG_ALLOW_SCOPE_WRITE; + + /** + * Constructs a Fog node with default parameters. The default + * values are as follows: + * <ul> + * color : black (0,0,0)<br> + * scope : empty (universe scope)<br> + * influencing bounds : null<br> + * influencing bounding leaf : null<br> + * </ul> + */ + public Fog() { + // Just use the defaults + } + + /** + * Constructs a Fog node with the specified fog color. + * @param color the fog color + */ + public Fog(Color3f color) { + ((FogRetained)this.retained).initColor(color); + } + + /** + * Constructs a Fog node with the specified fog color. + * @param r the red component of the fog color + * @param g the green component of the fog color + * @param b the blue component of the fog color + */ + public Fog(float r, float g, float b) { + ((FogRetained)this.retained).initColor(r, g, b); + } + + /** + * Sets the fog color to the specified color. + * @param color the new fog color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setColor(Color3f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog0")); + + if (isLive()) + ((FogRetained)this.retained).setColor(color); + else + ((FogRetained)this.retained).initColor(color); + } + + /** + * Sets the fog color to the specified color. + * @param r the red component of the fog color + * @param g the green component of the fog color + * @param b the blue component of the fog color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setColor(float r, float g, float b) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog0")); + + if (isLive()) + ((FogRetained)this.retained).setColor(r, g, b); + else + ((FogRetained)this.retained).initColor(r, g, b); + } + + /** + * Retrieves the fog color. + * @param color the vector that will receive the current fog color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getColor(Color3f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog2")); + + ((FogRetained)this.retained).getColor(color); + } + + /** + * Sets the Fog's influencing region to the specified bounds. + * This is used when the influencing bounding leaf is set to null. + * @param region the bounds that contains the Fog's new influencing region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInfluencingBounds(Bounds region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog3")); + + if (isLive()) + ((FogRetained)this.retained).setInfluencingBounds(region); + else + ((FogRetained)this.retained).initInfluencingBounds(region); + + } + + /** + * Retrieves the Fog node's influencing bounds. + * @return this Fog's influencing bounds information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getInfluencingBounds() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog4")); + + return ((FogRetained)this.retained).getInfluencingBounds(); + } + + /** + * Sets the Fog's influencing region to the specified bounding leaf. + * When set to a value other than null, this overrides the influencing + * bounds object. + * @param region the bounding leaf node used to specify the Fog + * node's new influencing region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInfluencingBoundingLeaf(BoundingLeaf region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog3")); + + if (isLive()) + ((FogRetained)this.retained).setInfluencingBoundingLeaf(region); + else + ((FogRetained)this.retained).initInfluencingBoundingLeaf(region); + } + + /** + * Retrieves the Fog node's influencing bounding leaf. + * @return this Fog's influencing bounding leaf information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BoundingLeaf getInfluencingBoundingLeaf() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog4")); + + return ((FogRetained)this.retained).getInfluencingBoundingLeaf(); + } + + + /** + * Replaces the node at the specified index in this Fog node's + * list of scopes with the specified Group node. + * By default, Fog nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be stored at the specified index. + * @param index the index of the Group node to be replaced. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void setScope(Group scope, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog7")); + + + if (isLive()) + ((FogRetained)this.retained).setScope(scope, index); + else + ((FogRetained)this.retained).initScope(scope, index); + } + + + /** + * Retrieves the Group node at the specified index from this Fog node's + * list of scopes. + * @param index the index of the Group node to be returned. + * @return the Group node at the specified index. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Group getScope(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog8")); + + return ((FogRetained)this.retained).getScope(index); + } + + + /** + * Inserts the specified Group node into this Fog node's + * list of scopes at the specified index. + * By default, Fog nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be inserted at the specified index. + * @param index the index at which the Group node is inserted. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void insertScope(Group scope, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog9")); + + if (isLive()) + ((FogRetained)this.retained).insertScope(scope, index); + else + ((FogRetained)this.retained).initInsertScope(scope, index); + } + + + /** + * Removes the node at the specified index from this Fog node's + * list of scopes. If this operation causes the list of scopes to + * become empty, then this Fog will have universe scope: all nodes + * within the region of influence will be affected by this Fog node. + * @param index the index of the Group node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the group node at the + * specified index is part of a compiled scene graph + */ + public void removeScope(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog10")); + + if (isLive()) + ((FogRetained)this.retained).removeScope(index); + else + ((FogRetained)this.retained).initRemoveScope(index); + } + + + /** + * Returns an enumeration of this Fog node's list of scopes. + * @return an Enumeration object containing all nodes in this Fog node's + * list of scopes. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Enumeration getAllScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog11")); + + return (Enumeration) ((FogRetained)this.retained).getAllScopes(); + } + + + /** + * Appends the specified Group node to this Fog node's list of scopes. + * By default, Fog nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be appended. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void addScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog12")); + + if (isLive()) + ((FogRetained)this.retained).addScope(scope); + else + ((FogRetained)this.retained).initAddScope(scope); + } + + + /** + * Returns the number of nodes in this Fog node's list of scopes. + * If this number is 0, then the list of scopes is empty and this + * Fog node has universe scope: all nodes within the region of + * influence are affected by this Fog node. + * @return the number of nodes in this Fog node's list of scopes. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int numScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog11")); + + return ((FogRetained)this.retained).numScopes(); + } + + + /** + * Retrieves the index of the specified Group node in this + * Fog node's list of scopes. + * + * @param scope the Group node to be looked up. + * @return the index of the specified Group node; + * returns -1 if the object is not in the list. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int indexOfScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog8")); + return ((FogRetained)this.retained).indexOfScope(scope); + } + + + /** + * Removes the specified Group node from this Fog + * node's list of scopes. If the specified object is not in the + * list, the list is not modified. If this operation causes the + * list of scopes to become empty, then this Fog + * will have universe scope: all nodes within the region of + * influence will be affected by this Fog node. + * + * @param scope the Group node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog10")); + + if (isLive()) + ((FogRetained)this.retained).removeScope(scope); + else + ((FogRetained)this.retained).initRemoveScope(scope); + } + + + /** + * Removes all Group nodes from this Fog node's + * list of scopes. The Fog node will then have + * universe scope: all nodes within the region of influence will + * be affected by this Fog node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if any group node in this + * node's list of scopes is part of a compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeAllScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Fog10")); + + if (isLive()) + ((FogRetained)this.retained).removeAllScopes(); + else + ((FogRetained)this.retained).initRemoveAllScopes(); + } + + + /** + * Copies all Fog information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + FogRetained attr = (FogRetained) originalNode.retained; + FogRetained rt = (FogRetained) retained; + + Color3f c = new Color3f(); + attr.getColor(c); + rt.initColor(c); + rt.initInfluencingBounds(attr.getInfluencingBounds()); + + Enumeration elm = attr.getAllScopes(); + while (elm.hasMoreElements()) { + // this reference will set correctly in updateNodeReferences() callback + rt.initAddScope((Group) elm.nextElement()); + } + + // this reference will set correctly in updateNodeReferences() callback + rt.initInfluencingBoundingLeaf(attr.getInfluencingBoundingLeaf()); + } + + /** + * Callback used to allow a node to check if any nodes referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any node references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding Node in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * node is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + + FogRetained rt = (FogRetained) retained; + BoundingLeaf bl = rt.getInfluencingBoundingLeaf(); + + if (bl != null) { + Object o = referenceTable.getNewObjectReference(bl); + rt.initInfluencingBoundingLeaf((BoundingLeaf) o); + } + + + int num = rt.numScopes(); + for (int i=0; i < num; i++) { + rt.initScope((Group) referenceTable. + getNewObjectReference(rt.getScope(i)), i); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/FogRetained.java b/src/classes/share/javax/media/j3d/FogRetained.java new file mode 100644 index 0000000..33a92b9 --- /dev/null +++ b/src/classes/share/javax/media/j3d/FogRetained.java @@ -0,0 +1,789 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Enumeration; +import java.util.Vector; +import java.util.ArrayList; + +/** + * The Fog leaf node defines Fog parameters. + * It also specifies an region of influence in which this fog node + * is active. + */ +abstract class FogRetained extends LeafRetained{ + + // Statics used when something in the fog changes + static final int COLOR_CHANGED = 0x0001; + static final int SCOPE_CHANGED = 0x0002; + static final int BOUNDS_CHANGED = 0x0004; + static final int BOUNDINGLEAF_CHANGED = 0x0008; + static final int INIT_MIRROR = 0x0010; + static final int CLEAR_MIRROR = 0x0020; + static final int LAST_DEFINED_BIT = 0x0020; + + // Fog color. + Color3f color = new Color3f(0.0f, 0.0f, 0.0f); + + /** + * The Boundary object defining the lights's region of influence. + */ + Bounds regionOfInfluence = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * Vector of GroupRetained nodes that scopes this fog. + */ + Vector scopes = new Vector(); + + // An int that is set when this fog is changed + int isDirty = 0xffff; + + // This is true when this fog is referenced in an immediate mode context + boolean inImmCtx = false; + + /** + * The transformed value of the applicationRegion. + */ + Bounds region = null; + + // A reference to the scene graph fog + FogRetained sgFog = null; + + // The mirror copy of this fog + FogRetained mirrorFog = null; + + // Target threads to be notified when light changes + static final int targetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + + // Boolean to indicate if this object is scoped (only used for mirror objects + boolean isScoped = false; + + // The object that contains the dynamic HashKey - a string type object + // Used in scoping + HashKey tempKey = new HashKey(250); + + /** + * The EnvironmentSets which reference this fog. + * Note that multiple RenderBin update thread may access + * this shared environmentSets simultaneously. + * So we use UnorderList when sync. all the operations. + */ + UnorderList environmentSets = new UnorderList(1, EnvironmentSet.class); + + // Is true, if the mirror fog is viewScoped + boolean isViewScoped = false; + + FogRetained() { + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + /** + * Initialize the fog color to the specified color. + */ + void initColor(Color3f color) { + this.color.set(color); + } + /** + * Sets the fog color to the specified color and send message + */ + void setColor(Color3f color) { + this.color.set(color); + sendMessage(COLOR_CHANGED, new Color3f(color)); + } + + /** + * Sets the fog color to the specified color. + */ + void initColor(float r, float g, float b) { + this.color.x = r; + this.color.y = g; + this.color.z = b; + } + /** + * Sets the fog color to the specified color and send message + */ + void setColor(float r, float g, float b) { + initColor(r, g, b); + sendMessage(COLOR_CHANGED, new Color3f(r, g, b)); + } + + /** + * Retrieves the fog color. + */ + void getColor(Color3f color) { + color.set(this.color); + } + + /** + * Set the Fog's region of influence. + */ + void initInfluencingBounds(Bounds region) { + if (region != null) { + this.regionOfInfluence = (Bounds) region.clone(); + } else { + this.regionOfInfluence = null; + } + if (staticTransform != null) { + this.regionOfInfluence.transform(staticTransform.transform); + } + } + + /** + * Set the Fog's region of influence and send message + */ + void setInfluencingBounds(Bounds region) { + initInfluencingBounds(region); + sendMessage(BOUNDS_CHANGED, + (region != null ? region.clone() : null)); + } + + /** + * Get the Fog's region of Influence. + */ + Bounds getInfluencingBounds() { + Bounds b = null; + if (regionOfInfluence != null) { + b = (Bounds)regionOfInfluence.clone(); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + b.transform(invTransform); + } + } + return b; + } + + /** + * Set the Fog's region of influence to the specified Leaf node. + */ + void initInfluencingBoundingLeaf(BoundingLeaf region) { + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + } else { + boundingLeaf = null; + } + } + + /** + * Set the Fog's region of influence to the specified Leaf node. + */ + void setInfluencingBoundingLeaf(BoundingLeaf region) { + if (boundingLeaf != null) + boundingLeaf.mirrorBoundingLeaf.removeUser(mirrorFog); + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + boundingLeaf.mirrorBoundingLeaf.addUser(mirrorFog); + } else { + boundingLeaf = null; + } + sendMessage(BOUNDINGLEAF_CHANGED, + (boundingLeaf != null ? + boundingLeaf.mirrorBoundingLeaf : null)); + } + + + /** + * Get the Fog's region of influence. + */ + BoundingLeaf getInfluencingBoundingLeaf() { + return (boundingLeaf != null ? + (BoundingLeaf)boundingLeaf.source : null); + } + + /** + * Replaces the specified scope with the scope provided. + * @param scope the new scope + * @param index which scope to replace + */ + void initScope(Group scope, int index) { + scopes.setElementAt((GroupRetained)(scope.retained), index); + + } + + /** + * Replaces the specified scope with the scope provided. + * @param scope the new scope + * @param index which scope to replace + */ + void setScope(Group scope, int index) { + + ArrayList addScopeList = new ArrayList(); + ArrayList removeScopeList = new ArrayList(); + GroupRetained group; + Object[] scopeInfo = new Object[3]; + + + group = (GroupRetained) scopes.get(index); + tempKey.reset(); + group.removeAllNodesForScopedFog(mirrorFog, removeScopeList, tempKey); + + group = (GroupRetained)scope.retained; + initScope(scope, index); + tempKey.reset(); + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + group.addAllNodesForScopedFog(mirrorFog,addScopeList, tempKey); + + scopeInfo[0] = addScopeList; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE:Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + /** + * Inserts the specified scope at specified index.before the + * fog is live + * @param scope the new scope + * @param index position to insert new scope at + */ + void initInsertScope(Node scope, int index) { + GroupRetained group = (GroupRetained)scope.retained; + group.setFogScope(); + scopes.insertElementAt((GroupRetained)(scope.retained), index); + } + + /** + * Inserts the specified scope at specified index and sends + * a message + * @param scope the new scope + * @param index position to insert new scope at + */ + void insertScope(Node scope, int index) { + Object[] scopeInfo = new Object[3]; + ArrayList addScopeList = new ArrayList(); + + initInsertScope(scope, index); + GroupRetained group = (GroupRetained)scope.retained; + tempKey.reset(); + group.addAllNodesForScopedFog(mirrorFog,addScopeList, tempKey); + scopeInfo[0] = addScopeList; + scopeInfo[1] = null; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + + void initRemoveScope(int index) { + GroupRetained group = (GroupRetained)scopes.elementAt(index); + scopes.removeElementAt(index); + group.removeFogScope(); + + } + + void removeScope(int index) { + + Object[] scopeInfo = new Object[3]; + ArrayList removeScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scopes.elementAt(index); + + tempKey.reset(); + group.removeAllNodesForScopedFog(mirrorFog, removeScopeList, tempKey); + + initRemoveScope(index); + scopeInfo[0] = null; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + + /** + * Returns the scope specified by the index. + * @param index which scope to return + * @return the scoperen at location index + */ + Group getScope(int index) { + return (Group)(((GroupRetained)(scopes.elementAt(index))).source); + } + + /** + * Returns an enumeration object of the scoperen. + * @return an enumeration object of the scoperen + */ + Enumeration getAllScopes() { + Enumeration elm = scopes.elements(); + Vector v = new Vector(scopes.size()); + while (elm.hasMoreElements()) { + v.add( ((GroupRetained) elm.nextElement()).source); + } + return v.elements(); + } + + /** + * Appends the specified scope to this node's list of scopes before + * the fog is alive + * @param scope the scope to add to this node's list of scopes + */ + void initAddScope(Group scope) { + GroupRetained group = (GroupRetained)scope.retained; + scopes.addElement((GroupRetained)(scope.retained)); + group.setFogScope(); + } + + /** + * Appends the specified scope to this node's list of scopes. + * @param scope the scope to add to this node's list of scopes + */ + void addScope(Group scope) { + + Object[] scopeInfo = new Object[3]; + ArrayList addScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scope.retained; + + initAddScope(scope); + tempKey.reset(); + group.addAllNodesForScopedFog(mirrorFog,addScopeList, tempKey); + scopeInfo[0] = addScopeList; + scopeInfo[1] = null; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + /** + * Returns a count of this nodes' scopes. + * @return the number of scopes descendant from this node + */ + int numScopes() { + return scopes.size(); + } + + /** + * Returns the index of the specified scope within this nodes' list of scopes + * @param scope whose index is desired + * @return index of specified scope + */ + int indexOfScope(Group scope) { + if(scope != null) + return scopes.indexOf((GroupRetained)scope.retained); + else + return scopes.indexOf(null); + } + + /** + * Removes the specified scope from this nodes' list of scopes + * @param scope to be removed. If the scope is not found, + * the method returns silently + */ + void removeScope(Group scope) { + int i = indexOfScope(scope); + if(i >= 0) + removeScope(i); + } + + void initRemoveScope(Group scope) { + int i = indexOfScope(scope); + if(i >= 0) + initRemoveScope(i); + } + + /** + * Removes all the scopes from this node's list of scopes. + * The node should revert to universal + * scope after this method returns + */ + void removeAllScopes() { + Object[] scopeInfo = new Object[3]; + ArrayList removeScopeList = new ArrayList(); + GroupRetained group; + int n = scopes.size(); + + tempKey.reset(); + for(int index = n-1; index >= 0; index--) { + group = (GroupRetained)scopes.elementAt(index); + group.removeAllNodesForScopedFog(mirrorFog, removeScopeList, tempKey); + initRemoveScope(index); + } + scopeInfo[0] = null; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = Boolean.FALSE; + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + /** + * Removes all scopes from this node + */ + void initRemoveAllScopes() { + int n = scopes.size(); + for(int index = n-1; index >= 0; index--) + initRemoveScope(index); + } + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return (inImmCtx); + } + + boolean isScoped() { + return (scopes != null); + } + + /** + * This abstract method is used to update the current native + * context fog values. + */ + abstract void update(long ctx, double scale); + + + void updateImmediateMirrorObject(Object[] objs) { + GroupRetained group; + Vector currentScopes; + int i, nscopes; + Transform3D trans; + + int component = ((Integer)objs[1]).intValue(); + if ((component & BOUNDS_CHANGED) != 0) { + mirrorFog.regionOfInfluence = (Bounds) objs[2]; + if (mirrorFog.boundingLeaf == null) { + if (objs[2] != null) { + mirrorFog.region = ((Bounds)mirrorFog.regionOfInfluence).copy(mirrorFog.region); + mirrorFog.region.transform( + mirrorFog.regionOfInfluence, + getCurrentLocalToVworld()); + } + else { + mirrorFog.region = null; + } + } + } + else if ((component & BOUNDINGLEAF_CHANGED) != 0) { + mirrorFog.boundingLeaf = (BoundingLeafRetained)objs[2]; + if (objs[2] != null) { + mirrorFog.region = (Bounds)mirrorFog.boundingLeaf.transformedRegion; + } + else { + if (mirrorFog.regionOfInfluence != null) { + mirrorFog.region = ((Bounds)mirrorFog.regionOfInfluence).copy(mirrorFog.region); + mirrorFog.region.transform( + mirrorFog.regionOfInfluence, + getCurrentLocalToVworld()); + } + else { + mirrorFog.region = null; + } + + } + } + else if ((component & SCOPE_CHANGED) != 0) { + Object[] scopeList = (Object[])objs[2]; + ArrayList addList = (ArrayList)scopeList[0]; + ArrayList removeList = (ArrayList)scopeList[1]; + boolean isScoped = ((Boolean)scopeList[2]).booleanValue(); + + if (addList != null) { + mirrorFog.isScoped = isScoped; + for (i = 0; i < addList.size(); i++) { + Shape3DRetained obj = ((GeometryAtom)addList.get(i)).source; + obj.addFog(mirrorFog); + } + } + + if (removeList != null) { + mirrorFog.isScoped = isScoped; + for (i = 0; i < removeList.size(); i++) { + Shape3DRetained obj = ((GeometryAtom)removeList.get(i)).source; + obj.removeFog(mirrorFog); + } + } + } + + + } + + /** + * The update Object function. + */ + void updateMirrorObject(Object[] objs) { + + int component = ((Integer)objs[1]).intValue(); + if ((component & COLOR_CHANGED) != 0) { + mirrorFog.color.set((Color3f)objs[2]); + } + if ((component & INIT_MIRROR) != 0) { + mirrorFog.color.set((Color3f)objs[3]); + } + } + + + /** + * Note: This routine will only be called on + * the mirror object - will update the object's + * cached region and transformed region + */ + void updateBoundingLeaf() { + if (boundingLeaf != null && boundingLeaf.switchState.currentSwitchOn) { + region = boundingLeaf.transformedRegion; + } else { + if (regionOfInfluence != null) { + region = regionOfInfluence.copy(region); + region.transform(regionOfInfluence, + getCurrentLocalToVworld()); + } else { + region = null; + } + } + } + + /** + * This setLive routine just calls the superclass's method (after + * checking for use by an immediate context). It is up to the + * subclasses of fog to add themselves to the list of fogs + */ + void setLive(SetLiveState s) { + GroupRetained group; + Vector currentScopes; + int i, nscopes; + TransformGroupRetained[] tlist; + + if (inImmCtx) { + throw new IllegalSharingException(J3dI18N.getString("FogRetained0")); + } + super.doSetLive(s); + + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("FogRetained1")); + } + + + // Create the mirror object + // Initialization of the mirror object during the INSERT_NODE + // message (in updateMirrorObject) + if (mirrorFog == null) { + // mirrorFog = (FogRetained)this.clone(true); + mirrorFog = (FogRetained)this.clone(); + // Assign the bounding leaf of this mirror object as null + // it will later be assigned to be the mirror of the lights + // bounding leaf object + mirrorFog.boundingLeaf = null; + mirrorFog.sgFog = this; + } + // initMirrorObject(); + // If bounding leaf is not null, add the mirror object as a user + // so that any changes to the bounding leaf will be received + if (boundingLeaf != null) { + boundingLeaf.mirrorBoundingLeaf.addUser(mirrorFog); + } + + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(mirrorFog); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(mirrorFog); + } + + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(mirrorFog, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + + + // process switch leaf + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(mirrorFog, Targets.ENV_TARGETS); + } + mirrorFog.switchState = (SwitchState)s.switchStates.get(0); + + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + + + + super.markAsLive(); + + + } + // This is called on the parent object + void initMirrorObject(Object[] args) { + Shape3DRetained shape; + Object[] scopeInfo = (Object[]) args[2]; + Boolean scoped = (Boolean)scopeInfo[0]; + ArrayList shapeList = (ArrayList)scopeInfo[1]; + BoundingLeafRetained bl=(BoundingLeafRetained)((Object[])args[4])[0]; + Bounds bnds = (Bounds)((Object[])args[4])[1]; + + mirrorFog.inBackgroundGroup = ((Boolean)((Object[])args[4])[2]).booleanValue(); + mirrorFog.geometryBackground = (BackgroundRetained)((Object[])args[4])[3]; + for (int i = 0; i < shapeList.size(); i++) { + shape = ((GeometryAtom)shapeList.get(i)).source; + shape.addFog(mirrorFog); + } + mirrorFog.isScoped = scoped.booleanValue(); + + if (bl != null) { + mirrorFog.boundingLeaf = bl.mirrorBoundingLeaf; + mirrorFog.region = boundingLeaf.transformedRegion; + } else { + mirrorFog.boundingLeaf = null; + mirrorFog.region = null; + } + + if (bnds != null) { + mirrorFog.regionOfInfluence = bnds; + if (mirrorFog.region == null) { + mirrorFog.region = (Bounds)regionOfInfluence.clone(); + mirrorFog.region.transform(regionOfInfluence, getLastLocalToVworld()); + } + } + else { + mirrorFog.regionOfInfluence = null; + } + + } + + // This is called on the parent object + void clearMirrorObject(Object[] args) { + Shape3DRetained shape; + ArrayList shapeList = (ArrayList)args[2]; + ArrayList removeScopeList = new ArrayList(); + + for (int i = 0; i < shapeList.size(); i++) { + shape = ((GeometryAtom)shapeList.get(i)).source; + shape.removeFog(mirrorFog); + } + mirrorFog.isScoped = false; + + + + } + + + + /** + * This clearLive routine first calls the superclass's method, then + * it removes itself to the list of fogs + */ + void clearLive(SetLiveState s) { + int i, j; + GroupRetained group; + + super.clearLive(s); + + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(mirrorFog, Targets.ENV_TARGETS); + } + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + + // Remove this mirror light as users of the bounding leaf + if (mirrorFog.boundingLeaf != null) + mirrorFog.boundingLeaf.removeUser(mirrorFog); + + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(mirrorFog); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(mirrorFog); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(mirrorFog, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + + if (scopes.size() > 0) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.FOG_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(CLEAR_MIRROR); + ArrayList removeScopeList = new ArrayList(); + for (i = 0; i < scopes.size(); i++) { + group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.removeAllNodesForScopedFog(mirrorFog, removeScopeList, tempKey); + } + createMessage.args[2] = removeScopeList; + VirtualUniverse.mc.processMessage(createMessage); + } + } + + // Clone the retained side only, internal use only + protected Object clone() { + FogRetained fr = (FogRetained)super.clone(); + + fr.color = new Color3f(color); + Bounds b = getInfluencingBounds(); + if (b != null) { + fr.initInfluencingBounds(b); + } + + fr.scopes = new Vector(); + fr.isDirty = 0xffff; + fr.inImmCtx = false; + fr.region = null; + fr.sgFog = null; + fr.mirrorFog = null; + fr.environmentSets = new UnorderList(1, EnvironmentSet.class); + return fr; + } + + void updateTransformChange() { + } + + // Called on mirror object + void updateImmediateTransformChange() { + // If bounding leaf is null, tranform the bounds object + if (boundingLeaf == null) { + if (regionOfInfluence != null) { + region = regionOfInfluence.copy(region); + region.transform(regionOfInfluence, + sgFog.getCurrentLocalToVworld()); + } + + } + } + + final void sendMessage(int attrMask, Object attr) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.universe = universe; + createMessage.type = J3dMessage.FOG_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + VirtualUniverse.mc.processMessage(createMessage); + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + if (regionOfInfluence != null) { + regionOfInfluence.transform(xform.transform); + } + } + void getMirrorObjects(ArrayList leafList, HashKey key) { + leafList.add(mirrorFog); + } + +} diff --git a/src/classes/share/javax/media/j3d/Font3D.java b/src/classes/share/javax/media/j3d/Font3D.java new file mode 100644 index 0000000..c1fbe48 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Font3D.java @@ -0,0 +1,1105 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import com.sun.j3d.utils.geometry.*; +import com.sun.j3d.internal.FastVector; +import java.awt.Font; +import java.awt.font.*; +import java.awt.geom.Rectangle2D; +import java.awt.geom.AffineTransform; +import javax.vecmath.*; +import java.awt.Shape; +import java.awt.geom.PathIterator; +import java.util.*; + +/** + * The Font3D object is used to store extruded 2D glyphs. These + * 3D glyphs can then be used to construct Text3D NodeComponent + * objects. + * <P> + * A 3D Font consists of a Java 2D font, a tesellation tolerance, + * and an extrusion path. The extrusion + * path creates depth by describing how the edge of a glyph varies + * in the Z axis. + * <P> + * The construction of a Text3D object requires a Font3D object. + * The Font3D object describes the style of the text, such as its + * depth. The text also needs other classes, such as java.awt.Font and + * FontExtrusion. The Font object describes the font name (Helvetica, + * Courier, etc.), the font style (bold, Italic, etc.), and point + * size. The FontExtrusion object extends Font3D by describing + * the extrusion path for the Font3D object (how the edge of the + * font glyph varies in the Z axis). + *<P> + * To ensure correct rendering, the 2D Font object should be created + * with the default AffineTransform. The point size of the 2D font will + * be used as a rough measure of how fine a tesselation to use when + * creating the Font3D object: the larger the point size, in + * general, the finer the tesselation. + * <P> + * Custom 3D fonts as well as methods to store 3D fonts + * to disk will be addressed in a future release. + * + * @see java.awt.Font + * @see FontExtrusion + * @see Text3D + */ +public class Font3D extends NodeComponent { + + Font font; + double tessellationTolerance; + FontExtrusion fontExtrusion; + FontRenderContext frc; + // Used by triangulateGlyphs method to split contour data into islands. + final static float EPS = 0.000001f; + + // Map glyph code to GeometryArrayRetained + Hashtable geomHash = new Hashtable(20); + + /** + * Constructs a Font3D object from the specified Font and + * FontExtrusion objects, using the default value for the + * tessellation tolerance. The default value is as follows: + * + * <ul> + * tessellation tolerance : 0.01<br> + * </ul> + * <P> + * The FontExtrusion object contains the extrusion path to use on + * the 2D Font glyphs. To ensure correct rendering the font must + * be created with the default AffineTransform. Passing null for + * the FontExtrusion parameter results in no extrusion being done. + * + * @param font the Java 2D font used to create the 3D font object + * @param extrudePath the extrusion path used to describe how + * the edge of the font varies along the Z axis + */ + public Font3D(Font font, FontExtrusion extrudePath) { + this(font, 0.01, extrudePath); + } + + /** + * Constructs a Font3D object from the specified Font and + * FontExtrusion objects, using the specified tessellation + * tolerance. + * The FontExtrusion object contains the extrusion path to use on + * the 2D Font glyphs. To ensure correct rendering, the font must + * be created with the default AffineTransform. Passing null for + * the FontExtrusion parameter results in no extrusion being done. + * + * @param font the Java 2D font used to create the 3D font object. + * @param tessellationTolerance the tessellation tolerance value + * used in tessellating the glyphs of the 2D Font. + * This corresponds to the <code>flatness</code> parameter in + * the <code>java.awt.Shape.getPathIterator</code> method. + * @param extrudePath the extrusion path used to describe how + * the edge of the font varies along the Z axis. + * + * @since Java 3D 1.2 + */ + public Font3D(Font font, + double tessellationTolerance, + FontExtrusion extrudePath) { + + this.font = font; + this.tessellationTolerance = tessellationTolerance; + this.fontExtrusion = extrudePath; + this.frc = new FontRenderContext(new AffineTransform(), + true, true); + } + + /** + * Returns the Java 2D Font used to create this Font3D object. + * @return Font object used by this Font3D + */ + public Font getFont() { + return this.font; + } + + + /** + * Returns the tessellation tolerance with which this Font3D was + * created. + * @return the tessellation tolerance used by this Font3D + * + * @since Java 3D 1.2 + */ + public double getTessellationTolerance() { + return tessellationTolerance; + } + + + /** + * Copies the FontExtrusion object used to create this Font3D object + * into the specified parameter. + * + * @param extrudePath object that will receive the + * FontExtrusion information for this Font3D object + */ + public void getFontExtrusion(FontExtrusion extrudePath) { + extrudePath = this.fontExtrusion; + } + + /** + * Returns the 3D bounding box of the specified glyph code. + * + * @param glyphCode the glyphCode from the original 2D Font + * @param bounds the 3D glyph's bounds + */ + public void getBoundingBox(int glyphCode, BoundingBox bounds){ + int[] gCodes = {glyphCode}; + GlyphVector gVec = font.createGlyphVector(frc, gCodes); + Rectangle2D.Float bounds2d = (Rectangle2D.Float) + (((GlyphMetrics)(gVec.getGlyphMetrics(0))).getBounds2D()); + + Point3d lower = new Point3d(bounds2d.x, bounds2d.y, 0.0); + Point3d upper; + if (fontExtrusion != null) { + upper = new Point3d(bounds2d.x + bounds2d.width, + bounds2d.y + bounds2d.height, + fontExtrusion.length); + } else { + upper = new Point3d(bounds2d.x + bounds2d.width, + bounds2d.y + bounds2d.height, + 0.0); + } + bounds.setLower(lower); + bounds.setUpper(upper); + } + + + // Triangulate glyph with 'unicode' if not already done. + GeometryArrayRetained triangulateGlyphs(GlyphVector gv, char c) { + Character ch = new Character(c); + GeometryArrayRetained geo = (GeometryArrayRetained) geomHash.get(ch); + + if (geo == null) { + // Font Y-axis is downwards, so send affine transform to flip it. + Rectangle2D bnd = gv.getVisualBounds(); + AffineTransform aTran = new AffineTransform(); + double tx = bnd.getX() + 0.5 * bnd.getWidth(); + double ty = bnd.getY() + 0.5 * bnd.getHeight(); + aTran.setToTranslation(-tx, -ty); + aTran.scale(1.0, -1.0); + aTran.translate(tx, -ty); + Shape shape = gv.getOutline(); + PathIterator pIt = shape.getPathIterator(aTran, tessellationTolerance); + int flag= -1, numContours = 0, numPoints = 0, i, j, k, num=0, vertCnt; + UnorderList coords = new UnorderList(100, Point3f.class); + float tmpCoords[] = new float[6]; + float lastX= .0f, lastY= .0f; + float firstPntx = Float.MAX_VALUE, firstPnty = Float.MAX_VALUE; + GeometryInfo gi = null; + NormalGenerator ng = new NormalGenerator(); + FastVector contours = new FastVector(10); + float maxY = -Float.MAX_VALUE; + int maxYIndex = 0, beginIdx = 0, endIdx = 0, start = 0; + + boolean setMaxY = false; + + + while (!pIt.isDone()) { + Point3f vertex = new Point3f(); + flag = pIt.currentSegment(tmpCoords); + if (flag == PathIterator.SEG_CLOSE){ + if (num > 0) { + if (setMaxY) { + // Get Previous point + beginIdx = start; + endIdx = numPoints-1; + } + contours.addElement(num); + num = 0; + numContours++; + } + } else if (flag == PathIterator.SEG_MOVETO){ + vertex.x = tmpCoords[0]; + vertex.y = tmpCoords[1]; + lastX = vertex.x; + lastY = vertex.y; + + if ((lastX == firstPntx) && (lastY == firstPnty)) { + pIt.next(); + continue; + } + setMaxY = false; + coords.add(vertex); + firstPntx = lastX; + firstPnty = lastY; + if (num> 0){ + contours.addElement(num); + num = 0; + numContours++; + } + num++; + numPoints++; + // skip checking of first point, + // since the last point will repeat this. + start = numPoints ; + } else if (flag == PathIterator.SEG_LINETO){ + vertex.x = tmpCoords[0]; + vertex.y = tmpCoords[1]; + //Check here for duplicate points. Code + //later in this function can not handle + //duplicate points. + + if ((vertex.x == lastX) && (vertex.y == lastY)) { + pIt.next(); + continue; + } + if (vertex.y > maxY) { + maxY = vertex.y; + maxYIndex = numPoints; + setMaxY = true; + } + lastX = vertex.x; + lastY = vertex.y; + coords.add(vertex); + num++; + numPoints++; + } + pIt.next(); + } + + // No data(e.g space, control characters) + // Two point can't form a valid contour + if (numPoints == 0){ + return null; + } + + + + // Determine font winding order use for side triangles + Point3f p1 = new Point3f(), p2 = new Point3f(), p3 = new Point3f(); + boolean flip_side_orient = true; + Point3f vertices[] = (Point3f []) coords.toArray(false); + + if (endIdx - beginIdx > 0) { + // must be true unless it is a single line + // define as "MoveTo p1 LineTo p2 Close" which is + // not a valid font definition. + + if (maxYIndex == beginIdx) { + p1.set(vertices[endIdx]); + } else { + p1.set(vertices[maxYIndex-1]); + } + p2.set(vertices[maxYIndex]); + if (maxYIndex == endIdx) { + p3.set(vertices[beginIdx]); + } else { + p3.set(vertices[maxYIndex+1]); + } + + if (p3.x != p2.x) { + if (p1.x != p2.x) { + // Use the one with smallest slope + if (Math.abs((p2.y - p1.y)/(p2.x - p1.x)) > + Math.abs((p3.y - p2.y)/(p3.x - p2.x))) { + flip_side_orient = (p3.x > p2.x); + } else { + flip_side_orient = (p2.x > p1.x); + } + } else { + flip_side_orient = (p3.x > p2.x); + } + } else { + // p1.x != p2.x, otherwise all three + // point form a straight vertical line with + // the middle point the highest. This is not a + // valid font definition. + flip_side_orient = (p2.x > p1.x); + } + } + + // Build a Tree of Islands + int startIdx = 0; + IslandsNode islandsTree = new IslandsNode(-1, -1); + int contourCounts[] = contours.getData(); + + + for (i= 0;i < contours.getSize(); i++) { + endIdx = startIdx + contourCounts[i]; + islandsTree.insert(new IslandsNode(startIdx, endIdx), vertices); + startIdx = endIdx; + } + + coords = null; // Free memory + contours = null; + contourCounts = null; + + // Compute islandCounts[][] and outVerts[][] + UnorderList islandsList = new UnorderList(10, IslandsNode.class); + islandsTree.collectOddLevelNode(islandsList, 0); + IslandsNode nodes[] = (IslandsNode []) islandsList.toArray(false); + int islandCounts[][] = new int[islandsList.arraySize()][]; + Point3f outVerts[][] = new Point3f[islandCounts.length][]; + int nchild, sum; + IslandsNode node; + + for (i=0; i < islandCounts.length; i++) { + node = nodes[i]; + nchild = node.numChild(); + islandCounts[i] = new int[nchild + 1]; + islandCounts[i][0] = node.numVertices(); + sum = 0; + sum += islandCounts[i][0]; + for (j=0; j < nchild; j++) { + islandCounts[i][j+1] = node.getChild(j).numVertices(); + sum += islandCounts[i][j+1]; + } + outVerts[i] = new Point3f[sum]; + startIdx = 0; + for (k=node.startIdx; k < node.endIdx; k++) { + outVerts[i][startIdx++] = vertices[k]; + } + + for (j=0; j < nchild; j++) { + endIdx = node.getChild(j).endIdx; + for (k=node.getChild(j).startIdx; k < endIdx; k++) { + outVerts[i][startIdx++] = vertices[k]; + } + } + } + + + + islandsTree = null; // Free memory + islandsList = null; + vertices = null; + + contourCounts = new int[1]; + int currCoordIndex = 0, vertOffset = 0; + ArrayList triangData = new ArrayList(); + + Point3f q1 = new Point3f(), q2 = new Point3f(), q3 = new Point3f(); + Vector3f n1 = new Vector3f(), n2 = new Vector3f(); + numPoints = 0; + //Now loop thru each island, calling triangulator once per island. + //Combine triangle data for all islands together in one object. + for (i=0;i < islandCounts.length;i++) { + contourCounts[0] = islandCounts[i].length; + numPoints += outVerts[i].length; + gi = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); + gi.setCoordinates(outVerts[i]); + gi.setStripCounts(islandCounts[i]); + gi.setContourCounts(contourCounts); + ng.generateNormals(gi); + + GeometryArray ga = gi.getGeometryArray(false, false, false); + vertOffset += ga.getVertexCount(); + + triangData.add(ga); + } + // Multiply by 2 since we create 2 faces of the font + // Second term is for side-faces along depth of the font + if (fontExtrusion == null) + vertCnt = vertOffset; + else{ + if (fontExtrusion.shape == null) + vertCnt = vertOffset * 2 + numPoints *6; + else{ + vertCnt = vertOffset * 2 + numPoints * 6 * + (fontExtrusion.pnts.length -1); + } + } + + // TODO: Should use IndexedTriangleArray to avoid + // duplication of vertices. To create triangles for + // side faces, every vertex is duplicated currently. + TriangleArray triAry = new TriangleArray(vertCnt, + GeometryArray.COORDINATES | + GeometryArray.NORMALS); + + boolean flip_orient[] = new boolean[islandCounts.length]; + boolean findOrient; + // last known non-degenerate normal + Vector3f goodNormal = new Vector3f(); + + + for (j=0;j < islandCounts.length;j++) { + GeometryArray ga = (GeometryArray)triangData.get(j); + vertOffset = ga.getVertexCount(); + + findOrient = false; + + //Create the triangle array + for (i= 0; i < vertOffset; i+= 3, currCoordIndex += 3){ + //Get 3 points. Since triangle is known to be flat, normal + // must be same for all 3 points. + ga.getCoordinate(i, p1); + ga.getNormal(i, n1); + ga.getCoordinate(i+1, p2); + ga.getCoordinate(i+2, p3); + + if (!findOrient) { + //Check here if triangles are wound incorrectly and need + //to be flipped. + if (!getNormal(p1,p2, p3, n2)) { + continue; + } + + if (n2.z >= EPS) { + flip_orient[j] = false; + } else if (n2.z <= -EPS) { + flip_orient[j] = true; + } else { + continue; + } + findOrient = true; + } + if (flip_orient[j]){ + //New Triangulator preserves contour orientation. If contour + //input is wound incorrectly, swap 2nd and 3rd points to + //sure all triangles are wound correctly for j3d. + q1.x = p2.x; q1.y = p2.y; q1.z = p2.z; + p2.x = p3.x; p2.y = p3.y; p2.z = p3.z; + p3.x = q1.x; p3.y = q1.y; p3.z = q1.z; + n1.x = -n1.x; n1.y = -n1.y; n1.z = -n1.z; + } + + + if (fontExtrusion != null) { + n2.x = -n1.x;n2.y = -n1.y;n2.z = -n1.z; + + triAry.setCoordinate(currCoordIndex, p1); + triAry.setNormal(currCoordIndex, n2); + triAry.setCoordinate(currCoordIndex+1, p3); + triAry.setNormal(currCoordIndex+1, n2); + triAry.setCoordinate(currCoordIndex+2, p2); + triAry.setNormal(currCoordIndex+2, n2); + + q1.x = p1.x; q1.y = p1.y; q1.z = p1.z + fontExtrusion.length; + q2.x = p2.x; q2.y = p2.y; q2.z = p2.z + fontExtrusion.length; + q3.x = p3.x; q3.y = p3.y; q3.z = p3.z + fontExtrusion.length; + + triAry.setCoordinate(currCoordIndex+vertOffset, q1); + triAry.setNormal(currCoordIndex+vertOffset, n1); + triAry.setCoordinate(currCoordIndex+1+vertOffset, q2); + triAry.setNormal(currCoordIndex+1+vertOffset, n1); + triAry.setCoordinate(currCoordIndex+2+vertOffset, q3); + triAry.setNormal(currCoordIndex+2+vertOffset, n1); + } else { + triAry.setCoordinate(currCoordIndex, p1); + triAry.setNormal(currCoordIndex, n1); + triAry.setCoordinate(currCoordIndex+1, p2); + triAry.setNormal(currCoordIndex+1, n1); + triAry.setCoordinate(currCoordIndex+2, p3); + triAry.setNormal(currCoordIndex+2, n1); + } + + } + if (fontExtrusion != null) { + currCoordIndex += vertOffset; + } + } + + //Now add side triangles in both cases. + + // Since we duplicated triangles with different Z, make sure + // currCoordIndex points to correct location. + if (fontExtrusion != null){ + if (fontExtrusion.shape == null){ + boolean smooth; + // we'll put a crease if the angle between the normals is + // greater than 44 degrees + float threshold = (float) Math.cos(44.0*Math.PI/180.0); + float cosine; + // need the previous normals to check for smoothing + Vector3f pn1 = null, pn2 = null; + // need the next normals to check for smoothing + Vector3f n3 = new Vector3f(), n4 = new Vector3f(); + // store the normals for each point because they are + // the same for both triangles + Vector3f p1Normal = new Vector3f(); + Vector3f p2Normal = new Vector3f(); + Vector3f p3Normal = new Vector3f(); + Vector3f q1Normal = new Vector3f(); + Vector3f q2Normal = new Vector3f(); + Vector3f q3Normal = new Vector3f(); + + for (i=0;i < islandCounts.length;i++){ + for (j=0, k=0, num =0;j < islandCounts[i].length;j++){ + num += islandCounts[i][j]; + p1.x = outVerts[i][num - 1].x; + p1.y = outVerts[i][num - 1].y; + p1.z = 0.0f; + q1.x = p1.x; q1.y = p1.y; q1.z = p1.z+fontExtrusion.length; + p2.z = 0.0f; + q2.z = p2.z+fontExtrusion.length; + for (int m=0; m < num;m++) { + p2.x = outVerts[i][m].x; + p2.y = outVerts[i][m].y; + q2.x = p2.x; + q2.y = p2.y; + if (getNormal(p1, q1, p2, n1)) { + + if (!flip_side_orient) { + n1.negate(); + } + goodNormal.set(n1); + break; + } + } + + for (;k < num;k++){ + p2.x = outVerts[i][k].x;p2.y = outVerts[i][k].y;p2.z = 0.0f; + q2.x = p2.x; q2.y = p2.y; q2.z = p2.z+fontExtrusion.length; + + if (!getNormal(p1, q1, p2, n1)) { + n1.set(goodNormal); + } else { + if (!flip_side_orient) { + n1.negate(); + } + goodNormal.set(n1); + } + + if (!getNormal(p2, q1, q2, n2)) { + n2.set(goodNormal); + } else { + if (!flip_side_orient) { + n2.negate(); + } + goodNormal.set(n2); + } + // if there is a previous normal, see if we need to smooth + // this normal or make a crease + + if (pn1 != null) { + cosine = n1.dot(pn2); + smooth = cosine > threshold; + if (smooth) { + p1Normal.x = (pn1.x + pn2.x + n1.x); + p1Normal.y = (pn1.y + pn2.y + n1.y); + p1Normal.z = (pn1.z + pn2.z + n1.z); + normalize(p1Normal); + + q1Normal.x = (pn2.x + n1.x + n2.x); + q1Normal.y = (pn2.y + n1.y + n2.y); + q1Normal.z = (pn2.z + n1.z + n2.z); + normalize(q1Normal); + } // if smooth + else { + p1Normal.x = n1.x; p1Normal.y = n1.y; p1Normal.z = n1.z; + q1Normal.x = n1.x+n2.x; + q1Normal.y = n1.y+n2.y; + q1Normal.z = n1.z+ n2.z; + normalize(q1Normal); + } // else + } // if pn1 != null + else { + pn1 = new Vector3f(); + pn2 = new Vector3f(); + p1Normal.x = n1.x; + p1Normal.y = n1.y; + p1Normal.z = n1.z; + + q1Normal.x = (n1.x + n2.x); + q1Normal.y = (n1.y + n2.y); + q1Normal.z = (n1.z + n2.z); + normalize(q1Normal); + } // else + + // if there is a next, check if we should smooth normal + + if (k+1 < num) { + p3.x = outVerts[i][k+1].x; p3.y = outVerts[i][k+1].y; + p3.z = 0.0f; + q3.x = p3.x; q3.y = p3.y; q3.z = p3.z + fontExtrusion.length; + + if (!getNormal(p2, q2, p3, n3)) { + n3.set(goodNormal); + } else { + if (!flip_side_orient) { + n3.negate(); + } + goodNormal.set(n3); + } + + if (!getNormal(p3, q2, q3, n4)) { + n4.set(goodNormal); + } else { + if (!flip_side_orient) { + n4.negate(); + } + goodNormal.set(n4); + } + + cosine = n2.dot(n3); + smooth = cosine > threshold; + + if (smooth) { + p2Normal.x = (n1.x + n2.x + n3.x); + p2Normal.y = (n1.y + n2.y + n3.y); + p2Normal.z = (n1.z + n2.z + n3.z); + normalize(p2Normal); + + q2Normal.x = (n2.x + n3.x + n4.x); + q2Normal.y = (n2.y + n3.y + n4.y); + q2Normal.z = (n2.z + n3.z + n4.z); + normalize(q2Normal); + } else { // if smooth + p2Normal.x = n1.x + n2.x; + p2Normal.y = n1.y + n2.y; + p2Normal.z = n1.z + n2.z; + normalize(p2Normal); + q2Normal.x = n2.x; q2Normal.y = n2.y; q2Normal.z = n2.z; + } // else + } else { // if k+1 < num + p2Normal.x = (n1.x + n2.x); + p2Normal.y = (n1.y + n2.y); + p2Normal.z = (n1.z + n2.z); + normalize(p2Normal); + + q2Normal.x = n2.x; + q2Normal.y = n2.y; + q2Normal.z = n2.z; + } // else + + // add pts for the 2 tris + // p1, q1, p2 and p2, q1, q2 + + if (flip_side_orient) { + triAry.setCoordinate(currCoordIndex, p1); + triAry.setNormal(currCoordIndex, p1Normal); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, q1); + triAry.setNormal(currCoordIndex, q1Normal); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, p2); + triAry.setNormal(currCoordIndex, p2Normal); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, p2); + triAry.setNormal(currCoordIndex, p2Normal); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, q1); + triAry.setNormal(currCoordIndex, q1Normal); + currCoordIndex++; + } else { + triAry.setCoordinate(currCoordIndex, q1); + triAry.setNormal(currCoordIndex, q1Normal); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, p1); + triAry.setNormal(currCoordIndex, p1Normal); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, p2); + triAry.setNormal(currCoordIndex, p2Normal); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, q1); + triAry.setNormal(currCoordIndex, q1Normal); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, p2); + triAry.setNormal(currCoordIndex, p2Normal); + currCoordIndex++; + } + triAry.setCoordinate(currCoordIndex, q2); + triAry.setNormal(currCoordIndex, q2Normal); + currCoordIndex++; + pn1.x = n1.x; pn1.y = n1.y; pn1.z = n1.z; + pn2.x = n2.x; pn2.y = n2.y; pn2.z = n2.z; + p1.x = p2.x; p1.y = p2.y; p1.z = p2.z; + q1.x = q2.x; q1.y = q2.y; q1.z = q2.z; + + }// for k + + // set the previous normals to null when we are done + pn1 = null; + pn2 = null; + }// for j + }//for i + } else { // if shape + int m, offset=0; + Point3f P2 = new Point3f(), Q2 = new Point3f(), P1=new Point3f(); + Vector3f nn = new Vector3f(), nn1= new Vector3f(), + nn2= new Vector3f(), nn3= new Vector3f(); + Vector3f nna = new Vector3f(), nnb=new Vector3f(); + float length; + boolean validNormal = false; + + // fontExtrusion.shape is specified, and is NOT straight line + for (i=0;i < islandCounts.length;i++){ + for (j=0, k= 0, offset = num =0;j < islandCounts[i].length;j++){ + num += islandCounts[i][j]; + + p1.x = outVerts[i][num - 1].x; + p1.y = outVerts[i][num - 1].y; + p1.z = 0.0f; + q1.x = p1.x; q1.y = p1.y; q1.z = p1.z+fontExtrusion.length; + p3.z = 0.0f; + for (m=num-2; m >= 0; m--) { + p3.x = outVerts[i][m].x; + p3.y = outVerts[i][m].y; + + if (getNormal(p3, q1, p1, nn1)) { + if (!flip_side_orient) { + nn1.negate(); + } + goodNormal.set(nn1); + break; + } + } + for (;k < num;k++){ + p2.x = outVerts[i][k].x;p2.y = outVerts[i][k].y;p2.z = 0.0f; + q2.x = p2.x; q2.y = p2.y; q2.z = p2.z+fontExtrusion.length; + getNormal(p1, q1, p2, nn2); + + p3.x = outVerts[i][(k+1)==num ? offset:(k+1)].x; + p3.y = outVerts[i][(k+1)==num ? offset:(k+1)].y; + p3.z = 0.0f; + if (!getNormal(p3,p2,q2, nn3)) { + nn3.set(goodNormal); + } else { + if (!flip_side_orient) { + nn3.negate(); + } + goodNormal.set(nn3); + } + + // Calculate normals at the point by averaging normals + // of two faces on each side of the point. + nna.x = (nn1.x+nn2.x); + nna.y = (nn1.y+nn2.y); + nna.z = (nn1.z+nn2.z); + normalize(nna); + + nnb.x = (nn3.x+nn2.x); + nnb.y = (nn3.y+nn2.y); + nnb.z = (nn3.z+nn2.z); + normalize(nnb); + + P1.x = p1.x;P1.y = p1.y;P1.z = p1.z; + P2.x = p2.x;P2.y = p2.y; P2.z = p2.z; + Q2.x = q2.x;Q2.y = q2.y; Q2.z = q2.z; + for (m=1;m < fontExtrusion.pnts.length;m++){ + q1.z = q2.z = fontExtrusion.pnts[m].x; + q1.x = P1.x + nna.x * fontExtrusion.pnts[m].y; + q1.y = P1.y + nna.y * fontExtrusion.pnts[m].y; + q2.x = P2.x + nnb.x * fontExtrusion.pnts[m].y; + q2.y = P2.y + nnb.y * fontExtrusion.pnts[m].y; + + if (!getNormal(p1, q1, p2, n1)) { + n1.set(goodNormal); + } else { + if (!flip_side_orient) { + n1.negate(); + } + goodNormal.set(n1); + } + + if (flip_side_orient) { + triAry.setCoordinate(currCoordIndex, p1); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, q1); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + } else { + triAry.setCoordinate(currCoordIndex, q1); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, p1); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + } + triAry.setCoordinate(currCoordIndex, p2); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + + if (!getNormal(p2, q1, q2, n1)) { + n1.set(goodNormal); + } else { + if (!flip_side_orient) { + n1.negate(); + } + goodNormal.set(n1); + } + + if (flip_side_orient) { + triAry.setCoordinate(currCoordIndex, p2); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, q1); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + } else { + triAry.setCoordinate(currCoordIndex, q1); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + + triAry.setCoordinate(currCoordIndex, p2); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + } + triAry.setCoordinate(currCoordIndex, q2); + triAry.setNormal(currCoordIndex, n1); + currCoordIndex++; + + p1.x = q1.x;p1.y = q1.y;p1.z = q1.z; + p2.x = q2.x;p2.y = q2.y;p2.z = q2.z; + }// for m + p1.x = P2.x; p1.y = P2.y; p1.z = P2.z; + q1.x = Q2.x; q1.y = Q2.y; q1.z = Q2.z; + nn1.x = nn2.x;nn1.y = nn2.y;nn1.z = nn2.z; + }// for k + offset = num; + }// for j + }//for i + }// if shape + }// if fontExtrusion + geo = (GeometryArrayRetained) triAry.retained; + geomHash.put(ch, geo); + } + + return geo; + } + + + static boolean getNormal(Point3f p1, Point3f p2, Point3f p3, Vector3f normal) { + Vector3f v1 = new Vector3f(); + Vector3f v2 = new Vector3f(); + + // Must compute normal + v1.sub(p2, p1); + v2.sub(p2, p3); + normal.cross(v1, v2); + normal.negate(); + + float length = normal.length(); + + if (length > 0) { + length = 1 / length; + normal.x *= length; + normal.y *= length; + normal.z *= length; + return true; + } + return false; + } + + + // check if 2 contours are inside/outside/intersect one another + // INPUT: + // vertCnt1, vertCnt2 - number of vertices in 2 contours + // begin1, begin2 - starting indices into vertices for 2 contours + // vertices - actual vertex data + // OUTPUT: + // status == 1 - intersecting contours + // 2 - first contour inside the second + // 3 - second contour inside the first + // 0 - disjoint contours(2 islands) + + static int check2Contours(int begin1, int end1, int begin2, int end2, + Point3f[] vertices) { + int i, j; + boolean inside2, inside1; + + inside2 = pointInPolygon2D(vertices[begin1].x, vertices[begin1].y, + begin2, end2, vertices); + + for (i=begin1+1; i < end1;i++) { + if (pointInPolygon2D(vertices[i].x, vertices[i].y, + begin2, end2, vertices) != inside2) { + return 1; //intersecting contours + } + } + + // Since we are using point in polygon test and not + // line in polygon test. There are cases we miss the interesting + // if we are not checking the reverse for all points. This happen + // when two points form a line pass through a polygon but the two + // points are outside of it. + + inside1 = pointInPolygon2D(vertices[begin2].x, vertices[begin2].y, + begin1, end1, vertices); + + for (i=begin2+1; i < end2;i++) { + if (pointInPolygon2D(vertices[i].x, vertices[i].y, + begin1, end1, vertices) != inside1) { + return 1; //intersecting contours + } + } + + if (!inside2) { + if (!inside1) { + return 0; // disjoint countours + } + // inside2 = false and inside1 = true + return 3; // second contour inside first + } + + // must be inside2 = true and inside1 = false + // Note that it is not possible inside2 = inside1 = true + // unless two contour overlap to each others. + // + return 2; // first contour inside second + } + + // Test if 2D point (x,y) lies inside polygon represented by verts. + // z-value of polygon vertices is ignored. Sent only to avoid data-copy. + // Uses ray-shooting algorithm to compute intersections along +X axis. + // This algorithm works for all polygons(concave, self-intersecting) and + // is best solution here due to large number of polygon vertices. + // Point is INSIDE if number of intersections is odd, OUTSIDE if number + // of intersections is even. + static boolean pointInPolygon2D(float x, float y, int begIdx, int endIdx, + Point3f[] verts){ + + int i, num_intersections = 0; + float xi; + + for (i=begIdx;i < endIdx-1;i++) { + if ((verts[i].y >= y && verts[i+1].y >= y) || + (verts[i].y < y && verts[i+1].y < y)) + continue; + + xi = verts[i].x + (verts[i].x - verts[i+1].x)*(y - verts[i].y)/ + (verts[i].y - verts[i+1].y); + + if (x < xi) num_intersections++; + } + + // Check for segment from last vertex to first vertex. + + if (!((verts[i].y >= y && verts[begIdx].y >= y) || + (verts[i].y < y && verts[begIdx].y < y))) { + xi = verts[i].x + (verts[i].x - verts[begIdx].x)*(y - verts[i].y)/ + (verts[i].y - verts[begIdx].y); + + if (x < xi) num_intersections++; + } + + return ((num_intersections % 2) != 0); + } + + + static final boolean normalize(Vector3f v) { + float len = v.length(); + + if (len > 0) { + len = 1.0f/len; + v.x *= len; + v.y *= len; + v.z *= len; + return true; + } + return false; + } + + + // A Tree of islands form based on contour, each parent's contour + // enclosed all the child. We built this since Triangular fail to + // handle the case of multiple concentrated contours. i.e. if + // 4 contours A > B > C > D. Triangular will fail recongized + // two island, one form by A & B and the other by C & D. + // Using this tree we can separate out every 2 levels and pass + // in to triangular to workaround its limitation. + static private class IslandsNode { + + private ArrayList islandsList = null; + int startIdx, endIdx; + + IslandsNode(int startIdx, int endIdx) { + this.startIdx = startIdx; + this.endIdx = endIdx; + islandsList = null; + } + + void addChild(IslandsNode node) { + + if (islandsList == null) { + islandsList = new ArrayList(5); + } + islandsList.add(node); + } + + void removeChild(IslandsNode node) { + islandsList.remove(islandsList.indexOf(node)); + } + + IslandsNode getChild(int idx) { + return (IslandsNode) islandsList.get(idx); + } + + int numChild() { + return (islandsList == null ? 0 : islandsList.size()); + } + + int numVertices() { + return endIdx - startIdx; + } + + void insert(IslandsNode newNode, Point3f[] vertices) { + boolean createNewLevel = false; + + if (islandsList != null) { + IslandsNode childNode; + int status; + + for (int i=numChild()-1; i>=0; i--) { + childNode = getChild(i); + status = check2Contours(newNode.startIdx, newNode.endIdx, + childNode.startIdx, childNode.endIdx, + vertices); + switch (status) { + case 2: // newNode inside childNode, go down recursively + childNode.insert(newNode, vertices); + return; + case 3:// childNode inside newNode, + // continue to search other childNode also + // inside this one and group them together. + newNode.addChild(childNode); + createNewLevel = true; + break; + default: // intersecting or disjoint + + } + } + } + + if (createNewLevel) { + // Remove child in newNode from this + for (int i=newNode.numChild()-1; i>=0; i--) { + removeChild(newNode.getChild(i)); + } + // Add the newNode to parent + } + addChild(newNode); + } + + // Return a list of node with odd number of level + void collectOddLevelNode(UnorderList list, int level) { + if ((level % 2) == 1) { + list.add(this); + } + if (islandsList != null) { + level++; + for (int i=numChild()-1; i>=0; i--) { + getChild(i).collectOddLevelNode(list, level); + } + } + } + } +} diff --git a/src/classes/share/javax/media/j3d/FontExtrusion.java b/src/classes/share/javax/media/j3d/FontExtrusion.java new file mode 100644 index 0000000..a2d5a5c --- /dev/null +++ b/src/classes/share/javax/media/j3d/FontExtrusion.java @@ -0,0 +1,241 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.*; +import java.lang.Math; +import java.awt.Shape; +import java.awt.geom.PathIterator; +import java.util.ArrayList; + + /** + * The FontExtrusion object is used to describe the extrusion path + * for a Font3D object. The extrusion path is used in conjunction + * with a Font2D object. The extrusion path defines the edge contour + * of 3D text. This contour is perpendicular to the face of the text. + * The extrusion has it's origin at the edge of the glyph with 1.0 being + * the height of the tallest glyph. Contour must be monotonic in x. + * <P> + * The shape of the extrusion path is, by default, a straight line + * from 0.0 to 0.2 (known as a straight bevel). The shape may be + * modified via the extrusionShape parameter, a Shape object that + * describes the 3D contour of a Font3D object. + * <P> + * User is responsible for data sanity and must make sure that + * extrusionShape does not cause intersection of adjacent glyphs + * or within single glyph. Else undefined output may be generated. + * + * @see java.awt.Font + * @see Font3D + */ +public class FontExtrusion extends Object { + + // Default FontExtrusion is a straight line of length .2 + float length = 0.2f; + Shape shape; + Point2f [] pnts; + + double tessellationTolerance = 0.01; + + /** + * Constructs a FontExtrusion object with default parameters. The + * default parameters are as follows: + * + * <ul> + * extrusion shape : null<br> + * tessellation tolerance : 0.01<br> + * </ul> + * + * A null extrusion shape specifies that a straight line from 0.0 + * to 0.2 (straight bevel) is used. + * + * @see Font3D + */ + public FontExtrusion() { + shape = null; + } + + /** + * Constructs a FontExtrusion object with the specified shape, using + * the default tessellation tolerance. The + * specified shape is used to construct the edge + * contour of a Font3D object. Each shape begins with an implicit + * point at 0.0. Contour must be monotonic in x. + * + * @param extrusionShape the shape object to use to generate the + * extrusion path. + * A null shape specifies that a straight line from 0.0 to 0.2 + * (straight bevel) is used. + * + * @exception IllegalArgumentException if multiple contours in + * extrusionShape, or contour is not monotonic or least x-value + * of a contour point is not 0.0f + * + * @see Font3D + */ + public FontExtrusion(Shape extrusionShape) { + setExtrusionShape(extrusionShape); + } + + + /** + * Constructs a FontExtrusion object with the specified shape, using + * the specified tessellation tolerance. The + * specified shape is used to construct the edge + * contour of a Font3D object. Each shape begins with an implicit + * point at 0.0. Contour must be monotonic in x. + * + * @param extrusionShape the shape object to use to generate the + * extrusion path. + * A null shape specifies that a straight line from 0.0 to 0.2 + * (straight bevel) is used. + * @param tessellationTolerance the tessellation tolerance value + * used in tessellating the extrusion shape. + * This corresponds to the <code>flatness</code> parameter in + * the <code>java.awt.Shape.getPathIterator</code> method. + * + * @exception IllegalArgumentException if multiple contours in + * extrusionShape, or contour is not monotonic or least x-value + * of a contour point is not 0.0f + * + * @see Font3D + * + * @since Java 3D 1.2 + */ + public FontExtrusion(Shape extrusionShape, + double tessellationTolerance) { + + this.tessellationTolerance = tessellationTolerance; + setExtrusionShape(extrusionShape); + } + + + /** + * Sets the FontExtrusion's shape parameter. This + * parameter is used to construct the 3D contour of a Font3D object. + * + * @param extrusionShape the shape object to use to generate the + * extrusion path. + * A null shape specifies that a straight line from 0.0 to 0.2 + * (straight bevel) is used. + * + * @exception IllegalArgumentException if multiple contours in + * extrusionShape, or contour is not monotonic or least x-value + * of a contour point is not 0.0f + * + * @see Font3D + * @see java.awt.Shape + */ + public void setExtrusionShape(Shape extrusionShape) { + shape = extrusionShape; + if (shape == null) return; + + PathIterator pIt = shape.getPathIterator(null, tessellationTolerance); + ArrayList coords = new ArrayList(); + float tmpCoords[] = new float[6], prevX = 0.0f; + int flag, n = 0, inc = -1; + + // Extrusion shape is restricted to be single contour, monotonous + // increasing, non-self-intersecting curve. Throw exception otherwise + while (!pIt.isDone()) { + Point2f vertex = new Point2f(); + flag = pIt.currentSegment(tmpCoords); + if (flag == PathIterator.SEG_LINETO){ + vertex.x = tmpCoords[0]; + vertex.y = tmpCoords[1]; + if (inc == -1){ + if (prevX < vertex.x) inc = 0; + else if (prevX > vertex.x) inc = 1; + } + //Flag 'inc' indicates if curve is monotonic increasing or + // monotonic decreasing. It is set to -1 initially and remains + // -1 if consecutive x values are same. Once 'inc' is set to + // 1 or 0, exception is thrown is curve changes direction. + if (((inc == 0) && (prevX > vertex.x)) || + ((inc == 1) && (prevX < vertex.x))) + throw new IllegalArgumentException(J3dI18N.getString("FontExtrusion0")); + + prevX = vertex.x; + n++; + coords.add(vertex); + }else if (flag == PathIterator.SEG_MOVETO){ + if (n != 0) + throw new IllegalArgumentException(J3dI18N.getString("FontExtrusion3")); + + vertex.x = tmpCoords[0]; + vertex.y = tmpCoords[1]; + prevX = vertex.x; + n++; + coords.add(vertex); + } + pIt.next(); + } + + //if (inc == 1){ + //Point2f vertex = new Point2f(0.0f, 0.0f); + //coords.add(vertex); + //} + int i, num = coords.size(); + pnts = new Point2f[num]; + //System.out.println("num "+num+" inc "+inc); + if (inc == 0){ + for (i=0;i < num;i++){ + pnts[i] = (Point2f)coords.get(i); + //System.out.println("i "+i+" x "+ pnts[i].x+" y "+pnts[i].y); + } + } + else { + for (i=0;i < num;i++) { + pnts[i] = (Point2f)coords.get(num - i -1); + //System.out.println("i "+i+" x "+ pnts[i].x+" y "+pnts[i].y); + } + } + + //Force last y to be zero until Text3D face scaling is implemented + pnts[num-1].y = 0.0f; + if (pnts[0].x != 0.0f) + throw new IllegalArgumentException(J3dI18N.getString("FontExtrusion1")); + + //Compute straight line distance between first and last points. + float dx = (pnts[0].x - pnts[num-1].x); + float dy = (pnts[0].y - pnts[num-1].y); + length = (float)Math.sqrt(dx*dx + dy*dy); + } + + + /** + * Gets the FontExtrusion's shape parameter. This + * parameter is used to construct the 3D contour of a Font3D object. + * + * @return extrusionShape the shape object used to generate the + * extrusion path + * + * @see Font3D + * @see java.awt.Shape + */ + public Shape getExtrusionShape() { + return shape; + } + + + /** + * Returns the tessellation tolerance with which this FontExtrusion was + * created. + * @return the tessellation tolerance used by this FontExtrusion + * + * @since Java 3D 1.2 + */ + public double getTessellationTolerance() { + return tessellationTolerance; + } + +} diff --git a/src/classes/share/javax/media/j3d/FreeListManager.java b/src/classes/share/javax/media/j3d/FreeListManager.java new file mode 100644 index 0000000..b923af2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/FreeListManager.java @@ -0,0 +1,98 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +class FreeListManager { + + private static final boolean DEBUG = false; + + // constants that represent the freelists managed by the Manager + static final int MESSAGE = 0; + static final int BHLEAF = 1; + static final int TRANSFORM3D = 2; + static final int BHINTERNAL = 3; + static final int DISPLAYLIST = 4; + static final int TEXTURE2D = 5; + static final int TEXTURE3D = 6; + static final int CANVASBIT = 7; + static final int VECTOR3D = 8; + static final int POINT3D = 9; + static int MAXINT = 9; + + // what list we are going to shrink next + private static int currlist = 0; + + // the freelists managed by the manager + static MemoryFreeList[] freelist = new MemoryFreeList[MAXINT+1]; + + static void createFreeLists() { + freelist[MESSAGE] = new MemoryFreeList("javax.media.j3d.J3dMessage"); + freelist[BHLEAF] = new MemoryFreeList("javax.media.j3d.BHLeafNode"); + freelist[TRANSFORM3D] = new MemoryFreeList("javax.media.j3d.Transform3D"); + freelist[BHINTERNAL] = new MemoryFreeList("javax.media.j3d.BHInternalNode"); + freelist[DISPLAYLIST] = new IntegerFreeList(); + freelist[TEXTURE2D] = new IntegerFreeList(); + freelist[TEXTURE3D] = new IntegerFreeList(); + freelist[CANVASBIT] = new IntegerFreeList(); + freelist[POINT3D] = new MemoryFreeList("javax.vecmath.Point3d"); + freelist[VECTOR3D] = new MemoryFreeList("javax.vecmath.Vector3d"); + } + + // allows list to be created. The listId for the new list is returned. + static int createNewFreeList(String className) { + MAXINT++; + MemoryFreeList[] temp = freelist; + freelist = new MemoryFreeList[MAXINT+1]; + System.arraycopy(temp, 0, freelist, 0, MAXINT+1); + freelist[MAXINT] = new MemoryFreeList(className); + return MAXINT; + } + + + // see if the current list can be shrunk + static void manageLists() { +// System.out.println("manageLists"); + if (freelist[currlist] != null) { + freelist[currlist].shrink(); + } + + currlist++; + if (currlist > MAXINT) currlist = 0; + } + + // return the freelist specified by the list param + static MemoryFreeList getFreeList(int list) { + if (list < 0 || list > MAXINT) { + if (DEBUG) System.out.println("illegal list"); + return null; + } + else { + return freelist[list]; + } + } + + static Object getObject(int listId) { + return freelist[listId].getObject(); + } + + static void freeObject(int listId, Object obj) { + freelist[listId].add(obj); + } + + static void clearList(int listId) { + freelist[listId].clear(); + } + + +} diff --git a/src/classes/share/javax/media/j3d/GeneralizedStrip.java b/src/classes/share/javax/media/j3d/GeneralizedStrip.java new file mode 100644 index 0000000..85ac1b6 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeneralizedStrip.java @@ -0,0 +1,875 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.* ; + + +/** + * This class provides static methods to support topological + * transformations on generalized strips. This is used by the + * GeometryDecompressor. These methods only need to look at the + * vertex replacement flags to determine how the vertices in the strip + * are connected. The connections are rearranged in different ways to + * transform generalized strips to GeometryArray representations. + * + * @see GeneralizedStripFlags + * @see GeneralizedVertexList + * @see GeometryDecompressor + */ +class GeneralizedStrip { + private static final boolean debug = false ; + + // Private convenience copies of various constants. + private static final int CW = + GeneralizedStripFlags.FRONTFACE_CW ; + private static final int CCW = + GeneralizedStripFlags.FRONTFACE_CCW ; + private static final int RESTART_CW = + GeneralizedStripFlags.RESTART_CW ; + private static final int RESTART_CCW = + GeneralizedStripFlags.RESTART_CCW ; + private static final int REPLACE_MIDDLE = + GeneralizedStripFlags.REPLACE_MIDDLE ; + private static final int REPLACE_OLDEST = + GeneralizedStripFlags.REPLACE_OLDEST ; + + /** + * The IntList is like an ArrayList, but avoids the Integer + * object wrapper and accessor overhead for simple lists of ints. + */ + static class IntList { + /** + * The array of ints. + */ + int ints[] ; + + /** + * The number of ints in this instance. + */ + int count ; + + /** + * Construct a new empty IntList of the given initial size. + * @param initialSize initial size of the backing array + */ + IntList(int initialSize) { + ints = new int[initialSize] ; + count = 0 ; + } + + /** + * Constructs an IntList with the given contents. + * @param ints the array of ints to use as the contents + */ + IntList(int ints[]) { + this.ints = ints ; + this.count = ints.length ; + } + + /** + * Add a new int to the end of this list. + * @param i the int to be appended to this list + */ + void add(int i) { + if (count == ints.length) { + int newints[] = new int[2*count] ; + System.arraycopy(ints, 0, newints, 0, count) ; + ints = newints ; + if (debug) + System.out.println + ("GeneralizedStrip.IntList: reallocated " + + (2*count) + " ints") ; + } + ints[count++] = i ; + } + + /** + * Trim the backing array to the current count and return the + * resulting backing array. + */ + int[] trim() { + if (count != ints.length) { + int newints[] = new int[count] ; + System.arraycopy(ints, 0, newints, 0, count) ; + ints = newints ; + } + return ints ; + } + + /** + * Fill the list with consecutive integers starting from 0. + */ + void fillAscending() { + for (int i = 0 ; i < ints.length ; i++) + ints[i] = i ; + + count = ints.length ; + } + + public String toString() { + String s = new String("[") ; + for (int i = 0 ; i < count-1 ; i++) + s = s + Integer.toString(ints[i]) + ", " ; + return s + Integer.toString(ints[count-1]) + "]" ; + } + } + + /** + * The StripArray class is used as the output of some conversion methods + * in the GeneralizedStrip class. + */ + static class StripArray { + /** + * A list of indices into the vertices of the original generalized + * strip. It specifies the order in which vertices in the original + * strip should be followed to build GeometryArray objects. + */ + IntList vertices ; + + /** + * A list of strip counts. + */ + IntList stripCounts ; + + /** + * Creates a StripArray with the specified vertices and stripCounts. + * @param vertices IntList containing vertex indicies. + * @param stripCounts IntList containing strip lengths. + */ + StripArray(IntList vertices, IntList stripCounts) { + this.vertices = vertices ; + this.stripCounts = stripCounts ; + } + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning a 2-element array of + * StripArray objects. The first StripArray will contain triangle strips + * and the second will contain triangle fans. + * + * @param vertices an object implementing GeneralizedStripFlags + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @return a 2-element array containing strips in 0 and fans in 1 + */ + static StripArray[] toStripsAndFans(GeneralizedStripFlags vertices, + int frontFace) { + + int size = vertices.getFlagCount() ; + + // Initialize IntLists to worst-case sizes. + IntList stripVerts = new IntList(size*3) ; + IntList fanVerts = new IntList(size*3) ; + IntList stripCounts = new IntList(size) ; + IntList fanCounts = new IntList(size) ; + + toStripsAndFans(vertices, frontFace, + stripVerts, stripCounts, fanVerts, fanCounts) ; + + // Construct the StripArray output. + StripArray sa[] = new StripArray[2] ; + + if (stripCounts.count > 0) + sa[0] = new StripArray(stripVerts, stripCounts) ; + + if (fanCounts.count > 0) + sa[1] = new StripArray(fanVerts, fanCounts) ; + + return sa ; + } + + private static void toStripsAndFans(GeneralizedStripFlags vertices, + int frontFace, + IntList stripVerts, + IntList stripCounts, + IntList fanVerts, + IntList fanCounts) { + int newFlag, curFlag, winding ; + int v, size, stripStart, stripLength ; + boolean transition = false ; + + stripStart = 0 ; + stripLength = 3 ; + curFlag = vertices.getFlag(0) ; + winding = (curFlag == RESTART_CW ? CW : CCW) ; + size = vertices.getFlagCount() ; + + // Vertex replace flags for the first 3 vertices are irrelevant since + // they can only define a single triangle. The first meaningful + // replace flag starts at the 4th vertex. + v = 3 ; + if (v < size) + curFlag = vertices.getFlag(v) ; + + while (v < size) { + newFlag = vertices.getFlag(v) ; + + if ((newFlag == curFlag) && + (newFlag != RESTART_CW) && (newFlag != RESTART_CCW)) { + // The last flag was the same as this one, and it wasn't a + // restart: proceed to the next vertex. + stripLength++ ; + v++ ; + + } else { + // Either this vertex flag changed from the last one, or + // the flag explicitly specifies a restart: process the + // last strip and start up a new one. + if (curFlag == REPLACE_MIDDLE) + addFan(fanVerts, fanCounts, stripStart, stripLength, + frontFace, winding, transition) ; + else + addStrip(stripVerts, stripCounts, stripStart, stripLength, + frontFace, winding) ; + + // Restart: skip to the 4th vertex of the new strip. + if ((newFlag == RESTART_CW) || (newFlag == RESTART_CCW)) { + winding = (newFlag == RESTART_CW ? CW : CCW) ; + stripStart = v ; + stripLength = 3 ; + v += 3 ; + transition = false ; + if (v < size) + curFlag = vertices.getFlag(v) ; + } + // Strip/fan transition: decrement start of strip. + else { + if (newFlag == REPLACE_OLDEST) { + // Flip winding order when transitioning from fans + // to strips. + winding = (winding == CW ? CCW : CW) ; + stripStart = v-2 ; + stripLength = 3 ; + } else { + // Flip winding order when transitioning from + // strips to fans only if the preceding strip has + // an even number of vertices. + if ((stripLength & 0x01) == 0) + winding = (winding == CW ? CCW : CW) ; + stripStart = v-3 ; + stripLength = 4 ; + } + v++ ; + transition = true ; + curFlag = newFlag ; + } + } + } + + // Finish off the last strip or fan. + // If v > size then the strip is degenerate. + if (v == size) + if (curFlag == REPLACE_MIDDLE) + addFan(fanVerts, fanCounts, stripStart, stripLength, + frontFace, winding, transition) ; + else + addStrip(stripVerts, stripCounts, stripStart, stripLength, + frontFace, winding) ; + else + throw new IllegalArgumentException + (J3dI18N.getString("GeneralizedStrip0")) ; + + if (debug) { + System.out.println("GeneralizedStrip.toStripsAndFans") ; + if (v > size) + System.out.println(" ended with a degenerate triangle:" + + " number of vertices: " + (v-size)) ; + + System.out.println("\n number of strips: " + stripCounts.count) ; + if (stripCounts.count > 0) { + System.out.println(" number of vertices: " + stripVerts.count) ; + System.out.println(" vertices/strip: " + + (float)stripVerts.count/stripCounts.count) ; + System.out.println(" strip counts: " + stripCounts.toString()) ; + // System.out.println(" indices: " + stripVerts.toString()) ; + } + + System.out.println("\n number of fans: " + fanCounts.count) ; + if (fanCounts.count > 0) { + System.out.println(" number of vertices: " + fanVerts.count) ; + System.out.println(" vertices/strip: " + + (float)fanVerts.count/fanCounts.count) ; + System.out.println(" fan counts: " + fanCounts.toString()) ; + // System.out.println(" indices: " + fanVerts.toString()) ; + } + System.out.println("\n total vertices: " + + (stripVerts.count + fanVerts.count) + + "\n original number of vertices: " + size + + "\n") ; + } + } + + // + // Java 3D specifies that the vertices of front-facing polygons + // have counter-clockwise (CCW) winding order when projected to + // the view surface. Polygons with clockwise (CW) vertex winding + // will be culled as back-facing by default. + // + // Generalized triangle strips can flip the orientation of their + // triangles with the RESTART_CW and RESTART_CCW vertex flags. + // Strips flagged with an orientation opposite to what has been + // specified as front-facing must have their windings reversed in + // order to have the correct face orientation when represented as + // GeometryArray objects. + // + private static void addStrip(IntList stripVerts, + IntList stripCounts, + int start, int length, + int frontFace, int winding) { + int vindex = start ; + + if (winding == frontFace) { + // Maintain original order. + stripCounts.add(length) ; + while (vindex < start + length) { + stripVerts.add(vindex++) ; + } + } else if ((length & 0x1) == 1) { + // Reverse winding order if number of vertices is odd. + stripCounts.add(length) ; + vindex += length-1 ; + while (vindex >= start) { + stripVerts.add(vindex--) ; + } + } else if (length == 4) { + // Swap middle vertices. + stripCounts.add(4) ; + stripVerts.add(vindex) ; + stripVerts.add(vindex+2) ; + stripVerts.add(vindex+1) ; + stripVerts.add(vindex+3) ; + } else { + // Make the 1st triangle a singleton with reverse winding. + stripCounts.add(3) ; + stripVerts.add(vindex) ; + stripVerts.add(vindex+2) ; + stripVerts.add(vindex+1) ; + if (length > 3) { + // Copy the rest of the vertices in original order. + vindex++ ; + stripCounts.add(length-1) ; + while (vindex < start + length) { + stripVerts.add(vindex++) ; + } + } + } + } + + private static void addFan(IntList fanVerts, + IntList fanCounts, + int start, int length, + int frontFace, int winding, + boolean transition) { + int vindex = start ; + fanVerts.add(vindex++) ; + + if (winding == frontFace) { + if (transition) { + // Skip 1st triangle if this is the result of a transition. + fanCounts.add(length-1) ; + vindex++ ; + } else { + fanCounts.add(length) ; + fanVerts.add(vindex++) ; + } + while (vindex < start + length) { + fanVerts.add(vindex++) ; + } + } else { + // Reverse winding order. + vindex += length-2 ; + while (vindex > start+1) { + fanVerts.add(vindex--) ; + } + if (transition) { + // Skip 1st triangle if this is the result of a transition. + fanCounts.add(length-1) ; + } else { + fanCounts.add(length) ; + fanVerts.add(vindex) ; + } + } + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning a StripArray containing + * exclusively strips. + * + * @param vertices an object implementing GeneralizedStripFlags + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @return a StripArray containing the converted strips + */ + static StripArray toTriangleStrips(GeneralizedStripFlags vertices, + int frontFace) { + + int size = vertices.getFlagCount() ; + + // initialize lists to worst-case sizes. + IntList stripVerts = new IntList(size*3) ; + IntList fanVerts = new IntList(size*3) ; + IntList stripCounts = new IntList(size) ; + IntList fanCounts = new IntList(size) ; + + toStripsAndFans(vertices, frontFace, + stripVerts, stripCounts, fanVerts, fanCounts) ; + + if (fanCounts.count == 0) + if (stripCounts.count > 0) + return new StripArray(stripVerts, stripCounts) ; + else + return null ; + + // convert each fan to one or more strips + int i, v = 0 ; + for (i = 0 ; i < fanCounts.count ; i++) { + fanToStrips(v, fanCounts.ints[i], fanVerts.ints, + stripVerts, stripCounts, false) ; + v += fanCounts.ints[i] ; + } + + // create the StripArray output + StripArray sa = new StripArray(stripVerts, stripCounts) ; + + if (debug) { + System.out.println("GeneralizedStrip.toTriangleStrips" + + "\n number of strips: " + + sa.stripCounts.count) ; + if (sa.stripCounts.count > 0) { + System.out.println(" number of vertices: " + + sa.vertices.count + + "\n vertices/strip: " + + ((float)sa.vertices.count / + (float)sa.stripCounts.count)) ; + System.out.print(" strip counts: [") ; + for (i = 0 ; i < sa.stripCounts.count-1 ; i++) + System.out.print(sa.stripCounts.ints[i] + ", ") ; + System.out.println(sa.stripCounts.ints[i] + "]") ; + } + System.out.println() ; + } + return sa ; + } + + private static void fanToStrips(int v, int length, int fans[], + IntList stripVerts, + IntList stripCounts, + boolean convexPlanar) { + if (convexPlanar) { + // Construct a strip by criss-crossing across the interior. + stripCounts.add(length) ; + stripVerts.add(fans[v]) ; + + int j = v + 1 ; + int k = v + (length - 1) ; + while (j <= k) { + stripVerts.add(fans[j++]) ; + if (j > k) break ; + stripVerts.add(fans[k--]) ; + } + } else { + // Traverse non-convex or non-planar fan, biting off 3-triangle + // strips or less. First 5 vertices produce 1 strip of 3 + // triangles, and every 4 vertices after that produce another + // strip of 3 triangles. Each remaining strip adds 2 vertices. + int fanStart = v ; + v++ ; + while (v+4 <= fanStart + length) { + stripVerts.add(fans[v]) ; + stripVerts.add(fans[v+1]) ; + stripVerts.add(fans[fanStart]) ; + stripVerts.add(fans[v+2]) ; + stripVerts.add(fans[v+3]) ; + stripCounts.add(5) ; + v += 3 ; + } + + // Finish off the fan. + if (v+1 < fanStart + length) { + stripVerts.add(fans[v]) ; + stripVerts.add(fans[v+1]) ; + stripVerts.add(fans[fanStart]) ; + v++ ; + + if (v+1 < fanStart + length) { + stripVerts.add(fans[v+1]) ; + stripCounts.add(4) ; + } + else + stripCounts.add(3) ; + } + } + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning an array of vertex + * references representing the original generalized strip as individual + * triangles. Each sequence of three consecutive vertex references in the + * output defines a single triangle. + * + * @param vertices an object implementing GeneralizedStripFlags + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @return an array of indices into the original vertex array + */ + static int[] toTriangles(GeneralizedStripFlags vertices, int frontFace) { + + int vertexCount = 0 ; + StripArray sa[] = toStripsAndFans(vertices, frontFace) ; + + if (sa[0] != null) + vertexCount = 3 * getTriangleCount(sa[0].stripCounts) ; + if (sa[1] != null) + vertexCount += 3 * getTriangleCount(sa[1].stripCounts) ; + + if (debug) + System.out.println("GeneralizedStrip.toTriangles\n" + + " number of triangles: " + vertexCount/3 + "\n" + + " number of vertices: " + vertexCount + "\n") ; + int t = 0 ; + int triangles[] = new int[vertexCount] ; + + if (sa[0] != null) + t = stripsToTriangles(t, triangles, + 0, sa[0].vertices.ints, + 0, sa[0].stripCounts.ints, + sa[0].stripCounts.count) ; + if (sa[1] != null) + t = fansToTriangles(t, triangles, + 0, sa[1].vertices.ints, + 0, sa[1].stripCounts.ints, + sa[1].stripCounts.count) ; + return triangles ; + } + + private static int stripsToTriangles(int tstart, int tbuff[], + int vstart, int vertices[], + int stripStart, int stripCounts[], + int stripCount) { + int t = tstart ; + int v = vstart ; + for (int i = 0 ; i < stripCount ; i++) { + for (int j = 0 ; j < stripCounts[i+stripStart] - 2 ; j++) { + if ((j & 0x01) == 0) { + // even-numbered triangles + tbuff[t*3 +0] = vertices[v+0] ; + tbuff[t*3 +1] = vertices[v+1] ; + tbuff[t*3 +2] = vertices[v+2] ; + } else { + // odd-numbered triangles + tbuff[t*3 +0] = vertices[v+1] ; + tbuff[t*3 +1] = vertices[v+0] ; + tbuff[t*3 +2] = vertices[v+2] ; + } + t++ ; v++ ; + } + v += 2 ; + } + return t ; + } + + private static int fansToTriangles(int tstart, int tbuff[], + int vstart, int vertices[], + int stripStart, int stripCounts[], + int stripCount) { + int t = tstart ; + int v = vstart ; + for (int i = 0 ; i < stripCount ; i++) { + for (int j = 0 ; j < stripCounts[i+stripStart] - 2 ; j++) { + tbuff[t*3 +0] = vertices[v] ; + tbuff[t*3 +1] = vertices[v+j+1] ; + tbuff[t*3 +2] = vertices[v+j+2] ; + t++ ; + } + v += stripCounts[i+stripStart] ; + } + return t ; + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning a 2-element array of + * StripArray objects. The first StripArray will contain triangle strips + * and the second will contain individual triangles in the vertices + * field. Short strips will be converted to individual triangles. + * + * @param vertices an object implementing GeneralizedStripFlags + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @param shortStripSize strips this size or less will be converted to + * individual triangles if there are more than maxShortStrips of them + * @param maxShortStrips maximum number of short strips allowed before + * creating individual triangles + * @return a 2-element array containing strips in 0 and triangles in 1 + */ + static StripArray[] toStripsAndTriangles(GeneralizedStripFlags vertices, + int frontFace, int shortStripSize, + int maxShortStrips) { + int longStripCount = 0 ; + int longStripVertexCount = 0 ; + int shortStripCount = 0 ; + int triangleCount = 0 ; + + StripArray sa[] = new StripArray[2] ; + StripArray ts = toTriangleStrips(vertices, frontFace) ; + + for (int i = 0 ; i < ts.stripCounts.count ; i++) + if (ts.stripCounts.ints[i] <= shortStripSize) { + shortStripCount++ ; + triangleCount += ts.stripCounts.ints[i] - 2 ; + } else { + longStripCount++ ; + longStripVertexCount += ts.stripCounts.ints[i] ; + } + + if (debug) + System.out.print("GeneralizedStrip.toStripsAndTriangles\n" + + " short strip size: " + shortStripSize + + " short strips tolerated: " + maxShortStrips + + " number of short strips: " + shortStripCount + + "\n\n") ; + + if (shortStripCount <= maxShortStrips) { + sa[0] = ts ; + sa[1] = null ; + } else { + int si = 0 ; int newStripVerts[] = new int[longStripVertexCount] ; + int ci = 0 ; int newStripCounts[] = new int[longStripCount] ; + int ti = 0 ; int triangles[] = new int[3*triangleCount] ; + int vi = 0 ; + + for (int i = 0 ; i < ts.stripCounts.count ; i++) { + if (ts.stripCounts.ints[i] <= shortStripSize) { + ti = stripsToTriangles(ti, triangles, + vi, ts.vertices.ints, + i, ts.stripCounts.ints, 1) ; + vi += ts.stripCounts.ints[i] ; + } else { + newStripCounts[ci++] = ts.stripCounts.ints[i] ; + for (int j = 0 ; j < ts.stripCounts.ints[i] ; j++) + newStripVerts[si++] = ts.vertices.ints[vi++] ; + } + } + + if (longStripCount > 0) + sa[0] = new StripArray(new IntList(newStripVerts), + new IntList(newStripCounts)) ; + else + sa[0] = null ; + + sa[1] = new StripArray(new IntList(triangles), null) ; + + if (debug) { + System.out.println(" triangles separated: " + triangleCount) ; + if (longStripCount > 0) { + System.out.println + (" new vertices/strip: " + + ((float)longStripVertexCount/(float)longStripCount)) ; + + System.out.print(" long strip counts: [") ; + for (int i = 0 ; i < longStripCount-1 ; i++) + System.out.print(newStripCounts[i++] + ", ") ; + + System.out.println + (newStripCounts[longStripCount-1] + "]\n") ; + } + } + } + return sa ; + } + + /** + * Interprets the vertex flags associated with a class implementing + * GeneralizedStripFlags, constructing and returning a StripArray. + * + * RESTART_CW and RESTART_CCW are treated as equivalent, as are + * REPLACE_MIDDLE and REPLACE_OLDEST. + * + * @param vertices an object implementing GeneralizedStripFlags + * @return a StripArray representing an array of line strips + */ + static StripArray toLineStrips(GeneralizedStripFlags vertices) { + int v, size, stripStart, stripLength, flag ; + + stripStart = 0 ; + stripLength = 2 ; + size = vertices.getFlagCount() ; + + // Initialize IntLists to worst-case sizes. + IntList stripVerts = new IntList(size*2) ; + IntList stripCounts = new IntList(size) ; + + // Vertex replace flags for the first two vertices are irrelevant. + v = 2 ; + while (v < size) { + flag = vertices.getFlag(v) ; + + if ((flag != RESTART_CW) && (flag != RESTART_CCW)) { + // proceed to the next vertex. + stripLength++ ; + v++ ; + + } else { + // Record the last strip. + stripCounts.add(stripLength) ; + for (int i = stripStart ; i < stripStart+stripLength ; i++) + stripVerts.add(i) ; + + // Start a new strip and skip to its 3rd vertex. + stripStart = v ; + stripLength = 2 ; + v += 2 ; + } + } + + // Finish off the last strip. + // If v > size then the strip is degenerate. + if (v == size) { + stripCounts.add(stripLength) ; + for (int i = stripStart ; i < stripStart+stripLength ; i++) + stripVerts.add(i) ; + } else + throw new IllegalArgumentException + (J3dI18N.getString("GeneralizedStrip0")) ; + + if (debug) { + System.out.println("GeneralizedStrip.toLineStrips\n") ; + if (v > size) + System.out.println(" ended with a degenerate line") ; + + System.out.println(" number of strips: " + stripCounts.count) ; + if (stripCounts.count > 0) { + System.out.println(" number of vertices: " + stripVerts.count) ; + System.out.println(" vertices/strip: " + + (float)stripVerts.count/stripCounts.count) ; + System.out.println(" strip counts: " + stripCounts.toString()) ; + // System.out.println(" indices: " + stripVerts.toString()) ; + } + System.out.println() ; + } + + if (stripCounts.count > 0) + return new StripArray(stripVerts, stripCounts) ; + else + return null ; + } + + /** + * Counts the number of lines defined by arrays of line strips. + * + * @param stripCounts array of strip counts, as used by the + * GeometryStripArray object + * @return number of lines in the strips + */ + static int getLineCount(int stripCounts[]) { + int count = 0 ; + for (int i = 0 ; i < stripCounts.length ; i++) + count += (stripCounts[i] - 1) ; + return count ; + } + + /** + * Counts the number of triangles defined by arrays of + * triangle strips or fans. + * + * @param stripCounts array of strip counts, as used by the + * GeometryStripArray object + * @return number of triangles in the strips or fans + */ + static int getTriangleCount(int stripCounts[]) { + int count = 0 ; + for (int i = 0 ; i < stripCounts.length ; i++) + count += (stripCounts[i] - 2) ; + return count ; + } + + /** + * Counts the number of triangles defined by arrays of + * triangle strips or fans. + * + * @param stripCounts IntList of strip counts + * @return number of triangles in the strips or fans + */ + static int getTriangleCount(IntList stripCounts) { + int count = 0 ; + for (int i = 0 ; i < stripCounts.count ; i++) + count += (stripCounts.ints[i] - 2) ; + return count ; + } + + /** + * Breaks up triangle strips into separate triangles. + * + * @param stripCounts array of strip counts, as used by the + * GeometryStripArray object + * @return array of ints which index into the original vertex array; each + * set of three consecutive vertex indices defines a single triangle + */ + static int[] stripsToTriangles(int stripCounts[]) { + int triangleCount = getTriangleCount(stripCounts) ; + int tbuff[] = new int[3*triangleCount] ; + IntList vertices = new IntList(triangleCount + 2*stripCounts.length) ; + + vertices.fillAscending() ; + stripsToTriangles(0, tbuff, + 0, vertices.ints, + 0, stripCounts, + stripCounts.length) ; + return tbuff ; + } + + /** + * Breaks up triangle fans into separate triangles. + * + * @param stripCounts array of strip counts, as used by the + * GeometryStripArray object + * @return array of ints which index into the original vertex array; each + * set of three consecutive vertex indices defines a single triangle + */ + static int[] fansToTriangles(int stripCounts[]) { + int triangleCount = getTriangleCount(stripCounts) ; + int tbuff[] = new int[3*triangleCount] ; + IntList vertices = new IntList(triangleCount + 2*stripCounts.length) ; + + vertices.fillAscending() ; + fansToTriangles(0, tbuff, + 0, vertices.ints, + 0, stripCounts, + stripCounts.length) ; + return tbuff ; + } + + /** + * Takes a fan and converts it to one or more strips. + * + * @param v index into the fans array of the first vertex in the fan + * @param length number of vertices in the fan + * @param fans array of vertex indices representing one or more fans + * @param convexPlanar if true indicates that the fan is convex and + * planar; such fans will always be converted into a single strip + * @return a StripArray containing the converted strips + */ + static StripArray fanToStrips(int v, int length, int fans[], + boolean convexPlanar) { + + // Initialize IntLists to worst-case sizes. + IntList stripVerts = new IntList(length*3) ; + IntList stripCounts = new IntList(length) ; + + fanToStrips(v, length, fans, stripVerts, stripCounts, convexPlanar) ; + return new StripArray(stripVerts, stripCounts) ; + } +} diff --git a/src/classes/share/javax/media/j3d/GeneralizedStripFlags.java b/src/classes/share/javax/media/j3d/GeneralizedStripFlags.java new file mode 100644 index 0000000..3728010 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeneralizedStripFlags.java @@ -0,0 +1,73 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A class which implements GeneralizedStripFlags provides the means to access + * the vertex replace code flags associated with each vertex of a generalized + * strip. This allows a flexible representation of generalized strips for + * various classes and makes it possible to provide a common subset of static + * methods which operate only on their topology. + * + * @see GeneralizedStrip + * @see GeneralizedVertexList + */ +interface GeneralizedStripFlags { + + /** + * This flag indicates that a vertex starts a new strip with clockwise + * winding. + */ + static final int RESTART_CW = 0 ; + + /** + * This flag indicates that a vertex starts a new strip with + * counter-clockwise winding. + */ + static final int RESTART_CCW = 1 ; + + /** + * This flag indicates that the next triangle in the strip is defined by + * replacing the middle vertex of the previous triangle in the strip. + */ + static final int REPLACE_MIDDLE = 2 ; + + /** + * This flag indicates that the next triangle in the strip is defined by + * replacing the oldest vertex of the previous triangle in the strip. + */ + static final int REPLACE_OLDEST = 3 ; + + /** + * This constant is used to indicate that triangles with clockwise vertex + * winding are front facing. + */ + static final int FRONTFACE_CW = 0 ; + + /** + * This constant is used to indicate that triangles with counter-clockwise + * vertex winding are front facing. + */ + static final int FRONTFACE_CCW = 1 ; + + /** + * Return the number of flags. This should be the same as the number of + * vertices in the generalized strip. + */ + int getFlagCount() ; + + /** + * Return the flag associated with the vertex at the specified index. + */ + int getFlag(int index) ; +} diff --git a/src/classes/share/javax/media/j3d/GeneralizedVertexList.java b/src/classes/share/javax/media/j3d/GeneralizedVertexList.java new file mode 100644 index 0000000..2fc0971 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeneralizedVertexList.java @@ -0,0 +1,387 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.* ; +import java.util.* ; + +/** + * The GeneralizedVertexList class is a variable-size list used to + * collect the vertices for a generalized strip of points, lines, or + * triangles. This is used by the GeometryDecompressor. This class + * implements the GeneralizedStripFlags interface and provides methods + * for copying instance vertex data into various fixed-size + * GeometryArray representations. + * + * @see GeneralizedStrip + * @see GeometryDecompressor + */ +class GeneralizedVertexList implements GeneralizedStripFlags { + + // The ArrayList containing the vertices. + private ArrayList vertices ; + + // Booleans for individual vertex components. + private boolean hasColor3 = false ; + private boolean hasColor4 = false ; + private boolean hasNormals = false ; + + // Indicates the vertex winding of front-facing triangles in this strip. + private int frontFace ; + + /** + * Count of number of strips generated after conversion to GeometryArray. + */ + int stripCount ; + + /** + * Count of number of vertices generated after conversion to GeometryArray. + */ + int vertexCount ; + + /** + * Count of number of triangles generated after conversion to GeometryArray. + */ + int triangleCount ; + + /** + * Bits describing the data bundled with each vertex. This is specified + * using the GeometryArray mask components. + */ + int vertexFormat ; + + /** + * Creates a new GeneralizedVertexList for the specified vertex format. + * @param vertexFormat a mask indicating which components are + * present in each vertex, as used by GeometryArray. + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @param initSize initial number of elements + * @see GeometryArray + */ + GeneralizedVertexList(int vertexFormat, int frontFace, int initSize) { + this.frontFace = frontFace ; + setVertexFormat(vertexFormat) ; + + if (initSize == 0) + vertices = new ArrayList() ; + else + vertices = new ArrayList(initSize) ; + + stripCount = 0 ; + vertexCount = 0 ; + triangleCount = 0 ; + } + + /** + * Creates a new GeneralizedVertexList for the specified vertex format. + * @param vertexFormat a mask indicating which components are + * present in each vertex, as used by GeometryArray. + * @param frontFace a flag, either GeneralizedStripFlags.FRONTFACE_CW or + * GeneralizedStripFlags.FRONTFACE_CCW, indicating front face winding + * @see GeometryArray + */ + GeneralizedVertexList(int vertexFormat, int frontFace) { + this(vertexFormat, frontFace, 0) ; + } + + /** + * Sets the vertex format for this vertex list. + * @param vertexFormat a mask indicating which components are + * present in each vertex, as used by GeometryArray. + */ + void setVertexFormat(int vertexFormat) { + this.vertexFormat = vertexFormat ; + + if ((vertexFormat & GeometryArray.NORMALS) != 0) + hasNormals = true ; + + if ((vertexFormat & GeometryArray.COLOR) != 0) + if ((vertexFormat & GeometryArray.WITH_ALPHA) != 0) + hasColor4 = true ; + else + hasColor3 = true ; + } + + /** + * A class with fields corresponding to all the data that can be bundled + * with the vertices of generalized strips. + */ + class Vertex { + int flag ; + Point3f coord ; + Color3f color3 ; + Color4f color4 ; + Vector3f normal ; + + Vertex(Point3f p, Vector3f n, Color4f c, int flag) { + this.flag = flag ; + coord = new Point3f(p) ; + + if (hasNormals) + normal = new Vector3f(n) ; + + if (hasColor3) + color3 = new Color3f(c.x, c.y, c.z) ; + + else if (hasColor4) + color4 = new Color4f(c) ; + } + } + + /** + * Copy vertex data to a new Vertex object and add it to this list. + */ + void addVertex(Point3f pos, Vector3f norm, Color4f color, int flag) { + vertices.add(new Vertex(pos, norm, color, flag)) ; + } + + /** + * Return the number of vertices in this list. + */ + int size() { + return vertices.size() ; + } + + // GeneralizedStripFlags interface implementation + public int getFlagCount() { + return vertices.size() ; + } + + // GeneralizedStripFlags interface implementation + public int getFlag(int index) { + return ((Vertex)vertices.get(index)).flag ; + } + + // Copy vertices in the given order to a fixed-length GeometryArray. + // Using the array versions of the GeometryArray set() methods results in + // a significant performance improvement despite needing to create + // fixed-length arrays to hold the vertex elements. + private void copyVertexData(GeometryArray ga, + GeneralizedStrip.IntList indices) { + Vertex v ; + Point3f p3f[] = new Point3f[indices.count] ; + + if (hasNormals) { + Vector3f v3f[] = new Vector3f[indices.count] ; + if (hasColor3) { + Color3f c3f[] = new Color3f[indices.count] ; + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + v3f[i] = v.normal ; + c3f[i] = v.color3 ; + } + ga.setColors(0, c3f) ; + + } else if (hasColor4) { + Color4f c4f[] = new Color4f[indices.count] ; + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + v3f[i] = v.normal ; + c4f[i] = v.color4 ; + } + ga.setColors(0, c4f) ; + + } else { + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + v3f[i] = v.normal ; + } + } + ga.setNormals(0, v3f) ; + + } else { + if (hasColor3) { + Color3f c3f[] = new Color3f[indices.count] ; + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + c3f[i] = v.color3 ; + } + ga.setColors(0, c3f) ; + + } else if (hasColor4) { + Color4f c4f[] = new Color4f[indices.count] ; + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + c4f[i] = v.color4 ; + } + ga.setColors(0, c4f) ; + + } else { + for (int i = 0 ; i < indices.count ; i++) { + v = (Vertex)vertices.get(indices.ints[i]) ; + p3f[i] = v.coord ; + } + } + } + ga.setCoordinates(0, p3f) ; + } + + /** + * Output a PointArray. + */ + PointArray toPointArray() { + int size = vertices.size() ; + + if (size > 0) { + PointArray pa = new PointArray(size, vertexFormat) ; + GeneralizedStrip.IntList il = new GeneralizedStrip.IntList(size) ; + + il.fillAscending() ; + copyVertexData(pa, il) ; + + vertexCount += size ; + return pa ; + } + else + return null ; + } + + /** + * Output a TriangleArray. + */ + TriangleArray toTriangleArray() { + int vertices[] = GeneralizedStrip.toTriangles(this, frontFace) ; + + if (vertices != null) { + TriangleArray ta ; + GeneralizedStrip.IntList il ; + + ta = new TriangleArray(vertices.length, vertexFormat) ; + il = new GeneralizedStrip.IntList(vertices) ; + copyVertexData(ta, il) ; + + vertexCount += vertices.length ; + triangleCount += vertices.length/3 ; + return ta ; + } else + return null ; + } + + /** + * Output a LineStripArray. + */ + LineStripArray toLineStripArray() { + GeneralizedStrip.StripArray stripArray = + GeneralizedStrip.toLineStrips(this) ; + + if (stripArray != null) { + LineStripArray lsa ; + lsa = new LineStripArray(stripArray.vertices.count, + vertexFormat, + stripArray.stripCounts.trim()) ; + + copyVertexData(lsa, stripArray.vertices) ; + + vertexCount += stripArray.vertices.count ; + stripCount += stripArray.stripCounts.count ; + return lsa ; + } else + return null ; + } + + /** + * Output a TriangleStripArray. + */ + TriangleStripArray toTriangleStripArray() { + GeneralizedStrip.StripArray stripArray = + GeneralizedStrip.toTriangleStrips(this, frontFace) ; + + if (stripArray != null) { + TriangleStripArray tsa ; + tsa = new TriangleStripArray(stripArray.vertices.count, + vertexFormat, + stripArray.stripCounts.trim()) ; + + copyVertexData(tsa, stripArray.vertices) ; + + vertexCount += stripArray.vertices.count ; + stripCount += stripArray.stripCounts.count ; + return tsa ; + } else + return null ; + } + + /** + * Output triangle strip and triangle fan arrays. + * @return a 2-element array of GeometryStripArray; element 0 if non-null + * will contain a TriangleStripArray, and element 1 if non-null will + * contain a TriangleFanArray. + */ + GeometryStripArray[] toStripAndFanArrays() { + GeneralizedStrip.StripArray stripArray[] = + GeneralizedStrip.toStripsAndFans(this, frontFace) ; + + GeometryStripArray gsa[] = new GeometryStripArray[2] ; + + if (stripArray[0] != null) { + gsa[0] = new TriangleStripArray(stripArray[0].vertices.count, + vertexFormat, + stripArray[0].stripCounts.trim()) ; + + copyVertexData(gsa[0], stripArray[0].vertices) ; + + vertexCount += stripArray[0].vertices.count ; + stripCount += stripArray[0].stripCounts.count ; + } + + if (stripArray[1] != null) { + gsa[1] = new TriangleFanArray(stripArray[1].vertices.count, + vertexFormat, + stripArray[1].stripCounts.trim()) ; + + copyVertexData(gsa[1], stripArray[1].vertices) ; + + vertexCount += stripArray[1].vertices.count ; + stripCount += stripArray[1].stripCounts.count ; + } + return gsa ; + } + + /** + * Output triangle strip and and triangle arrays. + * @return a 2-element array of GeometryArray; element 0 if non-null + * will contain a TriangleStripArray, and element 1 if non-null will + * contain a TriangleArray. + */ + GeometryArray[] toStripAndTriangleArrays() { + GeneralizedStrip.StripArray stripArray[] = + GeneralizedStrip.toStripsAndTriangles(this, frontFace, 4, 12) ; + + GeometryArray ga[] = new GeometryArray[2] ; + + if (stripArray[0] != null) { + ga[0] = new TriangleStripArray(stripArray[0].vertices.count, + vertexFormat, + stripArray[0].stripCounts.trim()) ; + + copyVertexData(ga[0], stripArray[0].vertices) ; + + vertexCount += stripArray[0].vertices.count ; + stripCount += stripArray[0].stripCounts.count ; + } + + if (stripArray[1] != null) { + ga[1] = new TriangleArray(stripArray[1].vertices.count, + vertexFormat) ; + + copyVertexData(ga[1], stripArray[1].vertices) ; + triangleCount += stripArray[1].vertices.count/3 ; + } + return ga ; + } +} diff --git a/src/classes/share/javax/media/j3d/Geometry.java b/src/classes/share/javax/media/j3d/Geometry.java new file mode 100644 index 0000000..7ffc858 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Geometry.java @@ -0,0 +1,45 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Geometry is an abstract class that specifies the geometry + * component information required by a Shape3D node. Geometry objects + * describe both the geometry and topology of the Shape3D nodes that + * reference them. Geometry objects consist of four generic geometric + * types:<P> + * <UL><LI>Compressed Geometry</LI> + * <LI>GeometryArray</LI> + * <LI>Raster</LI> + * <LI>Text3D</LI> + * </UL><P> + * Each of these geometric types defines a visible object or set of + * objects. A Geometry object is used as a component object of a Shape3D + * leaf node. + * + */ + +public abstract class Geometry extends NodeComponent { + + /** + * Specifies that this Geometry allows intersect operation. + */ + public static final int + ALLOW_INTERSECT = CapabilityBits.GEOMETRY_ALLOW_INTERSECT; + + /** + * Constructs a new Geometry object. + */ + public Geometry() { + } +} diff --git a/src/classes/share/javax/media/j3d/GeometryArray.java b/src/classes/share/javax/media/j3d/GeometryArray.java new file mode 100644 index 0000000..82aa50b --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryArray.java @@ -0,0 +1,5763 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + + +/** + * The GeometryArray object contains separate arrays of positional + * coordinates, colors, normals, and texture coordinates that + * describe point, line, or polygon geometry. This class is extended + * to create the various primitive types (such as lines, + * triangle strips, etc.). + * Vertex data may be passed to this geometry array in one of two + * ways: by copying the data into the array using the existing + * methods, or by passing a reference to the data. + * <p> + * <ul> + * <li> + * <b>By Copying:</b> + * The existing methods for setting positional coordinates, colors, + * normals, and texture coordinates (such as <code>setCoordinate</code>, + * <code>setColors</code>, etc.) copy the data into this + * GeometryArray. This is appropriate for many applications and + * offers an application much flexibility in organizing its data. + * This is the default mode. + * </li> + * <li><b>By Reference:</b> + * A new set of methods in Java 3D version 1.2 allows data to be + * accessed by reference, directly from the user's arrays. To use + * this feature, set the <code>BY_REFERENCE</code> bit in the + * <code>vertexFormat</code> field of the constructor for this + * GeometryArray. In this mode, the various set methods for + * coordinates, normals, colors, and texture coordinates are not used. + * Instead, new methods are used to set a reference to user-supplied + * coordinate, color, normal, and texture coordinate arrays (such as + * <code>setCoordRefFloat</code>, <code>setColorRefFloat</code>, + * etc.). Data in any array that is referenced by a live or compiled + * GeometryArray object may only be modified via the + * <code>updateData</code> method (subject to the + * <code>ALLOW_REF_DATA_WRITE</code> capability bit). Applications + * must exercise care not to violate this rule. If any referenced + * geometry data is modified outside of the <code>updateData</code> + * method, the results are undefined. + * </li> + * </ul> + * <p> + * All colors used in the geometry array object must be in the range [0.0,1.0]. + * Values outside this range will cause undefined results. + * All normals used in the geometry array object must be unit length + * vectors. That is their geometric length must be 1.0. Normals that + * are not unit length vectors will cause undefined results. + * <p> + * Note that the term <i>coordinate</i>, as used in the method names + * and method descriptions, actually refers to a set of <i>x</i>, + * <i>y</i>, and <i>z</i> coordinates representing the position of a + * single vertex. The term <i>coordinates</i> (plural) is used to + * indicate sets of <i>x</i>, <i>y</i>, and <i>z</i> coordinates for + * multiple vertices. This is somewhat at odds with the mathematical + * definition of a coordinate, but is used as a convenient shorthand. + * Similarly, the term <i>texture coordinate</i> is used to indicate a + * set of texture coordinates for a single vertex, while the term + * <i>texture coordinates</i> (plural) is used to indicate sets of + * texture coordinates for multiple vertices. + */ + +public abstract class GeometryArray extends Geometry { + + /** + * Specifies that this GeometryArray allows reading the array of + * coordinates. + */ + public static final int + ALLOW_COORDINATE_READ = CapabilityBits.GEOMETRY_ARRAY_ALLOW_COORDINATE_READ; + + /** + * Specifies that this GeometryArray allows writing the array of + * coordinates. + */ + public static final int + ALLOW_COORDINATE_WRITE = CapabilityBits.GEOMETRY_ARRAY_ALLOW_COORDINATE_WRITE; + + /** + * Specifies that this GeometryArray allows reading the array of + * colors. + */ + public static final int + ALLOW_COLOR_READ = CapabilityBits.GEOMETRY_ARRAY_ALLOW_COLOR_READ; + + /** + * Specifies that this GeometryArray allows writing the array of + * colors. + */ + public static final int + ALLOW_COLOR_WRITE = CapabilityBits.GEOMETRY_ARRAY_ALLOW_COLOR_WRITE; + + /** + * Specifies that this GeometryArray allows reading the array of + * normals. + */ + public static final int + ALLOW_NORMAL_READ = CapabilityBits.GEOMETRY_ARRAY_ALLOW_NORMAL_READ; + + /** + * Specifies that this GeometryArray allows writing the array of + * normals. + */ + public static final int + ALLOW_NORMAL_WRITE = CapabilityBits.GEOMETRY_ARRAY_ALLOW_NORMAL_WRITE; + + /** + * Specifies that this GeometryArray allows reading the array of + * texture coordinates. + */ + public static final int + ALLOW_TEXCOORD_READ = CapabilityBits.GEOMETRY_ARRAY_ALLOW_TEXCOORD_READ; + + /** + * Specifies that this GeometryArray allows writing the array of + * texture coordinates. + */ + public static final int + ALLOW_TEXCOORD_WRITE = CapabilityBits.GEOMETRY_ARRAY_ALLOW_TEXCOORD_WRITE; + + /** + * Specifies that this GeometryArray allows reading the count or + * initial index information for this object. + */ + public static final int + ALLOW_COUNT_READ = CapabilityBits.GEOMETRY_ARRAY_ALLOW_COUNT_READ; + + /** + * Specifies that this GeometryArray allows writing the count or + * initial index information for this object. + * + * @since Java 3D 1.2 + */ + public static final int + ALLOW_COUNT_WRITE = CapabilityBits.GEOMETRY_ARRAY_ALLOW_COUNT_WRITE; + + /** + * Specifies that this GeometryArray allows reading the vertex format + * information for this object. + */ + public static final int + ALLOW_FORMAT_READ = CapabilityBits.GEOMETRY_ARRAY_ALLOW_FORMAT_READ; + + /** + * Specifies that this GeometryArray allows reading the geometry + * data reference information for this object. This is only used in + * by-reference geometry mode. + * + * @since Java 3D 1.2 + */ + public static final int + ALLOW_REF_DATA_READ = CapabilityBits.GEOMETRY_ARRAY_ALLOW_REF_DATA_READ; + + private static final int J3D_1_2_ALLOW_REF_DATA_READ = + CapabilityBits.J3D_1_2_GEOMETRY_ARRAY_ALLOW_REF_DATA_READ; + + /** + * Specifies that this GeometryArray allows writing the geometry + * data reference information for this object. It also enables + * writing the referenced data itself, via the GeometryUpdater + * interface. This is only used in by-reference geometry mode. + * + * @since Java 3D 1.2 + */ + public static final int + ALLOW_REF_DATA_WRITE = CapabilityBits.GEOMETRY_ARRAY_ALLOW_REF_DATA_WRITE; + + /** + * Specifies that this GeometryArray contains an array of coordinates. + * This bit must be set. + */ + public static final int COORDINATES = 0x01; + + /** + * Specifies that this GeometryArray contains an array of normals. + */ + public static final int NORMALS = 0x02; + + /** + * Specifies that this GeometryArray contains an array of colors. + */ + static final int COLOR = 0x04; + + /** + * Specifies that this GeometryArray's colors contain alpha. + */ + static final int WITH_ALPHA = 0x08; + + /** + * Specifies that this GeometryArray contains an array of colors without alpha. + */ + public static final int COLOR_3 = COLOR; + + /** + * Specifies that this GeometryArray contains an array of colors with alpha. + * This takes precedence over COLOR_3. + */ + public static final int COLOR_4 = COLOR | WITH_ALPHA; + + /** + * Specifies that this GeometryArray contains one or more arrays of + * 2D texture coordinates. + */ + public static final int TEXTURE_COORDINATE_2 = 0x20; + + /** + * Specifies that this GeometryArray contains one or more arrays of + * 3D texture coordinates. + * This takes precedence over TEXTURE_COORDINATE_2. + */ + public static final int TEXTURE_COORDINATE_3 = 0x40; + + + /** + * Specifies that this GeometryArray contains one or more arrays of + * 4D texture coordinates. + * This takes precedence over TEXTURE_COORDINATE_2 and TEXTURE_COORDINATE_3. + * + * @since Java 3D 1.3 + */ + public static final int TEXTURE_COORDINATE_4 = 0x400; + + + static final int TEXTURE_COORDINATE = TEXTURE_COORDINATE_2 | + TEXTURE_COORDINATE_3 | + TEXTURE_COORDINATE_4; + + /** + * Specifies that the position, color, normal, and texture coordinate + * data for this GeometryArray are accessed by reference. + * + * @since Java 3D 1.2 + */ + public static final int BY_REFERENCE = 0x80; + + + /** + * Specifies that the position, color, normal, and texture + * coordinate data for this GeometryArray are accessed via a single + * interleaved, floating-point array reference. All of the data + * values for each vertex are stored in consecutive memory + * locations. This flag is only valid in conjunction with the + * <code>BY_REFERENCE</code> flag. + * + * @since Java 3D 1.2 + */ + public static final int INTERLEAVED = 0x100; + + /** + * Specifies that geometry by-reference data for this + * GeometryArray, whether interleaved or non-interleaved, is + * accessed via J3DBuffer objects that wrap NIO Buffer objects, + * rather than float, double, byte, or TupleXX arrays. This flag + * is only valid in conjunction with the <code>BY_REFERENCE</code> + * flag. + * + * <p> + * NOTE: Use of this class requires version 1.4 of the + * Java<sup><font size="-2">TM</font></sup> 2 Platform. + * + * @see J3DBuffer + * @see #setCoordRefBuffer(J3DBuffer) + * @see #setColorRefBuffer(J3DBuffer) + * @see #setNormalRefBuffer(J3DBuffer) + * @see #setTexCoordRefBuffer(int,J3DBuffer) + * @see #setInterleavedVertexBuffer(J3DBuffer) + * + * @since Java 3D 1.3 + */ + public static final int USE_NIO_BUFFER = 0x800; + + /** + * Specifies that only the coordinate indices are used for indexed + * geometry arrays. In this mode, the values from the coordinate + * index array are used as a single set of index values to access + * the vertex data for all four vertex components (coord, color, + * normal, and texCoord). The color, normal, and texCoord index arrays + * are ignored. This flag is only valid for indexed geometry arrays + * (subclasses of IndexedGeometryArray). + * + * @since Java 3D 1.3 + */ + public static final int USE_COORD_INDEX_ONLY = 0x200; + + // Used to keep track of the last bit (for adding new bits only) + private static final int LAST_FORMAT_BIT = 0x800; + + + // Scratch arrays for converting Point[234]f to TexCoord[234]f + private TexCoord2f [] texCoord2fArray = null; + private TexCoord3f [] texCoord3fArray = null; + private TexCoord4f [] texCoord4fArray = null; + private TexCoord2f texCoord2fScratch = null; + private TexCoord3f texCoord3fScratch = null; + + + + // non-public, no parameter constructor + GeometryArray() { + } + + + /** + * Constructs an empty GeometryArray object with the specified + * number of vertices and vertex format. Defaults are used + * for all other parameters. The default values are as follows: + * <ul> + * texCoordSetCount : 1<br> + * texCoordSetMap : { 0 }<br> + * validVertexCount : vertexCount<br> + * initialVertexIndex : 0<br> + * initialCoordIndex : 0<br> + * initialColorIndex : 0<br> + * initialNormalIndex : 0<br> + * initialTexCoordIndex : 0<br> + * all data array values : 0.0<br> + * all data array references : null<br> + * </ul> + * + * @param vertexCount the number of vertex elements in this GeometryArray + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: <code>COORDINATES</code>, to signal the inclusion of + * vertex positions--always present; <code>NORMALS</code>, to signal + * the inclusion of per vertex normals; one of <code>COLOR_3</code> or + * <code>COLOR_4</code>, to signal the inclusion of per vertex + * colors (without or with alpha information); one of + * <code>TEXTURE_COORDINATE_2</code>, <code>TEXTURE_COORDINATE_3</code> + * or <code>TEXTURE_COORDINATE_4</code>, + * to signal the + * inclusion of per-vertex texture coordinates (2D, 3D or 4D); + * <code>BY_REFERENCE</code>, to indicate that the data is passed + * by reference + * rather than by copying; <code>INTERLEAVED</code>, to indicate + * that the referenced + * data is interleaved in a single array; + * <code>USE_NIO_BUFFER</code>, to indicate that the referenced data + * is accessed via a J3DBuffer object that wraps an NIO buffer; + * <code>USE_COORD_INDEX_ONLY</code>, + * to indicate that only the coordinate indices are used for indexed + * geometry arrays. + * @exception IllegalArgumentException if vertexCount < 0, if + * vertexFormat does NOT include <code>COORDINATES</code>, + * if the <code>USE_COORD_INDEX_ONLY</code> bit is set for non-indexed + * geometry arrays (that is, GeometryArray objects that are not a + * subclass of IndexedGeometryArray), + * if the <code>INTERLEAVED</code> bit is set without the + * <code>BY_REFERENCE</code> bit being set, + * or if the <code>USE_NIO_BUFFER</code> bit is set without the + * <code>BY_REFERENCE</code> bit being set. + */ + public GeometryArray(int vertexCount, int vertexFormat) { + + if (vertexCount < 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray96")); + + if ((vertexFormat & COORDINATES) == 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray0")); + + if ((vertexFormat & INTERLEAVED) != 0 && + (vertexFormat & BY_REFERENCE) == 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray80")); + + if ((vertexFormat & USE_NIO_BUFFER) != 0 && + (vertexFormat & BY_REFERENCE) == 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray117")); + + if ((vertexFormat & TEXTURE_COORDINATE) != 0) { + if ((vertexFormat & TEXTURE_COORDINATE_2) != 0) { + texCoord2fArray = new TexCoord2f[1]; + texCoord2fScratch = new TexCoord2f(); + } + else if ((vertexFormat & TEXTURE_COORDINATE_3) != 0) { + texCoord3fArray = new TexCoord3f[1]; + texCoord3fScratch = new TexCoord3f(); + } else if ((vertexFormat & TEXTURE_COORDINATE_4) != 0) { + texCoord4fArray = new TexCoord4f[1]; + } + } + + ((GeometryArrayRetained)this.retained).createGeometryArrayData(vertexCount, vertexFormat); + } + + + /** + * Constructs an empty GeometryArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, and texture coordinate mapping array. Defaults are used + * for all other parameters. + * + * @param vertexCount the number of vertex elements in this + * GeometryArray<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: <code>COORDINATES</code>, to signal the inclusion of + * vertex positions--always present; <code>NORMALS</code>, to signal + * the inclusion of per vertex normals; one of <code>COLOR_3</code> or + * <code>COLOR_4</code>, to signal the inclusion of per vertex + * colors (without or with alpha information); one of + * <code>TEXTURE_COORDINATE_2</code> or <code>TEXTURE_COORDINATE_3</code> + * or <code>TEXTURE_COORDINATE_4</code>, + * to signal the + * inclusion of per-vertex texture coordinates (2D , 3D or 4D); + * <code>BY_REFERENCE</code>, to indicate that the data is passed + * by reference + * rather than by copying; <code>INTERLEAVED</code>, to indicate + * that the referenced + * data is interleaved in a single array; + * <code>USE_NIO_BUFFER</code>, to indicate that the referenced data + * is accessed via a J3DBuffer object that wraps an NIO buffer; + * <code>USE_COORD_INDEX_ONLY</code>, + * to indicate that only the coordinate indices are used for indexed + * geometry arrays.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code> or + * <code>TEXTURE_COORDINATE_3</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * <a name="texCoordSetMap"> + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code> or + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used. The following example + * illustrates the use of the <code>texCoordSetMap</code> array. + * + * <p> + * <ul> + * <table BORDER=1 CELLSPACING=1 CELLPADDING=1> + * <tr> + * <td><center><b>Index</b></center></td> + * <td><center><b>Element</b></center></td> + * <td><center><b>Description</b></center></td> + * </tr> + * <tr> + * <td><center>0</center></td> + * <td><center>1</center></td> + * <td><center>Use tex coord set 1 for tex unit 0</center></td> + * </tr> + * <tr> + * <td><center>1</center></td> + * <td><center>-1</center></td> + * <td><center>Use no tex coord set for tex unit 1</center></td> + * </tr> + * <tr> + * <td><center>2</center></td> + * <td><center>0</center></td> + * <td><center>Use tex coord set 0 for tex unit 2</center></td> + * </tr> + * <tr> + * <td><center>3</center></td> + * <td><center>1</center></td> + * <td><center>Reuse tex coord set 1 for tex unit 3</center></td> + * </tr> + * </table> + * </ul> + * <p> + * + * @exception IllegalArgumentException if + * <code>vertexCount < 0</code>, if vertexFormat does + * NOT include <code>COORDINATES</code>, if the + * <code>INTERLEAVED</code> bit is set without the + * <code>BY_REFERENCE</code> bit being set, if the + * <code>USE_NIO_BUFFER</code> bit is set without the + * <code>BY_REFERENCE</code> bit being set, if + * the <code>USE_COORD_INDEX_ONLY</code> bit is set for non-indexed + * geometry arrays (that is, GeometryArray objects that are not a + * subclass of IndexedGeometryArray), if + * <code>texCoordSetCount < 0</code>, or if any element + * in <code>texCoordSetMap[] >= texCoordSetCount</code>. + * + * @since Java 3D 1.2 + */ + public GeometryArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap) { + + if (vertexCount < 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray96")); + + if ((vertexFormat & COORDINATES) == 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray0" +)); + + if ((vertexFormat & INTERLEAVED) != 0 && + (vertexFormat & BY_REFERENCE) == 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray80")); + + if ((vertexFormat & USE_NIO_BUFFER) != 0 && + (vertexFormat & BY_REFERENCE) == 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray117")); + + if ((vertexFormat & TEXTURE_COORDINATE) != 0) { + if (texCoordSetMap == null) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray106")); + + if (texCoordSetCount == 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray107")); + + for (int i = 0; i < texCoordSetMap.length; i++) { + if (texCoordSetMap[i] >= texCoordSetCount) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray108")); + } + + if ((vertexFormat & TEXTURE_COORDINATE_2) != 0) { + texCoord2fArray = new TexCoord2f[1]; + texCoord2fScratch = new TexCoord2f(); + } + else if ((vertexFormat & TEXTURE_COORDINATE_3) != 0) { + texCoord3fArray = new TexCoord3f[1]; + texCoord3fScratch = new TexCoord3f(); + } + else if ((vertexFormat & TEXTURE_COORDINATE_4) != 0) { + texCoord4fArray = new TexCoord4f[1]; + } + } + + ((GeometryArrayRetained)this.retained).createGeometryArrayData(vertexCount, vertexFormat, texCoordSetCount, texCoordSetMap); + + } + + + //------------------------------------------------------------------ + // Common methods + //------------------------------------------------------------------ + + /** + * Retrieves the number of vertices in this GeometryArray + * @return number of vertices in this GeometryArray + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + */ + public int getVertexCount() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray1")); + + return ((GeometryArrayRetained)this.retained).getVertexCount(); + } + + /** + * Retrieves the vertexFormat of this GeometryArray + * @return format of vertices in this GeometryArray + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + */ + public int getVertexFormat() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_FORMAT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray2")); + + return ((GeometryArrayRetained)this.retained).getVertexFormat(); + } + + + /** + * Retrieves the number of texture coordinate sets in this + * GeometryArray object. + * + * @return the number of texture coordinate sets + * in this GeometryArray object + * + * @since Java 3D 1.2 + */ + public int getTexCoordSetCount() { + return ((GeometryArrayRetained)this.retained).getTexCoordSetCount(); + } + + + /** + * Retrieves the length of the texture coordinate set mapping + * array of this GeometryArray object. + * + * @return the length of the texture coordinate set mapping + * array of this GeometryArray object + * + * @since Java 3D 1.2 + */ + public int getTexCoordSetMapLength() { + return ((GeometryArrayRetained)this.retained).getTexCoordSetMapLength(); + } + + + /** + * Retrieves the texture coordinate set mapping + * array from this GeometryArray object. + * + * @param texCoordSetMap an array that will receive a copy of the + * texture coordinate set mapping array. The array must be large + * enough to hold all entries of the texture coordinate set + * mapping array. + * + * @since Java 3D 1.2 + */ + public void getTexCoordSetMap(int[] texCoordSetMap) { + ((GeometryArrayRetained)this.retained).getTexCoordSetMap(texCoordSetMap); + return; + } + + + /** + * Updates geometry array data that is accessed by reference. + * This method calls the updateData method of the specified + * GeometryUpdater object to synchronize updates to vertex + * data that is referenced by this GeometryArray object. + * Applications that wish to modify such data must perform all + * updates via this method. + * <p> + * This method may also be used to atomically set multiple + * references (for example, to coordinate and color arrays) + * or to atomically + * change multiple data values through the geometry data copying + * methods. + * + * @param updater object whose updateData callback method will be + * called to update the data referenced by this GeometryArray. + * @exception CapabilityNotSetException if the appropriate capability + * is not set, the vertex data mode is <code>BY_REFERENCE</code>, and this + * object is part of a live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void updateData(GeometryUpdater updater) { + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0 && + isLiveOrCompiled() && + !this.getCapability(ALLOW_REF_DATA_WRITE)) { + + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray81")); + } + + ((GeometryArrayRetained)this.retained).updateData(updater); + } + + + /** + * Sets the valid vertex count for this GeometryArray object. + * This count specifies the number of vertices actually used in + * rendering or other operations such as picking and collision. + * This attribute is initialized to <code>vertexCount</code>. + * + * @param validVertexCount the new valid vertex count. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * <p> + * @exception IllegalArgumentException if any of the following are + * true: + * <ul> + * <code>validVertexCount < 0</code>,<br> + * <code>initialVertexIndex + validVertexCount > vertexCount</code>,<br> + * <code>initialCoordIndex + validVertexCount > vertexCount</code>,<br> + * <code>initialColorIndex + validVertexCount > vertexCount</code>,<br> + * <code>initialNormalIndex + validVertexCount > vertexCount</code>,<br> + * <code>initialTexCoordIndex + validVertexCount > vertexCount</code> + * </ul> + * <p> + * @exception ArrayIndexOutOfBoundsException if the geometry data format + * is <code>BY_REFERENCE</code> and any the following + * are true for non-null array references: + * <ul> + * <code>CoordRef.length</code> < <i>num_words</i> * + * (<code>initialCoordIndex + validVertexCount</code>),<br> + * <code>ColorRef.length</code> < <i>num_words</i> * + * (<code>initialColorIndex + validVertexCount</code>),<br> + * <code>NormalRef.length</code> < <i>num_words</i> * + * (<code>initialNormalIndex + validVertexCount</code>),<br> + * <code>TexCoordRef.length</code> < <i>num_words</i> * + * (<code>initialTexCoordIndex + validVertexCount</code>),<br> + * <code>InterleavedVertices.length</code> < <i>words_per_vertex</i> * + * (<code>initialVertexIndex + validVertexCount</code>)<br> + * </ul> + * where <i>num_words</i> depends on which variant of + * <code>set</code><i>Array</i><code>Ref</code> is used, and + * <i>words_per_vertex</i> depends on which vertex formats are enabled + * for interleaved arrays. + * + * @since Java 3D 1.2 + */ + public void setValidVertexCount(int validVertexCount) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray88")); + + if (validVertexCount < 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray96")); + + ((GeometryArrayRetained)this.retained).setValidVertexCount(validVertexCount); + // NOTE: the checks for initial*Index + validVertexCount > + // vertexCount need to be done in the retained method + } + + + /** + * Gets the valid vertex count for this GeometryArray object. + * For geometry strip primitives (subclasses of GeometryStripArray), + * the valid vertex count is defined to be the sum of the + * stripVertexCounts array. + * @return the current valid vertex count + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getValidVertexCount() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray89")); + + return ((GeometryArrayRetained)this.retained).getValidVertexCount(); + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> + * into the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + // vertexFormat and vertexCount are copied in subclass when constructor + // public GeometryArray(int vertexCount, int vertexFormat) is used + // in cloneNodeComponent() + GeometryArrayRetained src = (GeometryArrayRetained) originalNodeComponent.retained; + GeometryArrayRetained dst = (GeometryArrayRetained) retained; + int format = src.getVertexFormat(); + if ((format & BY_REFERENCE) == 0) { + System.arraycopy(src.vertexData, 0, dst.vertexData, 0, + src.vertexData.length); + dst.setInitialVertexIndex(src.getInitialVertexIndex()); + + } else { + dst.setInitialCoordIndex(src.getInitialCoordIndex()); + dst.setInitialColorIndex(src.getInitialColorIndex()); + dst.setInitialNormalIndex(src.getInitialNormalIndex()); + int setCount = src.getTexCoordSetCount(); + for (int i=0; i < setCount; i++) { + dst.setInitialTexCoordIndex(i, src.getInitialTexCoordIndex(i)); + } + if ((format & INTERLEAVED) == 0) { + dst.setCoordRefFloat(src.getCoordRefFloat()); + dst.setCoordRefDouble(src.getCoordRefDouble()); + dst.setCoordRef3f(src.getCoordRef3f()); + dst.setCoordRef3d(src.getCoordRef3d()); + dst.setColorRefFloat(src.getColorRefFloat()); + dst.setColorRefByte(src.getColorRefByte()); + if ((format & WITH_ALPHA) == 0) { + dst.setColorRef3f(src.getColorRef3f()); + dst.setColorRef3b(src.getColorRef3b()); + } else { + dst.setColorRef4f(src.getColorRef4f()); + dst.setColorRef4b(src.getColorRef4b()); + } + dst.setNormalRefFloat(src.getNormalRefFloat()); + dst.setNormalRef3f(src.getNormalRef3f()); + for (int i=0; i < setCount; i++) { + dst.setTexCoordRefFloat(i, src.getTexCoordRefFloat(i)); + } + if ((format & TEXTURE_COORDINATE_2) != 0) { + for (int i=0; i < setCount; i++) { + dst.setTexCoordRef2f(i, src.getTexCoordRef2f(i)); + } + } + if ((format & TEXTURE_COORDINATE_3) != 0) { + for (int i=0; i < setCount; i++) { + dst.setTexCoordRef3f(i, src.getTexCoordRef3f(i)); + } + } + } else { + dst.setInterleavedVertices(src.getInterleavedVertices()); + } + } + } + + + //------------------------------------------------------------------ + // By-copying methods + //------------------------------------------------------------------ + + /** + * Sets the initial vertex index for this GeometryArray object. + * This index specifies the first vertex within this geometry + * array that is actually used in rendering or other operations + * such as picking and collision. This attribute is initialized + * to 0. + * This attribute is only used when the data mode for this + * geometry array object is not <code>BY_REFERENCE</code> + * or when the data mode is <code>INTERLEAVED</code>. + * + * @param initialVertexIndex the new initial vertex index. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalArgumentException if either of the following are + * true: + * <ul> + * <code>initialVertexIndex < 0</code> or<br> + * <code>initialVertexIndex + validVertexCount > vertexCount</code><br> + * </ul> + * + * @exception ArrayIndexOutOfBoundsException if the geometry data format + * is <code>INTERLEAVED</code>, the InterleavedVertices array is + * non-null, and: + * <ul> + * <code>InterleavedVertices.length</code> < <i>num_words</i> * + * (<code>initialVertexIndex + validVertexCount</code>)<br> + * </ul> + * where <i>num_words</i> depends on which vertex formats are enabled. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code> and is <i>not</i> + * <code>INTERLEAVED</code>. + * + * @since Java 3D 1.2 + */ + public void setInitialVertexIndex(int initialVertexIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray90")); + + if (initialVertexIndex < 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray97")); + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0 && (format & INTERLEAVED) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray105")); + + ((GeometryArrayRetained)this.retained).setInitialVertexIndex(initialVertexIndex); + // NOTE: the check for initialVertexIndex + validVertexCount > + // vertexCount is done in the retained method + } + + + /** + * Gets the initial vertex index for this GeometryArray object. + * This attribute is only used when the data mode for this + * geometry array object is <i>not</i> <code>BY_REFERENCE</code> + * or when the data mode is <code>INTERLEAVED</code>. + * @return the current initial vertex index for this GeometryArray object. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getInitialVertexIndex() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray91")); + + return ((GeometryArrayRetained)this.retained).getInitialVertexIndex(); + } + + + /** + * Sets the coordinate associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param coordinate source array of 3 values containing the new coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinate(int index, float coordinate[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray3")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinate(index, coordinate); + } + + /** + * Sets the coordinate associated with the vertex at + * the specified index. + * @param index destination vertex index in this geometry array + * @param coordinate source array of 3 values containing the new coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinate(int index, double coordinate[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray3")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinate(index, coordinate); + } + + /** + * Sets the coordinate associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param coordinate a point containing the new coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinate(int index, Point3f coordinate) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray3")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinate(index, coordinate); + } + + /** + * Sets the coordinate associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param coordinate a point containing the new coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinate(int index, Point3d coordinate) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray3")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinate(index, coordinate); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param coordinates source array of 3*n values containing n new coordinates + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinates(int index, float coordinates[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray7")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinates(index, coordinates); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param coordinates source array of 3*n values containing n new coordinates + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinates(int index, double coordinates[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray7")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinates(index, coordinates); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param coordinates source array of points containing new coordinates + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinates(int index, Point3f coordinates[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray7")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinates(index, coordinates); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param coordinates source array of points containing new coordinates + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinates(int index, Point3d coordinates[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray7")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinates(index, coordinates); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object using coordinate data starting + * from vertex index <code>start</code> for <code>length</code> vertices. + * @param index starting destination vertex index in this geometry array + * @param coordinates source array of 3*n values containing n new coordinates + * @param start starting source vertex index in <code>coordinates</code> array. + * @param length number of vertices to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinates(int index, float coordinates[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray7")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinates(index, coordinates, + start, length); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object using coordinate data starting + * from vertex index <code>start</code> for <code>length</code> vertices. + * @param index starting destination vertex index in this geometry array + * @param coordinates source array of 3*n values containing n new coordinates + * @param start starting source vertex index in <code>coordinates</code> array. + * @param length number of vertices to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinates(int index, double coordinates[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray7")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinates(index, coordinates, + start, length); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object using coordinate data starting + * from vertex index <code>start</code> for <code>length</code> vertices. + * @param index starting destination vertex index in this geometry array + * @param coordinates source array of points containing new coordinates + * @param start starting source vertex index in <code>coordinates</code> array. + * @param length number of vertices to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinates(int index, Point3f coordinates[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray7")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinates(index, coordinates, + start, length); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object using coordinate data starting + * from vertex index <code>start</code> for <code>length</code> vertices. + * @param index starting destination vertex index in this geometry array + * @param coordinates source array of points containing new coordinates + * @param start starting source vertex index in <code>coordinates</code> array. + * @param length number of vertices to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setCoordinates(int index, Point3d coordinates[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray7")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).setCoordinates(index, coordinates, + start, length); + } + + /** + * Sets the color associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param color source array of 3 or 4 values containing the new color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setColor(int index, float color[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray15")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).setColor(index, color); + } + + /** + * Sets the color associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param color source array of 3 or 4 values containing the new color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setColor(int index, byte color[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray15")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).setColor(index, color); + } + + /** + * Sets the color associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param color a Color3f containing the new color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in the vertex + * format + */ + public void setColor(int index, Color3f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray15")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).setColor(index, color); + } + + /** + * Sets the color associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param color a Color4f containing the new color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in the vertex + * format + */ + public void setColor(int index, Color4f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray15")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).setColor(index, color); + } + + /** + * Sets the color associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param color a Color3b containing the new color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in the vertex + * format + */ + public void setColor(int index, Color3b color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray15")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).setColor(index, color); + } + + /** + * Sets the color associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param color a Color4b containing the new color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in the vertex + * format + */ + public void setColor(int index, Color4b color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray15")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).setColor(index, color); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param colors source array of 3*n or 4*n values containing n new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setColors(int index, float colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param colors source array of 3*n or 4*n values containing n new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setColors(int index, byte colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param colors source array of Color3f objects containing new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in vertex format + */ + public void setColors(int index, Color3f colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param colors source array of Color4f objects containing new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in vertex format + */ + public void setColors(int index, Color4f colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param colors source array of Color3b objects containing new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in vertex format + */ + public void setColors(int index, Color3b colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param colors source array of Color4b objects containing new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in vertex format + */ + public void setColors(int index, Color4b colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>colors</code> + * starting at index <code>start</code> for <code>length</code> colors. + * @param index starting destination vertex index in this geometry array + * @param colors source array of 3*n or 4*n values containing n new colors + * @param start starting source vertex index in <code>colors</code> array. + * @param length number of colors to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setColors(int index, float colors[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors, start, + length); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>colors</code> + * starting at index <code>start</code> for <code>length</code> colors. + * @param index starting destination vertex index in this geometry array + * @param colors source array of 3*n or 4*n values containing n new colors + * @param start starting source vertex index in <code>colors</code> array. + * @param length number of colors to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setColors(int index, byte colors[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors, start, + length); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>colors</code> + * starting at index <code>start</code> for <code>length</code> colors. + * @param index starting destination vertex index in this geometry array + * @param colors source array of Color3f objects containing new colors + * @param start starting source vertex index in <code>colors</code> array. + * @param length number of colors to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in vertex format + */ + public void setColors(int index, Color3f colors[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors, start, + length); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>colors</code> + * starting at index <code>start</code> for <code>length</code> colors. + * @param index starting destination vertex index in this geometry array + * @param colors source array of Color4f objects containing new colors + * @param start starting source vertex index in <code>colors</code> array. + * @param length number of colors to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in vertex format + */ + public void setColors(int index, Color4f colors[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors, start, + length); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>colors</code> + * starting at index <code>start</code> for <code>length</code> colors. + * @param index starting destination vertex index in this geometry array + * @param colors source array of Color3b objects containing new colors + * @param start starting source vertex index in <code>colors</code> array. + * @param length number of colors to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in vertex format + */ + public void setColors(int index, Color3b colors[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors, start, + length); + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>colors</code> + * starting at index <code>start</code> for <code>length</code> colors. + * @param index starting destination vertex index in this geometry array + * @param colors source array of Color4b objects containing new colors + * @param start starting source vertex index in <code>colors</code> array. + * @param length number of colors to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if COLOR bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in vertex format + */ + public void setColors(int index, Color4b colors[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray21")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).setColors(index, colors, start, + length); + } + + /** + * Sets the normal associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param normal source array of 3 values containing the new normal + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if NORMALS bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setNormal(int index, float normal[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray33")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).setNormal(index, normal); + } + + /** + * Sets the normal associated with the vertex at + * the specified index for this object. + * @param index destination vertex index in this geometry array + * @param normal the vector containing the new normal + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if NORMALS bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setNormal(int index, Vector3f normal) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray33")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).setNormal(index, normal); + } + + /** + * Sets the normals associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param normals source array of 3*n values containing n new normals + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if NORMALS bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setNormals(int index, float normals[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray35")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).setNormals(index, normals); + } + + /** + * Sets the normals associated with the vertices starting at + * the specified index for this object. The entire source array is + * copied to this geometry array. + * @param index starting destination vertex index in this geometry array + * @param normals source array of vectors containing new normals + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if NORMALS bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setNormals(int index, Vector3f normals[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray35")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).setNormals(index, normals); + } + + /** + * Sets the normals associated with the vertices starting at + * the specified index for this object using data in <code>normals</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index starting destination vertex index in this geometry array + * @param normals source array of 3*n values containing n new normals + * @param start starting source vertex index in <code>normals</code> array. + * @param length number of normals to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if NORMALS bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setNormals(int index, float normals[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray35")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).setNormals(index, normals, start, length); + } + + /** + * Sets the normals associated with the vertices starting at + * the specified index for this object using data in <code>normals</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index starting destination vertex index in this geometry array + * @param normals source array of vectors containing new normals + * @param start starting source vertex index in <code>normals</code> array. + * @param length number of normals to be copied. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception ArrayIndexOutOfBoundsException if NORMALS bit NOT set in + * constructor <code>vertexFormat</code> or array index for element is out of bounds. + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void setNormals(int index, Vector3f normals[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray35")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).setNormals(index, normals, start, length); + } + + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinate(int texCoordSet, ...)</code> + */ + public void setTextureCoordinate(int index, float texCoord[]) { + setTextureCoordinate(0, index, texCoord); + } + + /** + * Sets the texture coordinate associated with the vertex at the + * specified index in the specified texture coordinate set for + * this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index destination vertex index in this geometry array + * @param texCoord source array of 2, 3 or 4 values containing the new + * texture coordinate + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinate(int texCoordSet, + int index, float texCoord[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray39")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoord, 0, 1); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinate(int texCoordSet, TexCoord2f texCoord)</code> + */ + public void setTextureCoordinate(int index, Point2f texCoord) { + texCoord2fScratch.set(texCoord); + setTextureCoordinate(0, index, texCoord2fScratch); + } + + /** + * Sets the texture coordinate associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index destination vertex index in this geometry array + * @param texCoord the TexCoord2f containing the new texture coordinate + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_3 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinate(int texCoordSet, + int index, TexCoord2f texCoord) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray39")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_3 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray94")); + + texCoord2fArray[0] = texCoord; + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoord2fArray, 0, 1); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinate(int texCoordSet, TexCoord3f texCoord)</code> + */ + public void setTextureCoordinate(int index, Point3f texCoord) { + texCoord3fScratch.set(texCoord); + setTextureCoordinate(0, index, texCoord3fScratch); + } + + /** + * Sets the texture coordinate associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index destination vertex index in this geometry array + * @param texCoord the TexCoord3f containing the new texture coordinate + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinate(int texCoordSet, + int index, TexCoord3f texCoord) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray39")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray95")); + + texCoord3fArray[0] = texCoord; + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoord3fArray, 0, 1); + } + + /** + * Sets the texture coordinate associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index destination vertex index in this geometry array + * @param texCoord the TexCoord4f containing the new texture coordinate + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_3 is specified in vertex format + * + * @since Java 3D 1.3 + */ + public void setTextureCoordinate(int texCoordSet, + int index, TexCoord4f texCoord) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray39")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_3)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray109")); + + texCoord4fArray[0] = texCoord; + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoord4fArray, 0, 1); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinates(int texCoordSet, ...)</code> + */ + public void setTextureCoordinates(int index, float texCoords[]) { + setTextureCoordinates(0, index, texCoords); + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index in the specified texture coordinate set + * for this object. The entire source array is + * copied to this geometry array. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting destination vertex index in this geometry array + * @param texCoords source array of 2*n, 3*n or 4*n values containing n new + * texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinates(int texCoordSet, + int index, float texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & GeometryArray.TEXTURE_COORDINATE_2) != 0) + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, 0, texCoords.length / 2); + else if ((format & GeometryArray.TEXTURE_COORDINATE_3) != 0) + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, 0, texCoords.length / 3); + else + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, 0, texCoords.length / 4); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinates(int texCoordSet, TexCoord2f texCoords[])</code> + */ + public void setTextureCoordinates(int index, Point2f texCoords[]) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + 0, index, texCoords, 0, texCoords.length); + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index in the specified texture coordinate set + * for this object. The entire source array is + * copied to this geometry array. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting destination vertex index in this geometry array + * @param texCoords source array of TexCoord2f objects containing new + * texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_3 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinates(int texCoordSet, + int index, TexCoord2f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_3 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray94")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, 0, texCoords.length); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinates(int texCoordSet, TexCoord3f texCoords[])</code> + */ + public void setTextureCoordinates(int index, Point3f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + 0, index, texCoords, 0, texCoords.length); + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index in the specified texture coordinate set + * for this object. The entire source array is + * copied to this geometry array. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting destination vertex index in this geometry array + * @param texCoords source array of TexCoord3f objects containing new + * texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinates(int texCoordSet, + int index, TexCoord3f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_3)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray95")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, 0, texCoords.length); + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index in the specified texture coordinate set + * for this object. The entire source array is + * copied to this geometry array. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting destination vertex index in this geometry array + * @param texCoords source array of TexCoord4f objects containing new + * texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_3 is specified in vertex format + * + * @since Java 3D 1.3 + */ + public void setTextureCoordinates(int texCoordSet, + int index, TexCoord4f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_3)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray109")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, 0, texCoords.length); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinates(int texCoordSet, ...)</code> + */ + public void setTextureCoordinates(int index, float texCoords[], + int start, int length) { + setTextureCoordinates(0, index, texCoords, start, length); + } + + /** + * Sets the texture coordinates associated with the vertices + * starting at the specified index in the specified texture + * coordinate set for this object using data in + * <code>texCoords</code> starting at index <code>start</code> and + * ending at index <code>start+length</code>. + * + * @param index starting destination vertex index in this geometry array + * @param texCoords source array of 2*n , 3*n or 4*n values containing + * n new * texture coordinates + * @param start starting source vertex index in <code>texCoords</code> + * array. + * @param length number of texture Coordinates to be copied. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinates(int texCoordSet, + int index, float texCoords[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, start, length); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinates(int texCoordSet, TexCoord2f texCoords[], ...)</code> + */ + public void setTextureCoordinates(int index, Point2f texCoords[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + 0, index, texCoords, start, length); + } + + /** + * Sets the texture coordinates associated with the vertices + * starting at the specified index in the specified texture + * coordinate set for this object using data in + * <code>texCoords</code> starting at index <code>start</code> and + * ending at index <code>start+length</code>. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting destination vertex index in this geometry array + * @param texCoords source array of TexCoord2f objects containing new + * texture coordinates + * @param start starting source vertex index in <code>texCoords</code> + * array. + * @param length number of texture Coordinates to be copied. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_3 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinates(int texCoordSet, + int index, TexCoord2f texCoords[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_3 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray94")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, start, length); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinates(int texCoordSet, TexCoord3f texCoords[], ...)</code> + */ + public void setTextureCoordinates(int index, Point3f texCoords[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + 0, index, texCoords, start, length); + } + + /** + * Sets the texture coordinates associated with the vertices + * starting at the specified index in the specified texture + * coordinate set for this object. starting at index + * <code>start</code> and ending at index <code>start+length</code>. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting destination vertex index in this geometry array + * @param texCoords source array of TexCoord3f objects containing new + * texture coordinates + * @param start starting source vertex index in <code>texCoords</code> + * array. + * @param length number of texture Coordinates to be copied. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinates(int texCoordSet, + int index, TexCoord3f texCoords[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray95")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, start, length); + } + + /** + * Sets the texture coordinates associated with the vertices + * starting at the specified index in the specified texture + * coordinate set for this object. starting at index + * <code>start</code> and ending at index <code>start+length</code>. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting destination vertex index in this geometry array + * @param texCoords source array of TexCoord4f objects containing new + * texture coordinates + * @param start starting source vertex index in <code>texCoords</code> + * array. + * @param length number of texture Coordinates to be copied. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_3 is specified in vertex format + * + * @since Java 3D 1.3 + */ + public void setTextureCoordinates(int texCoordSet, + int index, TexCoord4f texCoords[], + int start, int length) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray42")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_3)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray109")); + + ((GeometryArrayRetained)this.retained).setTextureCoordinates( + texCoordSet, index, texCoords, start, length); + } + + /** + * Gets the coordinate associated with the vertex at + * the specified index for this object using data in <code>texCoords</code> + * @param index source vertex index in this geometry array + * @param coordinate destination array of 3 values that will receive the coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getCoordinate(int index, float coordinate[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray48")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).getCoordinate(index, coordinate); + } + + /** + * Gets the coordinate associated with the vertex at + * the specified index for this object. + * @param index source vertex index in this geometry array + * @param coordinate destination array of 3 values that will receive the coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getCoordinate(int index, double coordinate[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray48")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).getCoordinate(index, coordinate); + } + + /** + * Gets the coordinate associated with the vertex at + * the specified index for this object. + * @param index source vertex index in this geometry array + * @param coordinate a vector that will receive the coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getCoordinate(int index, Point3f coordinate) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray48")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).getCoordinate(index, coordinate); + } + + /** + * Gets the coordinate associated with the vertex at + * the specified index for this object. + * @param index source vertex index in this geometry array + * @param coordinate a vector that will receive the coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getCoordinate(int index, Point3d coordinate) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray48")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).getCoordinate(index, coordinate); + } + + /** + * Gets the coordinates associated with the vertices starting at + * the specified index for this object. The length of the destination + * array determines the number of vertices copied. + * A maximum of <code>vertexCount-index</code> coordinates + * are copied. If the destination array is larger than is needed + * to hold the coordinates, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the coordinates, only as + * many coordinates as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param coordinates destination array of 3*n values that will receive new coordinates + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getCoordinates(int index, float coordinates[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray52")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).getCoordinates(index, coordinates); + } + + /** + * Gets the coordinates associated with the vertices starting at + * the specified index for this object. The length of the destination + * array determines the number of vertices copied. + * A maximum of <code>vertexCount-index</code> coordinates + * are copied. If the destination array is larger than is needed + * to hold the coordinates, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the coordinates, only as + * many coordinates as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param coordinates destination array of 3*n values that will receive new coordinates + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getCoordinates(int index, double coordinates[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray52")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).getCoordinates(index, coordinates); + } + + /** + * Gets the coordinates associated with the vertices starting at + * the specified index for this object. The length of the destination + * array determines the number of vertices copied. + * A maximum of <code>vertexCount-index</code> coordinates + * are copied. If the destination array is larger than is needed + * to hold the coordinates, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the coordinates, only as + * many coordinates as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param coordinates destination array of points that will receive new coordinates + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getCoordinates(int index, Point3f coordinates[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray52")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).getCoordinates(index, coordinates); + } + + /** + * Gets the coordinates associated with the vertices starting at + * the specified index for this object. The length of the destination + * array determines the number of vertices copied. + * A maximum of <code>vertexCount-index</code> coordinates + * are copied. If the destination array is larger than is needed + * to hold the coordinates, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the coordinates, only as + * many coordinates as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param coordinates destination array of points that will receive new coordinates + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getCoordinates(int index, Point3d coordinates[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray52")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + ((GeometryArrayRetained)this.retained).getCoordinates(index, coordinates); + } + + /** + * Gets the color associated with the vertex at + * the specified index for this object. The color is copied into the + * specified array. The array must be large enough to hold all + * of the colors. + * @param index source vertex index in this geometry array + * @param color destination array of 3 or 4 values that will receive the color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getColor(int index, float color[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray56")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).getColor(index, color); + } + + /** + * Gets the color associated with the vertex at + * the specified index for this object. The color is copied into the + * specified array. The array must be large enough to hold all of + * the colors. + * @param index source vertex index in this geometry array + * @param color destination array of 3 or 4 values that will receive the color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getColor(int index, byte color[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray56")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).getColor(index, color); + } + + /** + * Gets the color associated with the vertex at + * the specified index for this object. + * @param index source vertex index in this geometry array + * @param color a vector that will receive the color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in the vertex + * format + */ + public void getColor(int index, Color3f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray56")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).getColor(index, color); + } + + /** + * Gets the color associated with the vertex at + * the specified index for this object. + * @param index source vertex index in this geometry array + * @param color a vector that will receive the color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in the vertex + * format + */ + public void getColor(int index, Color4f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray56")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).getColor(index, color); + } + + /** + * Gets the color associated with the vertex at + * the specified index for this object. + * @param index source vertex index in this geometry array + * @param color a vector that will receive the color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in the vertex + * format + */ + public void getColor(int index, Color3b color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray56")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).getColor(index, color); + } + + /** + * Gets the color associated with the vertex at + * the specified index for this object. + * @param index source vertex index in this geometry array + * @param color a vector that will receive the color + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in the vertex + * format + */ + public void getColor(int index, Color4b color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray56")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).getColor(index, color); + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index for this object. The color is copied into the + * specified array. The length of the destination + * array determines the number of colors copied. + * A maximum of <code>vertexCount-index</code> colors + * are copied. If the destination array is larger than is needed + * to hold the colors, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the colors, only as + * many colors as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param colors destination array of 3*n or 4*n values that will + * receive n new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getColors(int index, float colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray62")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).getColors(index, colors); + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index for this object. The color is copied into the + * specified array. The length of the destination + * array determines the number of colors copied. + * A maximum of <code>vertexCount-index</code> colors + * are copied. If the destination array is larger than is needed + * to hold the colors, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the colors, only as + * many colors as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param colors destination array of 3*n or 4*n values that will + * receive new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getColors(int index, byte colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray62")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + ((GeometryArrayRetained)this.retained).getColors(index, colors); + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index for this object. The color is copied into the + * specified array. The length of the destination + * array determines the number of colors copied. + * A maximum of <code>vertexCount-index</code> colors + * are copied. If the destination array is larger than is needed + * to hold the colors, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the colors, only as + * many colors as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param colors destination array of Color3f objects that will receive new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in the vertex + * format + */ + public void getColors(int index, Color3f colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray62")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).getColors(index, colors); + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index for this object. The color is copied into the + * specified array. The length of the destination + * array determines the number of colors copied. + * A maximum of <code>vertexCount-index</code> colors + * are copied. If the destination array is larger than is needed + * to hold the colors, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the colors, only as + * many colors as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param colors destination array of Color4f objects that will receive new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in the vertex + * format + */ + public void getColors(int index, Color4f colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray62")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).getColors(index, colors); + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index for this object. The color is copied into the + * specified array. The length of the destination + * array determines the number of colors copied. + * A maximum of <code>vertexCount-index</code> colors + * are copied. If the destination array is larger than is needed + * to hold the colors, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the colors, only as + * many colors as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param colors destination array of Color3b objects that will receive new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_4 is specified in the vertex + * format + */ + public void getColors(int index, Color3b colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray62")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).getColors(index, colors); + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index for this object. The color is copied into the + * specified array. The length of the destination + * array determines the number of colors copied. + * A maximum of <code>vertexCount-index</code> colors + * are copied. If the destination array is larger than is needed + * to hold the colors, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the colors, only as + * many colors as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param colors destination array of Color4b objects that will receive new colors + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * @exception IllegalStateException if COLOR_3 is specified in the vertex + * format + */ + public void getColors(int index, Color4b colors[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray62")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & COLOR ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray76")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).getColors(index, colors); + } + + /** + * Gets the normal associated with the vertex at + * the specified index for this object. The normal is copied into + * the specified array. + * @param index source vertex index in this geometry array + * @param normal destination array of 3 values that will receive the normal + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getNormal(int index, float normal[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray68")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).getNormal(index, normal); + } + + /** + * Gets the normal associated with the vertex at + * the specified index for this object. + * @param index source vertex index in this geometry array + * @param normal the vector that will receive the normal + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getNormal(int index, Vector3f normal) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray68")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).getNormal(index, normal); + } + + /** + * Gets the normals associated with the vertices starting at + * the specified index for this object. The length of the destination + * array determines the number of normals copied. + * A maximum of <code>vertexCount-index</code> normals + * are copied. If the destination array is larger than is needed + * to hold the normals, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the normals, only as + * many normals as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param normals destination array of 3*n values that will receive the normal + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getNormals(int index, float normals[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray70")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).getNormals(index, normals); + } + + /** + * Gets the normals associated with the vertices starting at + * the specified index for this object. The length of the destination + * array determines the number of normals copied. + * A maximum of <code>vertexCount-index</code> normals + * are copied. If the destination array is larger than is needed + * to hold the normals, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the normals, only as + * many normals as the array will hold are copied. + * + * @param index starting source vertex index in this geometry array + * @param normals destination array of vectors that will receive the normals + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + */ + public void getNormals(int index, Vector3f normals[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray70")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & NORMALS ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray77")); + + ((GeometryArrayRetained)this.retained).getNormals(index, normals); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getTextureCoordinate(int texCoordSet, ...)</code> + */ + public void getTextureCoordinate(int index, float texCoord[]) { + getTextureCoordinate(0, index, texCoord); + } + + /** + * Gets the texture coordinate associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index source vertex index in this geometry array + * @param texCoord array of 2, 3 or 4 values that will receive the + * texture coordinate + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @since Java 3D 1.2 + */ + public void getTextureCoordinate(int texCoordSet, + int index, float texCoord[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray72")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + ((GeometryArrayRetained)this.retained).getTextureCoordinate( + texCoordSet, index, texCoord); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getTextureCoordinate(int texCoordSet, TexCoord2f texCoord)</code> + */ + public void getTextureCoordinate(int index, Point2f texCoord) { + getTextureCoordinate(0, index, texCoord2fArray[0]); + texCoord.set(texCoord2fArray[0]); + } + + /** + * Gets the texture coordinate associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index source vertex index in this geometry array + * @param texCoord the vector that will receive the texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_3 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void getTextureCoordinate(int texCoordSet, + int index, TexCoord2f texCoord) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray72")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_3 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray94")); + + ((GeometryArrayRetained)this.retained).getTextureCoordinate( + texCoordSet, index, texCoord); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getTextureCoordinate(int texCoordSet, TexCoord3f texCoord)</code> + */ + public void getTextureCoordinate(int index, Point3f texCoord) { + getTextureCoordinate(0, index, texCoord3fArray[0]); + texCoord.set(texCoord3fArray[0]); + } + + /** + * Gets the texture coordinate associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index source vertex index in this geometry array + * @param texCoord the vector that will receive the texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void getTextureCoordinate(int texCoordSet, + int index, TexCoord3f texCoord) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray72")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray95")); + ((GeometryArrayRetained)this.retained).getTextureCoordinate( + texCoordSet, index, texCoord); + } + + /** + * Gets the texture coordinate associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index source vertex index in this geometry array + * @param texCoord the vector that will receive the texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_3 is specified in vertex format + * + * @since Java 3D 1.3 + */ + public void getTextureCoordinate(int texCoordSet, + int index, TexCoord4f texCoord) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray72")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_3)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray109")); + ((GeometryArrayRetained)this.retained).getTextureCoordinate( + texCoordSet, index, texCoord); + } + + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getTextureCoordinates(int texCoordSet, ...)</code> + */ + public void getTextureCoordinates(int index, float texCoords[]) { + getTextureCoordinates(0, index, texCoords); + } + + /** + * Gets the texture coordinates associated with the vertices starting at + * the specified index in the specified texture coordinate set + * for this object. The length of the destination + * array determines the number of texture coordinates copied. + * A maximum of <code>vertexCount-index</code> texture coordinates + * are copied. If the destination array is larger than is needed + * to hold the texture coordinates, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the texture coordinates, only as + * many texture coordinates as the array will hold are copied. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting source vertex index in this geometry array + * @param texCoords destination array of 2*n , 3*n or 4*n values that + * will receive n new texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @since Java 3D 1.2 + */ + public void getTextureCoordinates(int texCoordSet, + int index, float texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray75")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + ((GeometryArrayRetained)this.retained).getTextureCoordinates( + texCoordSet, index, texCoords); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getTextureCoordinates(int texCoordSet, TexCoord2f texCoords[])</code> + */ + public void getTextureCoordinates(int index, Point2f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray75")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + ((GeometryArrayRetained)this.retained).getTextureCoordinates( + 0, index, texCoords); + } + + /** + * Gets the texture coordinates associated with the vertices starting at + * the specified index in the specified texture coordinate set + * for this object. The length of the destination + * array determines the number of texture coordinates copied. + * A maximum of <code>vertexCount-index</code> texture coordinates + * are copied. If the destination array is larger than is needed + * to hold the texture coordinates, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the texture coordinates, only as + * many texture coordinates as the array will hold are copied. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting source vertex index in this geometry array + * @param texCoords destination array of TexCoord2f objects that will + * receive the texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_3 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void getTextureCoordinates(int texCoordSet, + int index, TexCoord2f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray75")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_3 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray94")); + ((GeometryArrayRetained)this.retained).getTextureCoordinates( + texCoordSet, index, texCoords); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getTextureCoordinates(int texCoordSet, TexCoord3f texCoords[])</code> + */ + public void getTextureCoordinates(int index, Point3f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray75")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + ((GeometryArrayRetained)this.retained).getTextureCoordinates( + 0, index, texCoords); + } + + /** + * Gets the texture coordinates associated with the vertices starting at + * the specified index in the specified texture coordinate set + * for this object. The length of the destination + * array determines the number of texture coordinates copied. + * A maximum of <code>vertexCount-index</code> texture coordinates + * are copied. If the destination array is larger than is needed + * to hold the texture coordinates, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the texture coordinates, only as + * many texture coordinates as the array will hold are copied. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting source vertex index in this geometry array + * @param texCoords destination array of TexCoord3f objects that will + * receive the texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_4 is specified in vertex format + * + * @since Java 3D 1.2 + */ + public void getTextureCoordinates(int texCoordSet, + int index, TexCoord3f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray75")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray95")); + ((GeometryArrayRetained)this.retained).getTextureCoordinates( + texCoordSet, index, texCoords); + } + + + /** + * Gets the texture coordinates associated with the vertices starting at + * the specified index in the specified texture coordinate set + * for this object. The length of the destination + * array determines the number of texture coordinates copied. + * A maximum of <code>vertexCount-index</code> texture coordinates + * are copied. If the destination array is larger than is needed + * to hold the texture coordinates, the excess locations in the + * array are not modified. If the destination array is smaller + * than is needed to hold the texture coordinates, only as + * many texture coordinates as the array will hold are copied. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index starting source vertex index in this geometry array + * @param texCoords destination array of TexCoord4f objects that will + * receive the texture coordinates + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception IllegalStateException if the data mode for this geometry + * array object is <code>BY_REFERENCE</code>. + * + * @exception IllegalStateException if TEXTURE_COORDINATE_2 or + * TEXTURE_COORDINATE_3 is specified in vertex format + * + * @since Java 3D 1.3 + */ + public void getTextureCoordinates(int texCoordSet, + int index, TexCoord4f texCoords[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TEXCOORD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray75")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((format & TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + if (((((GeometryArrayRetained)this.retained).vertexFormat) & + (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_3)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray109")); + + ((GeometryArrayRetained)this.retained).getTextureCoordinates( + texCoordSet, index, texCoords); + } + + + //------------------------------------------------------------------ + // By-reference methods + //------------------------------------------------------------------ + + /** + * Sets the initial coordinate index for this GeometryArray object. + * This index specifies the first coordinate within the array of + * coordinates referenced by this geometry + * array that is actually used in rendering or other operations + * such as picking and collision. This attribute is initialized + * to 0. + * This attribute is only used when the data mode for this + * geometry array object is <code>BY_REFERENCE</code> + * and is <i>not</i> </code>INTERLEAVED</code>. + * + * @param initialCoordIndex the new initial coordinate index. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * <p> + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code> or if the data mode + * is <code>INTERLEAVED</code>. + * <p> + * @exception IllegalArgumentException if either of the following are + * true: + * <ul> + * <code>initialCoordIndex < 0</code> or<br> + * <code>initialCoordIndex + validVertexCount > vertexCount</code><br> + * </ul> + * <p> + * @exception ArrayIndexOutOfBoundsException if + * the CoordRef array is non-null and: + * <ul> + * <code>CoordRef.length</code> < <i>num_words</i> * + * (<code>initialCoordIndex + validVertexCount</code>)<br> + * </ul> + * where <i>num_words</i> depends on which variant of + * <code>setCoordRef</code> is used. + * + * @since Java 3D 1.2 + */ + public void setInitialCoordIndex(int initialCoordIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray90")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if (initialCoordIndex < 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray97")); + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setInitialCoordIndex(initialCoordIndex); + // NOTE: the check for initialCoordIndex + validVertexCount > + // vertexCount needs to be done in the retained method + } + + + /** + * Gets the initial coordinate index for this GeometryArray object. + * This attribute is only used when the data mode for this + * geometry array object is <code>BY_REFERENCE</code> + * and is <i>not</i> </code>INTERLEAVED</code>. + * @return the current initial coordinate index for this + * GeometryArray object. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getInitialCoordIndex() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray91")); + + return ((GeometryArrayRetained)this.retained).getInitialCoordIndex(); + } + + + /** + * Sets the initial color index for this GeometryArray object. + * This index specifies the first color within the array of + * colors referenced by this geometry + * array that is actually used in rendering or other operations + * such as picking and collision. This attribute is initialized + * to 0. + * This attribute is only used when the data mode for this + * geometry array object is <code>BY_REFERENCE</code> + * and is <i>not</i> </code>INTERLEAVED</code>. + * + * @param initialColorIndex the new initial color index. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * <p> + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code> or if the data mode + * is <code>INTERLEAVED</code>. + * <p> + * @exception IllegalArgumentException if either of the following are + * true: + * <ul> + * <code>initialColorIndex < 0</code> or<br> + * <code>initialColorIndex + validVertexCount > vertexCount</code><br> + * </ul> + * <p> + * @exception ArrayIndexOutOfBoundsException if + * the ColorRef array is non-null and: + * <ul> + * <code>ColorRef.length</code> < <i>num_words</i> * + * (<code>initialColorIndex + validVertexCount</code>)<br> + * </ul> + * where <i>num_words</i> depends on which variant of + * <code>setColorRef</code> is used. + * + * @since Java 3D 1.2 + */ + public void setInitialColorIndex(int initialColorIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray90")); + + if (initialColorIndex < 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray97")); + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setInitialColorIndex(initialColorIndex); + // NOTE: the check for initialColorIndex + validVertexCount > + // vertexCount needs to be done in the retained method + } + + + /** + * Gets the initial color index for this GeometryArray object. + * This attribute is only used when the data mode for this + * geometry array object is <code>BY_REFERENCE</code> + * and is <i>not</i> </code>INTERLEAVED</code>. + * @return the current initial color index for this + * GeometryArray object. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getInitialColorIndex() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray91")); + + return ((GeometryArrayRetained)this.retained).getInitialColorIndex(); + } + + + /** + * Sets the initial normal index for this GeometryArray object. + * This index specifies the first normal within the array of + * normals referenced by this geometry + * array that is actually used in rendering or other operations + * such as picking and collision. This attribute is initialized + * to 0. + * This attribute is only used when the data mode for this + * geometry array object is <code>BY_REFERENCE</code> + * and is <i>not</i> </code>INTERLEAVED</code>. + * + * @param initialNormalIndex the new initial normal index. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * <p> + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code> or if the data mode + * is <code>INTERLEAVED</code>. + * <p> + * @exception IllegalArgumentException if either of the following are + * true: + * <ul> + * <code>initialNormalIndex < 0</code> or<br> + * <code>initialNormalIndex + validVertexCount > vertexCount</code><br> + * </ul> + * <p> + * @exception ArrayIndexOutOfBoundsException if normals + * the NormalRef array is non-null and: + * <ul> + * <code>NormalRef.length</code> < <i>num_words</i> * + * (<code>initialNormalIndex + validVertexCount</code>)<br> + * </ul> + * where <i>num_words</i> depends on which variant of + * <code>setNormalRef</code> is used. + * + * @since Java 3D 1.2 + */ + public void setInitialNormalIndex(int initialNormalIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray90")); + + if (initialNormalIndex < 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray97")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setInitialNormalIndex(initialNormalIndex); + // NOTE: the check for initialNormalIndex + validVertexCount > + // vertexCount needs to be done in the retained method + } + + + /** + * Gets the initial normal index for this GeometryArray object. + * This attribute is only used when the data mode for this + * geometry array object is <code>BY_REFERENCE</code> + * and is <i>not</i> </code>INTERLEAVED</code>. + * @return the current initial normal index for this + * GeometryArray object. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getInitialNormalIndex() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray91")); + + return ((GeometryArrayRetained)this.retained).getInitialNormalIndex(); + } + + + /** + * Sets the initial texture coordinate index for the specified + * texture coordinate set for this GeometryArray object. This + * index specifies the first texture coordinate within the array + * of texture coordinates referenced by this geometry array that + * is actually used in rendering or other operations such as + * picking and collision. This attribute is initialized to 0. + * This attribute is only used when the data mode for this + * geometry array object is <code>BY_REFERENCE</code> + * and is <i>not</i> </code>INTERLEAVED</code>. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param initialTexCoordIndex the new initial texture coordinate index. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * <p> + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code> or if the data mode + * is <code>INTERLEAVED</code>. + * <p> + * @exception IllegalArgumentException if either of the following are + * true: + * <ul> + * <code>initialTexCoordIndex < 0</code> or<br> + * <code>initialTexCoordIndex + validVertexCount > vertexCount</code><br> + * </ul> + * <p> + * @exception ArrayIndexOutOfBoundsException if + * the TexCoordRef array is non-null and: + * <ul> + * <code>TexCoordRef.length</code> < <i>num_words</i> * + * (<code>initialTexCoordIndex + validVertexCount</code>)<br> + * </ul> + * where <i>num_words</i> depends on which variant of + * <code>setTexCoordRef</code> is used. + * <p> + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if texCoordSet is out of range. + * + * @since Java 3D 1.2 + */ + public void setInitialTexCoordIndex(int texCoordSet, + int initialTexCoordIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray90")); + + if (initialTexCoordIndex < 0) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray97")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setInitialTexCoordIndex( + texCoordSet, initialTexCoordIndex); + + // NOTE: the check for initialTexCoordIndex + validVertexCount > + // vertexCount needs to be done in the retained method + } + + + /** + * Gets the initial texture coordinate index for the specified + * texture coordinate set for this GeometryArray object. + * This attribute is only used when the data mode for this + * geometry array object is <code>BY_REFERENCE</code> + * and is <i>not</i> </code>INTERLEAVED</code>. + * + * @param texCoordSet texture coordinate set in this geometry array + * + * @return the current initial texture coordinate index for the specified + * texture coordinate set + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if texCoordSet is out of range. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getInitialTexCoordIndex(int texCoordSet) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray91")); + + return ((GeometryArrayRetained)this.retained).getInitialTexCoordIndex( + texCoordSet); + } + + + /** + * Sets the coordinate buffer reference to the specified + * buffer object. The buffer contains either a java.nio.FloatBuffer + * or java.nio.DoubleBuffer object containing single or double + * precision floating-point <i>x</i>, <i>y</i>, + * and <i>z</i> values for each vertex (for a total of 3*<i>n</i> + * values, where <i>n</i> is the number of vertices). + * If the coordinate buffer + * reference is null, the entire geometry array object is + * treated as if it were null--any Shape3D or Morph node that uses + * this geometry array will not be drawn. + * + * @param coords a J3DBuffer object to which a reference will be set. + * The buffer contains an NIO buffer of 3*<i>n</i> float or + * double values. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is not <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @exception IllegalArgumentException if the java.nio.Buffer + * contained in the specified J3DBuffer is not a + * java.nio.FloatBuffer or a java.nio.DoubleBuffer object. + * + * @exception ArrayIndexOutOfBoundsException if + * <code>coords.getBuffer().limit() < + * 3 * (initialCoordIndex + validVertexCount)</code>. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the coordinate index array is greater than or equal to the + * number of vertices defined by the coords object, + * <code>coords.getBuffer().limit() / 3</code>. + * + * @since Java 3D 1.3 + */ + public void setCoordRefBuffer(J3DBuffer coords) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setCoordRefBuffer(coords); + } + + + /** + * Gets the coordinate array buffer reference. + * @return the current coordinate array buffer reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is not <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @since Java 3D 1.3 + */ + public J3DBuffer getCoordRefBuffer() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getCoordRefBuffer(); + } + + + /** + * Sets the float coordinate array reference to the specified + * array. The array contains floating-point <i>x</i>, <i>y</i>, + * and <i>z</i> values for each vertex (for a total of 3*<i>n</i> + * values, where <i>n</i> is the number of vertices). Only one of + * <code>coordRefFloat</code>, <code>coordRefDouble</code>, + * <code>coordRef3f</code>, or <code>coordRef3d</code> may be + * non-null (or they may all be null). An attempt to set more + * than one of these attributes to a non-null reference will + * result in an exception being thrown. If all coordinate array + * references are null, the entire geometry array object is + * treated as if it were null--any Shape3D or Morph node that uses + * this geometry array will not be drawn. + * + * @param coords an array of 3*<i>n</i> values to which a + * reference will be set. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * @exception IllegalArgumentException if the specified array is + * non-null and any other coordinate reference is also non-null. + * @exception ArrayIndexOutOfBoundsException if + * <code>coords.length < 3 * (initialCoordIndex + validVertexCount)</code>. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the coordinate index array is greater than or equal to the + * number of vertices defined by the coords array, + * <code>coords.length / 3</code>. + * + * @since Java 3D 1.2 + */ + public void setCoordRefFloat(float[] coords) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setCoordRefFloat(coords); + + } + + + /** + * Gets the float coordinate array reference. + * @return the current float coordinate array reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @since Java 3D 1.2 + */ + public float[] getCoordRefFloat() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getCoordRefFloat(); + } + + + /** + * Sets the double coordinate array reference to the specified + * array. The array contains double-precision + * floating-point <i>x</i>, <i>y</i>, + * and <i>z</i> values for each vertex (for a total of 3*<i>n</i> + * values, where <i>n</i> is the number of vertices). Only one of + * <code>coordRefFloat</code>, <code>coordRefDouble</code>, + * <code>coordRef3f</code>, or <code>coordRef3d</code> may be + * non-null (or they may all be null). An attempt to set more + * than one of these attributes to a non-null reference will + * result in an exception being thrown. If all coordinate array + * references are null, the entire geometry array object is + * treated as if it were null--any Shape3D or Morph node that uses + * this geometry array will not be drawn. + * + * @param coords an array of 3*<i>n</i> values to which a + * reference will be set. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * @exception IllegalArgumentException if the specified array is + * non-null and any other coordinate reference is also non-null. + * @exception ArrayIndexOutOfBoundsException if + * <code>coords.length < 3 * (initialCoordIndex + validVertexCount)</code>. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the coordinate index array is greater than or equal to the + * number of vertices defined by the coords array, + * <code>coords.length / 3</code>. + * + * @since Java 3D 1.2 + */ + public void setCoordRefDouble(double[] coords) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setCoordRefDouble(coords); + + } + + + /** + * Gets the double coordinate array reference. + * @return the current double coordinate array reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @since Java 3D 1.2 + */ + public double[] getCoordRefDouble() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getCoordRefDouble(); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Point3f arrays + * + * @since Java 3D 1.2 + */ + public void setCoordRef3f(Point3f[] coords) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setCoordRef3f(coords); + + + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Point3f arrays + * + * @since Java 3D 1.2 + */ + public Point3f[] getCoordRef3f() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getCoordRef3f(); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Point3d arrays + * + * @since Java 3D 1.2 + */ + public void setCoordRef3d(Point3d[] coords) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setCoordRef3d(coords); + + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Point3d arrays + * + * @since Java 3D 1.2 + */ + public Point3d[] getCoordRef3d() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getCoordRef3d(); + } + + + /** + * Sets the color buffer reference to the specified + * buffer object. The buffer contains either a java.nio.FloatBuffer + * or java.nio.ByteBuffer object containing floating-point + * or byte <i>red</i>, <i>green</i>, + * <i>blue</i>, and, optionally, <i>alpha</i> values for each + * vertex (for a total of 3*<i>n</i> or 4*<i>n</i> values, where + * <i>n</i> is the number of vertices). + * If the color buffer reference is null and colors are enabled + * (that is, the vertexFormat includes either <code>COLOR_3</code> or + * <code>COLOR_4</code>), the entire geometry array object is + * treated as if it were null--any Shape3D or Morph node that uses + * this geometry array will not be drawn. + * + * @param colors a J3DBuffer object to which a reference will be set. + * The buffer contains an NIO buffer of 3*<i>n</i> or 4*<i>n</i> + * float or byte values. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is not <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @exception IllegalArgumentException if the java.nio.Buffer + * contained in the specified J3DBuffer is not a + * java.nio.FloatBuffer or a java.nio.ByteBuffer object. + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>COLOR</code> bits are set in the + * <code>vertexFormat</code>, or if + * <code>colors.getBuffer().limit() < </code> <i>num_words</i> <code> * + * (initialColorIndex + validVertexCount)</code>, + * where <i>num_words</i> is 3 or 4 depending on the vertex color format. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the color index array is greater than or equal to the + * number of vertices defined by the colors object, + * <code>colors.getBuffer().limit() / </code> <i>num_words</i>. + * + * @since Java 3D 1.3 + */ + public void setColorRefBuffer(J3DBuffer colors) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setColorRefBuffer(colors); + + } + + + /** + * Gets the color array buffer reference. + * @return the current color array buffer reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is not <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @since Java 3D 1.3 + */ + public J3DBuffer getColorRefBuffer() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getColorRefBuffer(); + } + + + /** + * Sets the float color array reference to the specified array. + * The array contains floating-point <i>red</i>, <i>green</i>, + * <i>blue</i>, and, optionally, <i>alpha</i> values for each + * vertex (for a total of 3*<i>n</i> or 4*<i>n</i> values, where + * <i>n</i> is the number of vertices). Only one of + * <code>colorRefFloat</code>, <code>colorRefByte</code>, + * <code>colorRef3f</code>, <code>colorRef4f</code>, + * <code>colorRef3b</code>, or <code>colorRef4b</code> may be + * non-null (or they may all be null). An attempt to set more + * than one of these attributes to a non-null reference will + * result in an exception being thrown. If all color array + * references are null and colors are enabled (that is, the + * vertexFormat includes either <code>COLOR_3</code> or + * <code>COLOR_4</code>), the entire geometry array object is + * treated as if it were null--any Shape3D or Morph node that uses + * this geometry array will not be drawn. + * + * @param colors an array of 3*<i>n</i> or 4*<i>n</i> values to which a + * reference will be set. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * @exception IllegalArgumentException if the specified array is + * non-null and any other color reference is also non-null. + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>COLOR</code> bits are set in the + * <code>vertexFormat</code>, or if + * <code>colors.length < </code> <i>num_words</i> <code> * + * (initialColorIndex + validVertexCount)</code>, + * where <i>num_words</i> is 3 or 4 depending on the vertex color format. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the color index array is greater than or equal to the + * number of vertices defined by the colors array, + * <code>colors.length / </code> <i>num_words</i>. + * + * @since Java 3D 1.2 + */ + public void setColorRefFloat(float[] colors) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setColorRefFloat(colors); + + } + + + /** + * Gets the float color array reference. + * @return the current float color array reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @since Java 3D 1.2 + */ + public float[] getColorRefFloat() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getColorRefFloat(); + } + + + /** + * Sets the byte color array reference to the specified array. + * The array contains <i>red</i>, <i>green</i>, + * <i>blue</i>, and, optionally, <i>alpha</i> values for each + * vertex (for a total of 3*<i>n</i> or 4*<i>n</i> values, where + * <i>n</i> is the number of vertices). Only one of + * <code>colorRefFloat</code>, <code>colorRefByte</code>, + * <code>colorRef3f</code>, <code>colorRef4f</code>, + * <code>colorRef3b</code>, or <code>colorRef4b</code> may be + * non-null (or they may all be null). An attempt to set more + * than one of these attributes to a non-null reference will + * result in an exception being thrown. If all color array + * references are null and colors are enabled (that is, the + * vertexFormat includes either <code>COLOR_3</code> or + * <code>COLOR_4</code>), the entire geometry array object is + * treated as if it were null--any Shape3D or Morph node that uses + * this geometry array will not be drawn. + * + * @param colors an array of 3*<i>n</i> or 4*<i>n</i> values to which a + * reference will be set. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * @exception IllegalArgumentException if the specified array is + * non-null and any other color reference is also non-null. + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>COLOR</code> bits are set in the + * <code>vertexFormat</code>, or if + * <code>colors.length < </code> <i>num_words</i> <code> * + * (initialColorIndex + validVertexCount)</code>, + * where <i>num_words</i> is 3 or 4 depending on the vertex color format. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the color index array is greater than or equal to the + * number of vertices defined by the colors array, + * <code>colors.length / </code> <i>num_words</i>. + * + * @since Java 3D 1.2 + */ + public void setColorRefByte(byte[] colors) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setColorRefByte(colors); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * Gets the byte color array reference. + * @return the current byte color array reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @since Java 3D 1.2 + */ + public byte[] getColorRefByte() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getColorRefByte(); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Color3f arrays + * + * @since Java 3D 1.2 + */ + public void setColorRef3f(Color3f[] colors) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).setColorRef3f(colors); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Color3f arrays + * + * @since Java 3D 1.2 + */ + public Color3f[] getColorRef3f() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getColorRef3f(); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Color4f arrays + * + * @since Java 3D 1.2 + */ + public void setColorRef4f(Color4f[] colors) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).setColorRef4f(colors); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Color4f arrays + * + * @since Java 3D 1.2 + */ + public Color4f[] getColorRef4f() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getColorRef4f(); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Color3b arrays + * + * @since Java 3D 1.2 + */ + public void setColorRef3b(Color3b[] colors) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + if ((format & WITH_ALPHA) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + + ((GeometryArrayRetained)this.retained).setColorRef3b(colors); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Color3b arrays + * + * @since Java 3D 1.2 + */ + public Color3b[] getColorRef3b() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getColorRef3b(); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Color4b arrays + * + * @since Java 3D 1.2 + */ + public void setColorRef4b(Color4b[] colors) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + if ((format & WITH_ALPHA) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + + ((GeometryArrayRetained)this.retained).setColorRef4b(colors); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Color4b arrays + * + * @since Java 3D 1.2 + */ + public Color4b[] getColorRef4b() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getColorRef4b(); + } + + + /** + * Sets the normal buffer reference to the specified + * buffer object. The buffer contains a java.nio.FloatBuffer + * object containing <i>nx</i>, <i>ny</i>, + * and <i>nz</i> values for each vertex (for a total of 3*<i>n</i> + * values, where <i>n</i> is the number of vertices). + * If the normal buffer reference is null and normals are enabled + * (that is, the vertexFormat includes <code>NORMAL</code>), the + * entire geometry array object is treated as if it were null--any + * Shape3D or Morph node that uses this geometry array will not be + * drawn. + * + * @param normals a J3DBuffer object to which a reference will be set. + * The buffer contains an NIO buffer of 3*<i>n</i> float values. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is not <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @exception IllegalArgumentException if the java.nio.Buffer + * contained in the specified J3DBuffer is not a + * java.nio.FloatBuffer object. + * + * @exception ArrayIndexOutOfBoundsException if + * <code>NORMALS</code> bit is not set in the + * <code>vertexFormat</code>, or if + * <code>normals.getBuffer().limit() < + * 3 * (initialNormalIndex + validVertexCount)</code>. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the normal index array is greater than or equal to the + * number of vertices defined by the normals object, + * <code>normals.getBuffer().limit() / 3</code>. + * + * @since Java 3D 1.3 + */ + public void setNormalRefBuffer(J3DBuffer normals) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setNormalRefBuffer(normals); + } + + + /** + * Gets the normal array buffer reference. + * @return the current normal array buffer reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is not <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @since Java 3D 1.3 + */ + public J3DBuffer getNormalRefBuffer() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getNormalRefBuffer(); + } + + + /** + * Sets the float normal array reference to the specified + * array. The array contains floating-point <i>nx</i>, <i>ny</i>, + * and <i>nz</i> values for each vertex (for a total of 3*<i>n</i> + * values, where <i>n</i> is the number of vertices). Only one of + * <code>normalRefFloat</code> or <code>normalRef3f</code> may be + * non-null (or they may all be null). An attempt to set more + * than one of these attributes to a non-null reference will + * result in an exception being thrown. If all normal array + * references are null and normals are enabled (that is, the + * vertexFormat includes + * <code>NORMAL</code>), the entire geometry array object is + * treated as if it were null--any Shape3D or Morph node that uses + * this geometry array will not be drawn. + * + * @param normals an array of 3*<i>n</i> values to which a + * reference will be set. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * @exception IllegalArgumentException if the specified array is + * non-null and any other normal reference is also non-null. + * @exception ArrayIndexOutOfBoundsException if + * <code>NORMALS</code> bit is not set in the + * <code>vertexFormat</code>, or if + * <code>normals.length < 3 * (initialNormalIndex + validVertexCount)</code>. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the normal index array is greater than or equal to the + * number of vertices defined by the normals array, + * <code>normals.length / 3</code>. + * + * @since Java 3D 1.2 + */ + public void setNormalRefFloat(float[] normals) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setNormalRefFloat(normals); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * Gets the float normal array reference. + * @return the current float normal array reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @since Java 3D 1.2 + */ + public float[] getNormalRefFloat() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getNormalRefFloat(); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Vector3f arrays + * + * @since Java 3D 1.2 + */ + public void setNormalRef3f(Vector3f[] normals) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setNormalRef3f(normals); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for Vector3f arrays + * + * @since Java 3D 1.2 + */ + public Vector3f[] getNormalRef3f() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getNormalRef3f(); + } + + + /** + * Sets the texture coordinate array reference for the specified + * texture coordinate set to the + * specified buffer object. The buffer contains a java.nio.FloatBuffer + * object containing <i>s</i>, + * <i>t</i>, and, optionally, <i>r</i> and <i>q</i> values for each + * vertex (for + * a total of 2*<i>n</i> , 3*<i>n</i> or 4*<i>n</i> values, + * where <i>n</i> is + * the number of vertices). + * If the texCoord buffer reference is null and texture + * coordinates are enabled (that is, the vertexFormat includes + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code>, or + * <code>TEXTURE_COORDINATE_4</code>), the entire geometry + * array object is treated as if it were null--any Shape3D or + * Morph node that uses this geometry array will not be drawn. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param texCoords a J3DBuffer object to which a reference will be set. + * The buffer contains an NIO buffer of 2*<i>n</i>, 3*<i>n</i> or + * 4*<i>n</i> float values. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is not <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @exception IllegalArgumentException if the java.nio.Buffer + * contained in the specified J3DBuffer is not a + * java.nio.FloatBuffer object. + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code>, or if texCoordSet is out of range, + * or if + * <code>texCoords.getBuffer().limit() < </code> <i>num_words</i> + * <code> * (initialTexCoordIndex + validVertexCount)</code>, + * where <i>num_words</i> is 2, 3, or 4 depending on the vertex + * texture coordinate format. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the texture coordinate index array is greater than or equal to the + * number of vertices defined by the texCoords object, + * <code>texCoords.getBuffer().limit() / </code> <i>num_words</i>. + * + * @since Java 3D 1.3 + */ + public void setTexCoordRefBuffer(int texCoordSet, J3DBuffer texCoords) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setTexCoordRefBuffer( + texCoordSet, texCoords); + + } + + + /** + * Gets the texture coordinate array buffer reference for the specified + * texture coordinate set. + * + * @param texCoordSet texture coordinate set in this geometry array + * + * @return the current texture coordinate array buffer reference + * for the specified texture coordinate set + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is not <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or texCoordSet is out of range. + * + * @since Java 3D 1.3 + */ + public J3DBuffer getTexCoordRefBuffer(int texCoordSet) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getTexCoordRefBuffer(texCoordSet); + } + + + /** + * Sets the float texture coordinate array reference for the specified + * texture coordinate set to the + * specified array. The array contains floating-point <i>s</i>, + * <i>t</i>, and, optionally, <i>r</i> and <i>q</i> values for each + * vertex (for + * a total of 2*<i>n</i> , 3*<i>n</i> or 4*<i>n</i> values, + * where <i>n</i> is + * the number of vertices). Only one of + * <code>texCoordRefFloat</code>, <code>texCoordRef2f</code>, or + * <code>texCoordRef3f</code> may be non-null (or they may all be + * null). An attempt to set more than one of these attributes to + * a non-null reference will result in an exception being thrown. + * If all texCoord array references are null and texture + * coordinates are enabled (that is, the vertexFormat includes + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code>, or + * <code>TEXTURE_COORDINATE_4</code>), the entire geometry + * array object is treated as if it were null--any Shape3D or + * Morph node that uses this geometry array will not be drawn. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param texCoords an array of 2*<i>n</i>, 3*<i>n</i> or + * 4*<i>n</i> values to + * which a reference will be set. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * @exception IllegalArgumentException if the specified array is + * non-null and any other texCoord reference is also non-null. + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code>, or if texCoordSet is out of range, + * or if + * <code>texCoords.length < </code> <i>num_words</i> <code> * + * (initialTexCoordIndex + validVertexCount)</code>, + * where <i>num_words</i> is 2, 3, or 4 depending on the vertex + * texture coordinate format. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the texture coordinate index array is greater than or equal to the + * number of vertices defined by the texCoords array, + * <code>texCoords.length / </code> <i>num_words</i>. + * + * @since Java 3D 1.2 + */ + public void setTexCoordRefFloat(int texCoordSet, float[] texCoords) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + ((GeometryArrayRetained)this.retained).setTexCoordRefFloat( + texCoordSet, texCoords); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * Gets the float texture coordinate array reference for the specified + * texture coordinate set. + * + * @param texCoordSet texture coordinate set in this geometry array + * + * @return the current float texture coordinate array reference + * for the specified texture coordinate set + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>BY_REFERENCE</code>, + * is <code>USE_NIO_BUFFER</code>, or is <code>INTERLEAVED</code>. + * + * @exception ArrayIndexOutOfBoundsException if none of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or texCoordSet is out of range. + * + * @since Java 3D 1.2 + */ + public float[] getTexCoordRefFloat(int texCoordSet) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getTexCoordRefFloat( + texCoordSet); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for TexCoord2f arrays + * + * @since Java 3D 1.2 + */ + public void setTexCoordRef2f(int texCoordSet, TexCoord2f[] texCoords) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + if ((format & (TEXTURE_COORDINATE_3 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray94")); + + ((GeometryArrayRetained)this.retained).setTexCoordRef2f( + texCoordSet, texCoords); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for TexCoord2f arrays + * + * @since Java 3D 1.2 + */ + public TexCoord2f[] getTexCoordRef2f(int texCoordSet) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getTexCoordRef2f( + texCoordSet); + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for TexCoord3f arrays + * + * @since Java 3D 1.2 + */ + public void setTexCoordRef3f(int texCoordSet, TexCoord3f[] texCoords) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + if ((format & (TEXTURE_COORDINATE_2 | TEXTURE_COORDINATE_4)) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray95")); + + ((GeometryArrayRetained)this.retained).setTexCoordRef3f( + texCoordSet, texCoords); + + // NOTE: the checks for multiple non-null references, and the + // array length check need to be done in the retained method + } + + + /** + * @deprecated As of Java 3D version 1.3, use geometry by-copy + * for TexCoord3f arrays + * + * @since Java 3D 1.2 + */ + public TexCoord3f[] getTexCoordRef3f(int texCoordSet) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & BY_REFERENCE) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray83")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + if ((format & INTERLEAVED) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray84")); + + return ((GeometryArrayRetained)this.retained).getTexCoordRef3f( + texCoordSet); + } + + /** + * Sets the interleaved vertex array reference to the specified + * array. The vertex components must be stored in a predetermined + * order in the array. The order is: texture coordinates, colors, + * normals, and positional coordinates. In the case of texture + * coordinates, the values for each texture coordinate set + * are stored in order from 0 through texCoordSetCount-1. Only those + * components that are enabled appear in the vertex. The number + * of words per vertex depends on which vertex components are + * enabled. Texture coordinates, if enabled, use 2 words per + * texture coordinate set per vertex for + * <code>TEXTURE_COORDINATE_2</code>, 3 words per texture + * coordinate set per vertex for + * <code>TEXTURE_COORDINATE_3</code> or 4 words per texture + * coordinate set per vertex for + * <code>TEXTURE_COORDINATE_4</code>. Colors, if enabled, use 3 + * words per vertex for <code>COLOR_3</code> or 4 words per vertex + * for <code>COLOR_4</code>. Normals, if enabled, use 3 words per + * vertex. Positional coordinates, which are always enabled, use + * 3 words per vertex. For example, the format of interleaved + * data for a GeometryArray object whose vertexFormat includes + * <code>COORDINATES</code>, <code>COLOR_3</code>, and + * <code>NORMALS</code> would be: <i>red</i>, <i>green</i>, + * <i>blue</i>, <i>Nx</i>, <i>Ny</i>, <i>Nz</i>, <i>x</i>, + * <i>y</i>, <i>z</i>. All components of a vertex are stored in + * adjacent memory locations. The first component of vertex 0 is + * stored beginning at index 0 in the array. The first component + * of vertex 1 is stored beginning at index + * <i>words_per_vertex</i> in the array. The total number of + * words needed to store <i>n</i> vertices is + * <i>words_per_vertex</i>*<i>n</i>. + * + * @param vertexData an array of vertex values to which a + * reference will be set. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>INTERLEAVED</code> + * or is <code>USE_NIO_BUFFER</code>. + * + * @exception ArrayIndexOutOfBoundsException if + * <code>vertexData.length</code> < <i>words_per_vertex</i> * + * (<code>initialVertexIndex + validVertexCount</code>), + * where <i>words_per_vertex</i> depends on which formats are enabled. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the index array associated with any of the enabled vertex + * components (coord, color, normal, texcoord) is greater than or + * equal to the number of vertices defined by the vertexData + * array, + * <code>vertexData.length / </code> <i>words_per_vertex</i>. + * + * @since Java 3D 1.2 + */ + public void setInterleavedVertices(float[] vertexData) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & INTERLEAVED) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray85")); + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + ((GeometryArrayRetained)this.retained).setInterleavedVertices(vertexData); + + // NOTE: the array length check needs to be done in the retained method + } + + + /** + * Gets the interleaved vertices array reference. + * @return the current interleaved vertices array reference. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>INTERLEAVED</code> + * or is <code>USE_NIO_BUFFER</code>. + * + * @since Java 3D 1.2 + */ + public float[] getInterleavedVertices() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & INTERLEAVED) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray85")); + + + if ((format & USE_NIO_BUFFER) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray119")); + + return ((GeometryArrayRetained)this.retained).getInterleavedVertices(); + } + + /** + * Sets the interleaved vertex buffer reference to the specified + * buffer object. The buffer must contain a java.nio.FloatBuffer object. + * The vertex components must be stored in a predetermined + * order in the buffer. The order is: texture coordinates, colors, + * normals, and positional coordinates. In the case of texture + * coordinates, the values for each texture coordinate set + * are stored in order from 0 through texCoordSetCount-1. Only those + * components that are enabled appear in the vertex. The number + * of words per vertex depends on which vertex components are + * enabled. Texture coordinates, if enabled, use 2 words per + * texture coordinate set per vertex for + * <code>TEXTURE_COORDINATE_2</code>, 3 words per texture + * coordinate set per vertex for + * <code>TEXTURE_COORDINATE_3</code> or 4 words per texture + * coordinate set per vertex for + * <code>TEXTURE_COORDINATE_4</code>. Colors, if enabled, use 3 + * words per vertex for <code>COLOR_3</code> or 4 words per vertex + * for <code>COLOR_4</code>. Normals, if enabled, use 3 words per + * vertex. Positional coordinates, which are always enabled, use + * 3 words per vertex. For example, the format of interleaved + * data for a GeometryArray object whose vertexFormat includes + * <code>COORDINATES</code>, <code>COLOR_3</code>, and + * <code>NORMALS</code> would be: <i>red</i>, <i>green</i>, + * <i>blue</i>, <i>Nx</i>, <i>Ny</i>, <i>Nz</i>, <i>x</i>, + * <i>y</i>, <i>z</i>. All components of a vertex are stored in + * adjacent memory locations. The first component of vertex 0 is + * stored beginning at index 0 in the buffer. The first component + * of vertex 1 is stored beginning at index + * <i>words_per_vertex</i> in the buffer. The total number of + * words needed to store <i>n</i> vertices is + * <i>words_per_vertex</i>*<i>n</i>. + * + * @param vertexData a J3DBuffer object to which a reference will be set. + * The buffer contains an NIO float buffer of + * <i>words_per_vertex</i>*<i>n</i> values. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>INTERLEAVED</code> + * or is not <code>USE_NIO_BUFFER</code>. + * + * @exception IllegalArgumentException if the java.nio.Buffer + * contained in the specified J3DBuffer is not a + * java.nio.FloatBuffer object. + * + * @exception ArrayIndexOutOfBoundsException if + * <code>vertexData.getBuffer().limit()</code> < <i>words_per_vertex</i> * + * (<code>initialVertexIndex + validVertexCount</code>), + * where <i>words_per_vertex</i> depends on which formats are enabled. + * + * @exception ArrayIndexOutOfBoundsException if this GeometryArray + * object is a subclass of IndexedGeometryArray, and any element + * in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the index array associated with any of the enabled vertex + * components (coord, color, normal, texcoord) is greater than or + * equal to the number of vertices defined by the vertexData + * object, + * <code>vertexData.getBuffer().limit() / </code> <i>words_per_vertex</i>. + * + * @since Java 3D 1.3 + */ + public void setInterleavedVertexBuffer(J3DBuffer vertexData) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray86")); + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & INTERLEAVED) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray85")); + + + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + ((GeometryArrayRetained)this.retained).setInterleavedVertexBuffer(vertexData); + + } + + + /** + * Gets the interleaved vertex array buffer reference. + * @return the current interleaved vertex array buffer reference. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalStateException if the data mode for this geometry + * array object is not <code>INTERLEAVED</code> + * or is not <code>USE_NIO_BUFFER</code>. + * + * @since Java 3D 1.3 + */ + public J3DBuffer getInterleavedVertexBuffer() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_REF_DATA_READ) && + !this.getCapability(J3D_1_2_ALLOW_REF_DATA_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("GeometryArray87")); + } + + int format = ((GeometryArrayRetained)this.retained).vertexFormat; + if ((format & INTERLEAVED) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray85")); + + if ((format & USE_NIO_BUFFER) == 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray118")); + + return ((GeometryArrayRetained)this.retained).getInterleavedVertexBuffer(); + + } +} diff --git a/src/classes/share/javax/media/j3d/GeometryArrayRetained.java b/src/classes/share/javax/media/j3d/GeometryArrayRetained.java new file mode 100644 index 0000000..774371d --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryArrayRetained.java @@ -0,0 +1,10631 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import com.sun.j3d.internal.Distance; +import javax.vecmath.*; +import java.lang.Math; +import java.util.ArrayList; +import java.util.Vector; +import java.util.Enumeration; +import com.sun.j3d.internal.ByteBufferWrapper; +import com.sun.j3d.internal.BufferWrapper; +import com.sun.j3d.internal.FloatBufferWrapper; +import com.sun.j3d.internal.DoubleBufferWrapper; + + +/** + * The GeometryArray object contains arrays of positional coordinates, + * colors, normals and/or texture coordinates that describe + * point, line, or surface geometry. It is extended to create + * the various primitive types (e.g., lines, triangle_strips, etc.) + */ + +abstract class GeometryArrayRetained extends GeometryRetained{ + + + + // TODO: Memory footprint reduction. Should have separate object to + // to contain specific data such as a ByRef object for + // all ByRef related data. So that incases where no + // ByRef is needed, the ByRef object reference is + // set to null. Hence saving memory! + // Need object such as Texture, D3d and ByRef ... + // + + + // Contains a bitset indicating which components are present + int vertexFormat; + + // Whether this geometry was ever rendered as transparent + int c4fAllocated = 0; + + // Total Number of vertices + int vertexCount; + + // number of vertices used in rendering + int validVertexCount; + + // The vertex data in packed format + float vertexData[]; + + // vertex data in packed format for each screen in multi-screen situation + // if alpha values of each vertex are to be updated + float mvertexData[][]; + + // + // The following offset/stride values are internally computed + // from the format + // + + // Stride (in words) from one vertex to the next + int stride; + + // Stride (in words) from one texture coordinate to the next + int texCoordStride; + + // Offset (in words) within each vertex of the coordinate position + int coordinateOffset; + + // Offset (in words) within each vertex of the normal + int normalOffset; + + // Offset (in words) within each vertex of the color + int colorOffset; + + // Offset (in words) within each vertex of the texture coordinate + int textureOffset; + + // alpha value for transparency and texture blending + float[] lastAlpha = new float[1]; + float lastScreenAlpha = -1; + + int colorChanged = 0; + + // true if alpha value from transparencyAttrubute has changed + boolean alphaChanged = false; + + // byte to float scale factor + static final float ByteToFloatScale = 1.0f/255.0f; + + // float to byte scale factor + static final float FloatToByteScale = 255.0f; + + // Set flag indicating that we are in the updater. This flag + // can be used by the various setRef methods to inhibit any + // update messages + boolean inUpdater = false; + + // Array List used for messages + ArrayList gaList = new ArrayList(1); + + + // Target threads to be notified when morph changes + static final int targetThreads = (J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_GEOMETRY); + + // used for byReference geometry + float[] floatRefCoords = null; + double[] doubleRefCoords = null; + Point3d[] p3dRefCoords = null; + Point3f[] p3fRefCoords = null; + + // Used for NIO buffer geometry + J3DBuffer coordRefBuffer = null; + FloatBufferWrapper floatBufferRefCoords = null; + DoubleBufferWrapper doubleBufferRefCoords = null; + + // Initial index to use for rendering + int initialCoordIndex = 0; + int initialColorIndex = 0; + int initialNormalIndex = 0; + int initialTexCoordIndex[] = null; + int initialVertexIndex = 0; + + + // used for byReference colors + float[] floatRefColors = null; + byte[] byteRefColors = null; + Color3f[] c3fRefColors = null; + Color4f[] c4fRefColors = null; + Color3b[] c3bRefColors = null; + Color4b[] c4bRefColors = null; + + // Used for NIO buffer colors + J3DBuffer colorRefBuffer = null; + FloatBufferWrapper floatBufferRefColors = null; + ByteBufferWrapper byteBufferRefColors = null; + + // flag to indicate if the "by reference" component is already set + int vertexType = 0; + static final int PF = 0x1; + static final int PD = 0x2; + static final int P3F = 0x4; + static final int P3D = 0x8; + static final int VERTEX_DEFINED = PF | PD | P3F | P3D; + + + static final int CF = 0x10; + static final int CUB = 0x20; + static final int C3F = 0x40; + static final int C4F = 0x80; + static final int C3UB = 0x100; + static final int C4UB = 0x200; + static final int COLOR_DEFINED = CF | CUB | C3F | C4F| C3UB | C4UB; + + static final int NF = 0x400; + static final int N3F = 0x800; + static final int NORMAL_DEFINED = NF | N3F; + + static final int TF = 0x1000; + static final int T2F = 0x2000; + static final int T3F = 0x4000; + static final int TEXCOORD_DEFINED = TF | T2F | T3F; + + // flag for execute geometry array when by reference + static final int COORD_FLOAT = 0x01; + static final int COORD_DOUBLE = 0x02; + static final int COLOR_FLOAT = 0x04; + static final int COLOR_BYTE = 0x08; + static final int NORMAL_FLOAT = 0x10; + static final int TEXCOORD_FLOAT = 0x20; + + + // used by "by reference" normals + float[] floatRefNormals = null; + Vector3f[] v3fRefNormals = null; + + // Used for NIO buffer normals + J3DBuffer normalRefBuffer = null; + FloatBufferWrapper floatBufferRefNormals = null; + + + // used by "by reference" tex coords + Object[] refTexCoords = null; + TexCoord2f[] t2fRefTexCoords = null; + TexCoord3f[] t3fRefTexCoords = null; + + // Used for NIO buffer tex coords + Object[] refTexCoordsBuffer = null; + //FloatBufferWrapper[] floatBufferRefTexCoords = null; + + + // used by interleaved array + float[] interLeavedVertexData = null; + + // used by interleaved NIO buffer + J3DBuffer interleavedVertexBuffer = null; + FloatBufferWrapper interleavedFloatBufferImpl = null; + + // pointers used, when transparency is turned on + // or when its an object such as C3F, P3F etc .. + // TODO: Update this for J3DBuffer + float[] mirrorFloatRefCoords = null; + double[] mirrorDoubleRefCoords = null; + float[] mirrorFloatRefNormals = null; + float[] mirrorFloatRefTexCoords = null; + Object[] mirrorRefTexCoords = null; + + float[][] mirrorFloatRefColors = new float[1][]; + byte[][] mirrorUnsignedByteRefColors= new byte[1][]; + float[][] mirrorInterleavedColorPointer = null; + + + // This native method builds a native representation of this object, then + // returns the nativeId. + native int build(int geoType); + + // boolean to determine if a mirror was allocated + int mirrorVertexAllocated = 0; + int mirrorColorAllocated = 0; + boolean mirrorTexCoordAllocated = false; + boolean mirrorNormalAllocated = false; + + // Some dirty bits for GeometryArrays + static final int COORDINATE_CHANGED = 0x01; + static final int NORMAL_CHANGED = 0x02; + static final int COLOR_CHANGED = 0x04; + static final int TEXTURE_CHANGED = 0x08; + static final int BOUNDS_CHANGED = 0x10; + static final int INDEX_CHANGED = 0x20; + static final int STRIPCOUNT_CHANGED = 0x40; + static final int VERTEX_CHANGED = COORDINATE_CHANGED | + NORMAL_CHANGED | + COLOR_CHANGED | + TEXTURE_CHANGED; + + + static final int defaultTexCoordSetMap[] = {0}; + int texCoordSetCount = 0; + int [] texCoordSetMap = null; + + // this array contains offset to the texCoord data for each + // texture unit. -1 means no corresponding texCoord data offset + int [] texCoordSetMapOffset = null; + + // This point to a list of VertexBuffers in a Vector structure + // Each element correspond to a D3D context that create this VB. + // Note that this GeometryArray can be used by multiple ctx. + long pVertexBuffers = 0; + int dirtyFlag; + + // each bit corresponds to a unique renderer if shared context + // or a unique canvas otherwise + int resourceCreationMask = 0x0; + + + // Reference count of renderMolecules per dlist created, this is either + // per renderer for useSharedCtx or per Canvas for non-shared ctx + // note since + // renderer and canvasindex starts from 1, the first entry of this + // array will never be used. + int[] renderMolPerDlist = new int[2]; + + // timestamp used to create display list; same as above, this is either + // one per renderer for useSharedCtx, or one per Canvas for non-shared + // ctx + long[] timeStampPerDlist = new long[2]; + + + // Unique display list Id, if this geometry is shared + int dlistId = -1; + Integer dlistObj = null; + + // A list of pre-defined bits to indicate which component + // in this Texture object changed. + // static final int DLIST_CREATE_CHANGED = 0x01; + static final int INIT_MIRROR_GEOMETRY = 0x02; + + + // A list of Universes that this Geometry is referenced in Morph from + ArrayList morphUniverseList = null; + + // A list of ArrayLists which contain all the MorphRetained objects + // refering to this geometry. Each list corresponds to the universe + // above. + ArrayList morphUserLists = null; + + // The following variables are only used in compile mode + + // Offset of a geometry array into the merged array + int[] geoOffset; + + // vertexcount of a geometry array in a merge array + int[] compileVcount; + + boolean isCompiled = false; + + boolean isShared = false; + + IndexedGeometryArrayRetained cloneSourceArray = null; + +// private MemoryFreeList pickVectorFreelist = +// FreeListManager.getFreeList(FreeListManager.PICKVECTOR); + + static final double EPS = 1.0e-13; + + native void freeD3DArray(boolean deleteVB); + + GeometryArrayRetained() { + dirtyFlag = INDEX_CHANGED|VERTEX_CHANGED; + lastAlpha[0] = 1.0f; + } + + + void setLive(boolean inBackgroundGroup, int refCount) { + dirtyFlag = VERTEX_CHANGED|INDEX_CHANGED; + isEditable = !isWriteStatic(); + super.doSetLive(inBackgroundGroup, refCount); + super.markAsLive(); + // Send message to RenderingAttribute structure to obtain a dlistId + // System.out.println("Geometry - "+this+"refCount = "+this.refCount); + if (this.refCount > 1) { + // Send to rendering attribute structure, + /* + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.GEOMETRYARRAY_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(DLIST_CREATE_CHANGED); + VirtualUniverse.mc.processMessage(createMessage); + */ + isShared = true; + } // Clone geometry only for the first setLive + else { + // If geometry is indexed and use_index_coord is false, unindexify + // otherwise, set mirrorGeometry to null (from previous clearLive) + if (this instanceof IndexedGeometryArrayRetained) { + // Send to rendering attribute structure, + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.GEOMETRY_CHANGED; + createMessage.universe = null; + createMessage.args[0] = null; + createMessage.args[1]= this; + createMessage.args[2]= new Integer(INIT_MIRROR_GEOMETRY); + VirtualUniverse.mc.processMessage(createMessage); + } + } + + } + + void clearLive(int refCount) { + super.clearLive(refCount); + + if (this.refCount <= 0) { + if (pVertexBuffers != 0) { + J3dMessage renderMessage = VirtualUniverse.mc.getMessage(); + renderMessage.threads = J3dThread.RENDER_THREAD; + renderMessage.type = J3dMessage.RENDER_IMMEDIATE; + renderMessage.universe = null; + renderMessage.view = null; + renderMessage.args[0] = null; + renderMessage.args[1] = this; + // Any one renderer is fine since VB store the ctx + // where it is created. + Enumeration e = Screen3D.deviceRendererMap.elements(); + Renderer rdr = (Renderer) e.nextElement(); + rdr.rendererStructure.addMessage(renderMessage); + VirtualUniverse.mc.setWorkForRequestRenderer(); + } + isShared = false; + } + } + + void computeBoundingBox() { + + // System.out.println("computeBoundingBox ...."); + + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + // by copy + computeBoundingBox(initialVertexIndex, vertexData); + + } else if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { // USE_NIO_BUFFER + //System.out.println("vertexFormat & GeometryArray.USE_NIO_BUFFER"); + if((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + computeBoundingBox(initialCoordIndex, interleavedFloatBufferImpl); + } else if((vertexType & PF) != 0) { + computeBoundingBox(floatBufferRefCoords); + } else if((vertexType & PD) != 0) { + computeBoundingBox(doubleBufferRefCoords); + } + + } else if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + //System.out.println("vertexFormat & GeometryArray.INTERLEAVED"); + computeBoundingBox(initialCoordIndex, interLeavedVertexData); + } else if ((vertexType & PF) != 0) { + //System.out.println("vertexType & PF"); + computeBoundingBox(floatRefCoords); + } else if ((vertexType & P3F) != 0) { + //System.out.println("vertexType & P3F"); + computeBoundingBox(p3fRefCoords); + } else if ((vertexType & P3D) != 0) { + //System.out.println("vertexType & P3D"); + computeBoundingBox(p3dRefCoords); + } else if ((vertexType & PD) != 0) { + //System.out.println("vertexType & PD"); + computeBoundingBox(doubleRefCoords); + } + } + + + // NullGeometry is true only for byRef case + void processCoordsChanged(boolean nullGeo) { + + /* + System.out.println("processCoordsChanged : nullGeo " + nullGeo); + System.out.println("Before :processCoordsChanged : geoBounds "); + System.out.println(geoBounds); + */ + if (nullGeo) { + synchronized(geoBounds) { + geoBounds.setLower(-1.0, -1.0, -1.0); + geoBounds.setUpper(1.0, 1.0, 1.0); + boundsDirty = false; + } + synchronized(centroid) { + recompCentroid = false; + this.centroid.set(geoBounds.getCenter()); + } + + } + else { + // re-compute centroid if used + synchronized(centroid) { + recompCentroid = true; + } + + synchronized(geoBounds) { + boundsDirty = true; + computeBoundingBox(); + } + + /* + System.out.println("After :processCoordsChanged : geoBounds "); + System.out.println(geoBounds); + */ + } + } + + + void computeBoundingBox(int vIndex, float[] vdata) { + int i, offset; + double xmin, xmax, ymin, ymax, zmin, zmax; + + + //System.out.println("Before : computeBoundingBox : geoBounds "); + // System.out.println(geoBounds); + + synchronized(geoBounds) { + + // If autobounds compute is false then return + // It is possible that user call getBounds() before + // this Geometry add to live scene graph. + if ((computeGeoBounds == 0) && (refCount > 0)) { + return; + } + if (!boundsDirty) + return; + + // Initial offset + offset = vIndex * stride+coordinateOffset; + // Compute the bounding box + xmin = xmax = vdata[offset]; + ymin = ymax = vdata[offset+1]; + zmin = zmax = vdata[offset+2]; + offset += stride; + for (i=1; i<validVertexCount; i++) { + if (vdata[offset] > xmax) + xmax = vdata[offset]; + if (vdata[offset] < xmin) + xmin = vdata[offset]; + + if (vdata[offset+1] > ymax) + ymax = vdata[offset+1]; + if (vdata[offset+1] < ymin) + ymin = vdata[offset+1]; + + if (vdata[offset+2] > zmax) + zmax = vdata[offset+2]; + if (vdata[offset+2] < zmin) + zmin = vdata[offset+2]; + + offset += stride; + } + + geoBounds.setUpper(xmax, ymax, zmax); + geoBounds.setLower(xmin, ymin, zmin); + boundsDirty = false; + } + /* + System.out.println("After : computeBoundingBox : geoBounds "); + System.out.println(geoBounds); + */ + } + + // Compute boundingbox for interleaved nio buffer + void computeBoundingBox(int vIndex, FloatBufferWrapper vdata) { + int i, offset; + double xmin, xmax, ymin, ymax, zmin, zmax; + + + synchronized(geoBounds) { + // If autobounds compute is false then return + if ((computeGeoBounds == 0) && (refCount > 0)) { + return; + } + + if (!boundsDirty) + return; + + // Initial offset + offset = vIndex * stride+coordinateOffset; + // Compute the bounding box + xmin = xmax = vdata.get(offset); + ymin = ymax = vdata.get(offset+1); + zmin = zmax = vdata.get(offset+2); + offset += stride; + for (i=1; i<validVertexCount; i++) { + if (vdata.get(offset) > xmax) + xmax = vdata.get(offset); + if (vdata.get(offset) < xmin) + xmin = vdata.get(offset); + + if (vdata.get(offset+1) > ymax) + ymax = vdata.get(offset+1); + if (vdata.get(offset+1) < ymin) + ymin = vdata.get(offset+1); + + if (vdata.get(offset+2) > zmax) + zmax = vdata.get(offset+2); + if (vdata.get(offset+2) < zmin) + zmin = vdata.get(offset+2); + + offset += stride; + } + + geoBounds.setUpper(xmax, ymax, zmax); + geoBounds.setLower(xmin, ymin, zmin); + boundsDirty = false; + } + } + + + // compute bounding box for coord with noi buffer + void computeBoundingBox( DoubleBufferWrapper buffer) { + int i, j, k, sIndex; + double xmin, xmax, ymin, ymax, zmin, zmax; + + synchronized(geoBounds) { + // If autobounds compute is false then return + if ((computeGeoBounds == 0) && (refCount > 0)) { + return; + } + + if (!boundsDirty) + return; + + sIndex = initialCoordIndex; + int maxIndex = 3*validVertexCount; + + // Compute the bounding box + xmin = xmax = buffer.get(sIndex++); + ymin = ymax = buffer.get(sIndex++); + zmin = zmax = buffer.get(sIndex++); + + for (i=sIndex; i<maxIndex; i+=3) { + j = i + 1; + k = i + 2; + + if (buffer.get(i) > xmax) + xmax = buffer.get(i); + if (buffer.get(i) < xmin) + xmin = buffer.get(i); + + if (buffer.get(j) > ymax) + ymax = buffer.get(j); + if (buffer.get(j) < ymin) + ymin = buffer.get(j); + + if (buffer.get(k) > zmax) + zmax = buffer.get(k); + if (buffer.get(k) < zmin) + zmin = buffer.get(k); + + } + geoBounds.setUpper(xmax, ymax, zmax); + geoBounds.setLower(xmin, ymin, zmin); + boundsDirty = false; + } + } + + // compute bounding box for coord with noi buffer + void computeBoundingBox( FloatBufferWrapper buffer) { + int i, j, k, sIndex; + double xmin, xmax, ymin, ymax, zmin, zmax; + + + synchronized(geoBounds) { + // If autobounds compute is false then return + if ((computeGeoBounds == 0) && (refCount > 0)) { + return; + } + + if (!boundsDirty) + return; + + + sIndex = initialCoordIndex; + int maxIndex = 3*validVertexCount; + + // Compute the bounding box + xmin = xmax = buffer.get(sIndex++); + ymin = ymax = buffer.get(sIndex++); + zmin = zmax = buffer.get(sIndex++); + + for (i=sIndex; i<maxIndex; i+=3) { + j = i + 1; + k = i + 2; + + if (buffer.get(i) > xmax) + xmax = buffer.get(i); + if (buffer.get(i) < xmin) + xmin = buffer.get(i); + + if (buffer.get(j) > ymax) + ymax = buffer.get(j); + if (buffer.get(j) < ymin) + ymin = buffer.get(j); + + if (buffer.get(k) > zmax) + zmax = buffer.get(k); + if (buffer.get(k) < zmin) + zmin = buffer.get(k); + + } + geoBounds.setUpper(xmax, ymax, zmax); + geoBounds.setLower(xmin, ymin, zmin); + boundsDirty = false; + } + } + + void computeBoundingBox(float[] coords) { + // System.out.println("GeometryArrayRetained : computeBoundingBox(float[] coords)"); + int i, j, k, sIndex; + double xmin, xmax, ymin, ymax, zmin, zmax; + + synchronized(geoBounds) { + // If autobounds compute is false then return + if ((computeGeoBounds == 0) && (refCount > 0)) { + return; + } + + if (!boundsDirty) + return; + + sIndex = initialCoordIndex; + int maxIndex = 3*validVertexCount; + + // Compute the bounding box + xmin = xmax = coords[sIndex++]; + ymin = ymax = coords[sIndex++]; + zmin = zmax = coords[sIndex++]; + + for (i=sIndex; i<maxIndex; i+=3) { + j = i + 1; + k = i + 2; + + if (coords[i] > xmax) + xmax = coords[i]; + if (coords[i] < xmin) + xmin = coords[i]; + + if (coords[j] > ymax) + ymax = coords[j]; + if (coords[j] < ymin) + ymin = coords[j]; + + if (coords[k] > zmax) + zmax = coords[k]; + if (coords[k] < zmin) + zmin = coords[k]; + + } + geoBounds.setUpper(xmax, ymax, zmax); + // System.out.println("max(" + xmax + ", " + ymax + ", " + zmax + ")"); + geoBounds.setLower(xmin, ymin, zmin); + // System.out.println("min(" + xmin + ", " + ymin + ", " + zmin + ")"); + + boundsDirty = false; + } + + } + + void computeBoundingBox(double[] coords) { + int i, j, k, sIndex; + double xmin, xmax, ymin, ymax, zmin, zmax; + + synchronized(geoBounds) { + // If autobounds compute is false then return + if ((computeGeoBounds == 0) && (refCount > 0)) { + return; + } + + if (!boundsDirty) + return; + + + sIndex = initialCoordIndex; + int maxIndex = 3*validVertexCount; + + // Compute the bounding box + xmin = xmax = coords[sIndex++]; + ymin = ymax = coords[sIndex++]; + zmin = zmax = coords[sIndex++]; + + for (i=sIndex; i<maxIndex; i+=3) { + j = i + 1; + k = i + 2; + + if (coords[i] > xmax) + xmax = coords[i]; + if (coords[i] < xmin) + xmin = coords[i]; + + if (coords[j] > ymax) + ymax = coords[j]; + if (coords[j] < ymin) + ymin = coords[j]; + + if (coords[k] > zmax) + zmax = coords[k]; + if (coords[k] < zmin) + zmin = coords[k]; + + } + geoBounds.setUpper(xmax, ymax, zmax); + geoBounds.setLower(xmin, ymin, zmin); + boundsDirty = false; + } + + } + + void computeBoundingBox(Point3f[] coords) { + + double xmin, xmax, ymin, ymax, zmin, zmax; + Point3f p; + + synchronized(geoBounds) { + // If autobounds compute is false then return + if ((computeGeoBounds == 0) && (refCount > 0)) { + return; + } + + if (!boundsDirty) + return; + + + + // Compute the bounding box + xmin = xmax = coords[initialCoordIndex].x; + ymin = ymax = coords[initialCoordIndex].y; + zmin = zmax = coords[initialCoordIndex].z; + + for (int i=initialCoordIndex+1; i<validVertexCount; i++) { + p = coords[i]; + if (p.x > xmax) xmax = p.x; + if (p.x < xmin) xmin = p.x; + + if (p.y > ymax) ymax = p.y; + if (p.y < ymin) ymin = p.y; + + if (p.z > zmax) zmax = p.z; + if (p.z < zmin) zmin = p.z; + + } + geoBounds.setUpper(xmax, ymax, zmax); + geoBounds.setLower(xmin, ymin, zmin); + boundsDirty = false; + } + + } + + void computeBoundingBox(Point3d[] coords) { + + double xmin, xmax, ymin, ymax, zmin, zmax; + Point3d p; + + synchronized(geoBounds) { + // If autobounds compute is false then return + if ((computeGeoBounds == 0) && (refCount > 0)) { + return; + } + + if (!boundsDirty) + return; + + + // Compute the bounding box + xmin = xmax = coords[initialCoordIndex].x; + ymin = ymax = coords[initialCoordIndex].y; + zmin = zmax = coords[initialCoordIndex].z; + + for (int i=initialCoordIndex+1; i<validVertexCount; i++) { + p = coords[i]; + if (p.x > xmax) xmax = p.x; + if (p.x < xmin) xmin = p.x; + + if (p.y > ymax) ymax = p.y; + if (p.y < ymin) ymin = p.y; + + if (p.z > zmax) zmax = p.z; + if (p.z < zmin) zmin = p.z; + + } + geoBounds.setUpper(xmax, ymax, zmax); + geoBounds.setLower(xmin, ymin, zmin); + boundsDirty = false; + } + + } + + + synchronized void update() { + } + + void setupMirrorVertexPointer(int vType) { + int i, index; + + switch (vType) { + case PF: + if (floatRefCoords == null) { + if ((vertexType & VERTEX_DEFINED) == PF) { + vertexType &= ~PF; + mirrorFloatRefCoords = null; + mirrorVertexAllocated &= ~PF; + } + } + else { + vertexType |= PF; + mirrorFloatRefCoords = floatRefCoords; + mirrorVertexAllocated &= ~PF; + } + + break; + case PD: + if (doubleRefCoords == null) { + if ((vertexType & VERTEX_DEFINED) == PD) { + mirrorDoubleRefCoords = null; + mirrorVertexAllocated &= ~PD; + vertexType &= ~PD; + } + vertexType &= ~PD; + } + else { + vertexType |= PD; + mirrorDoubleRefCoords = doubleRefCoords; + mirrorVertexAllocated &= ~PD; + } + + break; + case P3F: + if (p3fRefCoords == null) { + vertexType &= ~P3F; + // Don't set the mirrorFloatRefCoords to null, + // may be able to re-use + // mirrorFloatRefCoords = null; + } + else { + vertexType |= P3F; + + if ((mirrorVertexAllocated & PF) == 0) { + mirrorFloatRefCoords = new float[vertexCount * 3]; + mirrorVertexAllocated |= PF; + } + + index = initialCoordIndex * 3; + for ( i=initialCoordIndex; i<validVertexCount; i++) { + mirrorFloatRefCoords[index++] = p3fRefCoords[i].x; + mirrorFloatRefCoords[index++] = p3fRefCoords[i].y; + mirrorFloatRefCoords[index++] = p3fRefCoords[i].z; + } + } + break; + case P3D: + if (p3dRefCoords == null) { + vertexType &= ~P3D; + // Don't set the mirrorDoubleRefCoords to null, + // may be able to re-use + // mirrorDoubleRefCoords = null; + } + else { + vertexType |= P3D; + + if ((mirrorVertexAllocated & PD) == 0) { + mirrorDoubleRefCoords = new double[vertexCount * 3]; + mirrorVertexAllocated |= PD; + } + + index = initialCoordIndex * 3; + for ( i=initialCoordIndex; i<validVertexCount; i++) { + mirrorDoubleRefCoords[index++] = p3dRefCoords[i].x; + mirrorDoubleRefCoords[index++] = p3dRefCoords[i].y; + mirrorDoubleRefCoords[index++] = p3dRefCoords[i].z; + } + } + break; + default: + break; + + } + + } + + // TODO: may not need this function in NIO buffer version + // setup mirror vertex pointers for J3DBuffer version + void setupMirrorVertexPointerNIOBuffer(int vType) { + int i, index = 0; + switch(vType) { + case PF: + + break; + case PD: + + break; + + // do not need to handle P3F and P3D case in NIO buffer version + default: + break; + + } + + } + + // If turned transparent the first time, then force it to allocate + void setupMirrorInterleavedColorPointer(boolean force) { + int index, length, offset; + int i; + + if (force || (c4fAllocated != 0)) { // Color is present + + length = 4 * vertexCount; + + if (mirrorInterleavedColorPointer == null) { + mirrorInterleavedColorPointer = new float[1][length]; + } + + index = 4 * initialVertexIndex; + offset = stride * initialVertexIndex + colorOffset; + + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0 && + interLeavedVertexData != null ) { // java array + if ((vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + + for (i = initialVertexIndex; i < validVertexCount; i++) { + mirrorInterleavedColorPointer[0][index++] = + interLeavedVertexData[offset]; + mirrorInterleavedColorPointer[0][index++] = + interLeavedVertexData[offset+1]; + mirrorInterleavedColorPointer[0][index++] = + interLeavedVertexData[offset+2]; + mirrorInterleavedColorPointer[0][index++] = + interLeavedVertexData[offset+3]; + offset += stride; + } + } + else { + for (i = initialVertexIndex; i < validVertexCount; i++) { + mirrorInterleavedColorPointer[0][index++] = + interLeavedVertexData[offset]; + mirrorInterleavedColorPointer[0][index++] = + interLeavedVertexData[offset+1]; + mirrorInterleavedColorPointer[0][index++] = + interLeavedVertexData[offset+2]; + mirrorInterleavedColorPointer[0][index++] = 1.0f; + offset += stride; + } + } + + } else { // NIO BUFFER + if ((vertexFormat & GeometryArray.WITH_ALPHA) != 0 && + interleavedFloatBufferImpl != null) { + for (i = initialVertexIndex; i < validVertexCount; i++) { + interleavedFloatBufferImpl.position(offset); + interleavedFloatBufferImpl.get(mirrorInterleavedColorPointer[0], + index , 4); + index += 4; + offset += stride; + } + } + else { + for (i = initialVertexIndex; i < validVertexCount; i++) { + interleavedFloatBufferImpl.position(offset); + interleavedFloatBufferImpl.get(mirrorInterleavedColorPointer[0], + index, 3); + mirrorInterleavedColorPointer[0][index+3] = 1.0f; + index += 4; + offset += stride; + + } + } + } + c4fAllocated = GeometryArray.WITH_ALPHA; + } + } + + // If turned transparent the first time, then force it to allocate + void setupMirrorColorPointer(int ctype, boolean force) { + int i, srcIndex = 0, dstIndex = 0; + int multiplier; + + if (c4fAllocated == 0 && !force) { + multiplier = 3; + } else { + + // If the first time, we are forced to allocate 4f, then + // we need to force the allocation of the colors again + // for the case when allocation has previously occurred + // only for RGB + if (force && (c4fAllocated == 0) && + (vertexFormat & GeometryArray.WITH_ALPHA) == 0) { + mirrorColorAllocated = 0; + } + c4fAllocated = GeometryArray.WITH_ALPHA; + multiplier = 4; + } + + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { // java array + switch (ctype) { + case CF: + if (floatRefColors == null) { + if ((c4fAllocated == 0) && !force && + (vertexType & COLOR_DEFINED) == CF) { + mirrorFloatRefColors[0] = null; + mirrorColorAllocated &= ~CF; + } + vertexType &= ~CF; + return; + } + + vertexType |= CF; + if (c4fAllocated == 0 && !force) { + mirrorFloatRefColors[0] = floatRefColors; + mirrorColorAllocated &= ~CF; + } + else { + if ((mirrorColorAllocated & CF) == 0) { + mirrorFloatRefColors[0] = new float[4 * vertexCount]; + mirrorColorAllocated |= CF; + } + + if ((vertexFormat & GeometryArray.WITH_ALPHA) == 0) { + + srcIndex = initialColorIndex * 3; + dstIndex = initialColorIndex * 4; + + for (i = initialColorIndex; i < validVertexCount; i++) { + mirrorFloatRefColors[0][dstIndex++] = + floatRefColors[srcIndex++]; + mirrorFloatRefColors[0][dstIndex++] = + floatRefColors[srcIndex++]; + mirrorFloatRefColors[0][dstIndex++] = + floatRefColors[srcIndex++]; + mirrorFloatRefColors[0][dstIndex++] = 1.0f; + } + + } + else { + srcIndex = initialColorIndex * 4; + System.arraycopy(floatRefColors, srcIndex, + mirrorFloatRefColors[0], srcIndex, + (4*validVertexCount)); + } + } + break; + case CUB: + if (byteRefColors == null) { + if (c4fAllocated == 0 && !force && + ((vertexType & COLOR_DEFINED) == CUB) ) { + mirrorUnsignedByteRefColors[0] = null; + mirrorColorAllocated &= ~CUB; + } + vertexType &= ~CUB; + return; + } + vertexType |= CUB; + if (c4fAllocated == 0 && !force) { + mirrorUnsignedByteRefColors[0] = byteRefColors; + mirrorColorAllocated &= ~CUB;; + } + else { + if ((mirrorColorAllocated & CUB) == 0) { + mirrorUnsignedByteRefColors[0] = new byte[4 * vertexCount]; + mirrorColorAllocated |= CUB; + } + if ((vertexFormat & GeometryArray.WITH_ALPHA) == 0) { + + srcIndex = initialColorIndex * 3; + dstIndex = initialColorIndex * 4; + + for (i = initialColorIndex; i < validVertexCount; i++) { + mirrorUnsignedByteRefColors[0][dstIndex++] = + byteRefColors[srcIndex++]; + mirrorUnsignedByteRefColors[0][dstIndex++] = + byteRefColors[srcIndex++]; + mirrorUnsignedByteRefColors[0][dstIndex++] = + byteRefColors[srcIndex++]; + mirrorUnsignedByteRefColors[0][dstIndex++] = + (byte)(255.0); + } + } + else { + srcIndex = initialColorIndex * 4; + System.arraycopy(byteRefColors, srcIndex, + mirrorUnsignedByteRefColors[0], srcIndex, + (4*validVertexCount)); + } + } + + break; + case C3F: + if (c3fRefColors == null) { + vertexType &= ~C3F; + return; + } + vertexType |=C3F ; + + if ((mirrorColorAllocated & CF) == 0) { + mirrorFloatRefColors[0] = new float[vertexCount * multiplier]; + mirrorColorAllocated |= CF; + } + if ((c4fAllocated & GeometryArray.WITH_ALPHA) == 0) { + + dstIndex = initialColorIndex * 3; + for (i = initialColorIndex; i < validVertexCount; i++) { + mirrorFloatRefColors[0][dstIndex++] = c3fRefColors[i].x; + mirrorFloatRefColors[0][dstIndex++] = c3fRefColors[i].y; + mirrorFloatRefColors[0][dstIndex++] = c3fRefColors[i].z; + } + } else { + + dstIndex = initialColorIndex * 4; + for (i = initialColorIndex; i < validVertexCount; i++) { + mirrorFloatRefColors[0][dstIndex++] = c3fRefColors[i].x; + mirrorFloatRefColors[0][dstIndex++] = c3fRefColors[i].y; + mirrorFloatRefColors[0][dstIndex++] = c3fRefColors[i].z; + mirrorFloatRefColors[0][dstIndex++] = 1.0f; + } + } + + break; + case C4F: + if (c4fRefColors == null) { + vertexType &= ~C4F; + return; + } + vertexType |=C4F ; + + if ((mirrorColorAllocated & CF) == 0) { + mirrorFloatRefColors[0] = new float[vertexCount << 2]; + mirrorColorAllocated |= CF; + } + + dstIndex = initialColorIndex * 4; + for (i = initialColorIndex; i < validVertexCount; i++) { + mirrorFloatRefColors[0][dstIndex++] = c4fRefColors[i].x; + mirrorFloatRefColors[0][dstIndex++] = c4fRefColors[i].y; + mirrorFloatRefColors[0][dstIndex++] = c4fRefColors[i].z; + mirrorFloatRefColors[0][dstIndex++] = c4fRefColors[i].w; + } + break; + case C3UB: + if (c3bRefColors == null) { + vertexType &= ~C3UB; + return; + } + vertexType |=C3UB ; + + if ((mirrorColorAllocated & CUB) == 0) { + mirrorUnsignedByteRefColors[0] = + new byte[vertexCount * multiplier]; + mirrorColorAllocated |= CUB; + } + if ((c4fAllocated & GeometryArray.WITH_ALPHA) == 0) { + dstIndex = initialColorIndex * 3; + for (i = initialColorIndex; i < validVertexCount; i++) { + mirrorUnsignedByteRefColors[0][dstIndex++] = c3bRefColors[i].x; + mirrorUnsignedByteRefColors[0][dstIndex++] = c3bRefColors[i].y; + mirrorUnsignedByteRefColors[0][dstIndex++] = c3bRefColors[i].z; + } + } else { + dstIndex = initialColorIndex * 4; + for (i = initialColorIndex; i < validVertexCount; i++) { + mirrorUnsignedByteRefColors[0][dstIndex++] = c3bRefColors[i].x; + mirrorUnsignedByteRefColors[0][dstIndex++] = c3bRefColors[i].y; + mirrorUnsignedByteRefColors[0][dstIndex++] = c3bRefColors[i].z; + mirrorUnsignedByteRefColors[0][dstIndex++] = (byte)255; + } + } + break; + case C4UB: + if (c4bRefColors == null) { + vertexType &= ~C4UB; + return; + } + vertexType |=C4UB ; + if ((mirrorColorAllocated & CUB) == 0) { + mirrorUnsignedByteRefColors[0] = new byte[vertexCount << 2]; + mirrorColorAllocated |= CUB; + } + + dstIndex = initialColorIndex * 4; + for (i = initialColorIndex; i < validVertexCount; i++) { + mirrorUnsignedByteRefColors[0][dstIndex++] = c4bRefColors[i].x; + mirrorUnsignedByteRefColors[0][dstIndex++] = c4bRefColors[i].y; + mirrorUnsignedByteRefColors[0][dstIndex++] = c4bRefColors[i].z; + mirrorUnsignedByteRefColors[0][dstIndex++] = c4bRefColors[i].w; + } + break; + default: + break; + } + } + else { //USE_NIO_BUFFER is set + if( colorRefBuffer == null) { + if (c4fAllocated == 0 && !force && + (vertexType & COLOR_DEFINED) == CF) { + mirrorFloatRefColors[0] = null; + mirrorColorAllocated &= ~CF; + } + vertexType &= ~CF; + + if (c4fAllocated == 0 && !force && + ((vertexType & COLOR_DEFINED) == CUB) ) { + mirrorUnsignedByteRefColors[0] = null; + mirrorColorAllocated &= ~CUB; + } + vertexType &= ~CUB; + return; + + } else if( floatBufferRefColors != null) { + vertexType |= CF; + vertexType &= ~CUB; + if (c4fAllocated == 0 && !force) { + // TODO: make suren mirrorFloatRefColors[0] is set right + mirrorFloatRefColors[0] = null; + mirrorColorAllocated &= ~CF; + } + else { + if ((mirrorColorAllocated & CF) == 0) { + mirrorFloatRefColors[0] = new float[4 * vertexCount]; + mirrorColorAllocated |= CF; + } + floatBufferRefColors.rewind(); + if ((vertexFormat & GeometryArray.WITH_ALPHA) == 0) { + srcIndex = initialColorIndex * 3; + dstIndex = initialColorIndex * 4; + floatBufferRefColors.position(srcIndex); + + for (i = initialColorIndex; i < validVertexCount; i++) { + floatBufferRefColors.get(mirrorFloatRefColors[0], dstIndex, 3); + mirrorFloatRefColors[0][dstIndex+3] = 1.0f; + dstIndex += 4; + } + } + else { + + srcIndex = initialColorIndex * 4; + dstIndex = initialColorIndex * 4; + floatBufferRefColors.position(srcIndex); + for (i = initialColorIndex; i < validVertexCount; i++) { + floatBufferRefColors.get(mirrorFloatRefColors[0], dstIndex, 4); + dstIndex+= 4; + } + } + } + } else if ( byteBufferRefColors != null) { + vertexType |= CUB; + vertexType &= ~CF; + if (c4fAllocated == 0 && !force) { + // TODO: make suren mirrorUnsignedByteRefColors[0] is set right + mirrorUnsignedByteRefColors[0] = null; + mirrorColorAllocated &= ~CUB;; + } + else { + if ((mirrorColorAllocated & CUB) == 0) { + mirrorUnsignedByteRefColors[0] = new byte[4 * vertexCount]; + mirrorColorAllocated |= CUB; + } + + byteBufferRefColors.rewind(); + if ((vertexFormat & GeometryArray.WITH_ALPHA) == 0) { + srcIndex = initialColorIndex * 3; + dstIndex = initialColorIndex * 4; + byteBufferRefColors.position(srcIndex); + for (i = initialColorIndex; i < validVertexCount; i++) { + byteBufferRefColors.get(mirrorUnsignedByteRefColors[0], + dstIndex, 3); + mirrorUnsignedByteRefColors[0][dstIndex+3] = (byte)(255.0); + dstIndex += 4; + } + } + else { + srcIndex = initialColorIndex * 4; + dstIndex = initialColorIndex * 4; + byteBufferRefColors.position(srcIndex); + for (i = initialColorIndex; i < validVertexCount; i++) { + byteBufferRefColors.get(mirrorUnsignedByteRefColors[0], dstIndex, 4); + dstIndex+= 4; + } + } + } // end of else + }//end of else if ( byteBufferRefColors != null) + }//end of NIO BUFFER case + + colorChanged = 0xffff; + } + + + void setupMirrorNormalPointer(int ntype) { + int i, index; + + switch (ntype) { + case NF: + if (floatRefNormals == null) { + if ((vertexType & NORMAL_DEFINED) == NF) { + vertexType &= ~NF; + mirrorFloatRefNormals = null; + mirrorNormalAllocated = false; + } + } + else { + vertexType |= NF; + mirrorFloatRefNormals = floatRefNormals; + mirrorNormalAllocated = false; + } + break; + case N3F: + if (v3fRefNormals == null) { + if ((vertexType & NORMAL_DEFINED) == N3F) { + vertexType &= ~N3F; + } + return; + } + else { + vertexType |= N3F; + } + if (!mirrorNormalAllocated) { + mirrorFloatRefNormals = new float[vertexCount * 3]; + mirrorNormalAllocated = true; + } + + index = initialNormalIndex * 3; + for (i = initialNormalIndex; i < validVertexCount; i++) { + mirrorFloatRefNormals[index++] = v3fRefNormals[i].x; + mirrorFloatRefNormals[index++] = v3fRefNormals[i].y; + mirrorFloatRefNormals[index++] = v3fRefNormals[i].z; + } + break; + default: + break; } + } + + void setupMirrorTexCoordPointer(int type) { + for (int i = 0; i < texCoordSetCount; i++) { + setupMirrorTexCoordPointer(i, type); + } + } + + void setupMirrorTexCoordPointer(int texCoordSet, int type) { + int i, index; + + if (mirrorRefTexCoords == null) + mirrorRefTexCoords = new Object[texCoordSetCount]; + + switch (type) { + case TF: + if (refTexCoords[texCoordSet] == null) { + if ((vertexType & TEXCOORD_DEFINED) == TF) { + vertexType &= ~TF; + mirrorRefTexCoords[texCoordSet] = null; + mirrorTexCoordAllocated = false; + } + } + else { + vertexType |= TF; + mirrorRefTexCoords[texCoordSet] = refTexCoords[texCoordSet]; + mirrorTexCoordAllocated = false; + } + break; + case T2F: + t2fRefTexCoords = (TexCoord2f[])refTexCoords[texCoordSet]; + + if (t2fRefTexCoords == null) { + if ((vertexType & TEXCOORD_DEFINED) == T2F) { + vertexType &= ~T2F; + } + return; + } + else { + vertexType |= T2F; + } + + mirrorFloatRefTexCoords = (float[])mirrorRefTexCoords[texCoordSet]; + if (mirrorFloatRefTexCoords != null) { + if (mirrorFloatRefTexCoords.length < (vertexCount * 2)) + mirrorRefTexCoords[texCoordSet] = + mirrorFloatRefTexCoords = new float[vertexCount * 2]; + } + else { + mirrorRefTexCoords[texCoordSet] = + mirrorFloatRefTexCoords = new float[vertexCount * 2]; + } + + index = initialTexCoordIndex[texCoordSet] * 2; + for (i = initialTexCoordIndex[texCoordSet]; i < validVertexCount; i++) { + mirrorFloatRefTexCoords[index++] = t2fRefTexCoords[i].x; + mirrorFloatRefTexCoords[index++] = t2fRefTexCoords[i].y; + } + mirrorTexCoordAllocated = true; + break; + case T3F: + + t3fRefTexCoords = (TexCoord3f[])refTexCoords[texCoordSet]; + if (t3fRefTexCoords == null) { + if ((vertexType & TEXCOORD_DEFINED) == T3F) { + vertexType &= ~T3F; + } + return; + } + else { + vertexType |= T3F; + } + + mirrorFloatRefTexCoords = (float[])mirrorRefTexCoords[texCoordSet]; + if (mirrorFloatRefTexCoords != null) { + if (mirrorFloatRefTexCoords.length < (vertexCount * 3)) + mirrorRefTexCoords[texCoordSet] = + mirrorFloatRefTexCoords = new float[vertexCount * 3]; + } + else { + mirrorRefTexCoords[texCoordSet] = + mirrorFloatRefTexCoords = new float[vertexCount * 3]; + } + + index = initialTexCoordIndex[texCoordSet] * 3; + for (i = initialTexCoordIndex[texCoordSet]; i < validVertexCount; i++) { + mirrorFloatRefTexCoords[index++] = t3fRefTexCoords[i].x; + mirrorFloatRefTexCoords[index++] = t3fRefTexCoords[i].y; + mirrorFloatRefTexCoords[index++] = t3fRefTexCoords[i].z; + } + mirrorTexCoordAllocated = true; + break; + + default: + break; + } + } + + + void createGeometryArrayData(int vertexCount, int vertexFormat) + { + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + createGeometryArrayData(vertexCount, vertexFormat, 1, + defaultTexCoordSetMap); + } else { + createGeometryArrayData(vertexCount, vertexFormat, 0, null); + } + } + + void createGeometryArrayData(int vertexCount, int vertexFormat, + int texCoordSetCount, int[] texCoordSetMap) + { + this.vertexFormat = vertexFormat; + this.vertexCount = vertexCount; + this.validVertexCount = vertexCount; + this.texCoordSetCount = texCoordSetCount; + this.texCoordSetMap = texCoordSetMap; + this.stride = this.stride(); + this.texCoordSetMapOffset = this.texCoordSetMapOffset(); + this.textureOffset = 0; + this.colorOffset = this.colorOffset(); + this.normalOffset = this.normalOffset(); + this.coordinateOffset = this.coordinateOffset(); + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + this.vertexData = new float[this.vertexCount * this.stride]; + } + else { // By reference geometry + this.vertexData = null; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + this.refTexCoords = new Object[texCoordSetCount]; // keep J3DBufferImp object in nio buffer case + if((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0 ) + this.refTexCoordsBuffer = new Object[texCoordSetCount]; // keep J3DBuffer object + } + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + this.initialTexCoordIndex = new int[texCoordSetCount]; + } + noAlpha = ((vertexFormat & GeometryArray.WITH_ALPHA) == 0); + lastAlpha[0] = 1.0f; + + } + + // used for GeometryArrays by Copy or interleaved + native void execute(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, + boolean useAlpha, + boolean multiScreen, + boolean ignoreVertexColors, + int startVIndex, int vcount, int vformat, + int texCoordSetCount, int texCoordSetMap[], + int texCoordSetMapLen, + int[] texCoordSetOffset, + int numActiveTexUnitState, + int[] texUnitStateMap, + float[] varray, float[] cdata, int texUnitIndex, int cdirty); + + + + + // used by GeometryArray by Reference with java arrays + native void executeVA(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, + boolean multiScreen, + boolean ignoreVertexColors, + int vcount, + int vformat, + int vdefined, + int coordIndex, float[] vfcoords, double[] vdcoords, + int colorIndex, float[] cfdata, byte[] cbdata, + int normalIndex, float[] ndata, + int pass, int texcoordmaplength, + int[] texcoordoffset, + int numActiveTexUnitState, int[] texunitstatemap, + int[] texIndex, int texstride, Object[] texCoords, + int cdirty); + + + + // used by GeometryArray by Reference with NIO buffer + native void executeVABuffer(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, + boolean multiScreen, + boolean ignoreVertexColors, + int vcount, + int vformat, + int vdefined, + int coordIndex, + Object vcoords, + int colorIndex, + Object cdataBuffer, + float[] cfdata, byte[] cbdata, + int normalIndex, Object ndata, + int pass, int texcoordmaplength, + int[] texcoordoffset, + int numActiveTexUnitState, int[] texunitstatemap, + int[] texIndex, int texstride, Object[] texCoords, + int cdirty); + + // used by GeometryArray by Reference in interleaved format with NIO buffer + native void executeInterleavedBuffer(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, + boolean useAlpha, + boolean multiScreen, + boolean ignoreVertexColors, + int startVIndex, int vcount, int vformat, + int texCoordSetCount, int texCoordSetMap[], + int texCoordSetMapLen, + int[] texCoordSetOffset, + int numActiveTexUnitState, + int[] texUnitStateMap, + Object varray, float[] cdata, int texUnitIndex, int cdirty); + + + + native void setVertexFormat(int vformat, boolean useAlpha, + boolean ignoreVertexColors, long ctx); + native void disableGlobalAlpha(long ctx, int vformat, + boolean useAlpha, + boolean ignoreVertexColors); + + void setVertexFormat(boolean useAlpha, boolean ignoreVC, long ctx) { + setVertexFormat(vertexFormat, useAlpha, ignoreVC, ctx); + } + + void disableGlobalAlpha(long ctx, boolean useAlpha, boolean ignoreVC) { + // If global alpha was turned on, then disable it + disableGlobalAlpha(ctx, vertexFormat, useAlpha, ignoreVC); + } + + + float[] updateAlphaInFloatRefColors(Canvas3D cv, int screen, float alpha) { + + //System.out.println("updateAlphaInFloatRefColors screen = " + screen + + // " alpha " + alpha ); + + // no need to update alpha values if canvas supports global alpha + if (cv.supportGlobalAlpha()) { + cv.setGlobalAlpha(cv.ctx, alpha); + return mirrorFloatRefColors[0]; + } + + // update alpha only if vertex format includes alpha + if (((vertexFormat | c4fAllocated) & GeometryArray.WITH_ALPHA) == 0) + return mirrorFloatRefColors[0]; + + // if alpha is smaller than EPSILON, set it to EPSILON, so that + // even if alpha is equal to 0, we will not completely lose + // the original alpha value + if (alpha <= EPSILON) { + alpha = (float)EPSILON; + } + + // allocate an entry for the last alpha of the screen if needed + if (lastAlpha == null) { + lastAlpha = new float[screen + 1]; + lastAlpha[screen] = 1.0f; + } else if (lastAlpha.length <= screen) { + float[] la = new float[screen + 1]; + for (int i = 0; i < lastAlpha.length; i++) { + la[i] = lastAlpha[i]; + } + lastAlpha = la; + lastAlpha[screen] = 1.0f; + } + + //System.out.println("updateAlphaInFloatRefColors screen is " + screen + // + " mirrorFloatRefColors.length " + + // mirrorFloatRefColors.length); + + // allocate a copy of the color data for the screen if needed. + // this piece of code is mainly for multi-screens case + if (mirrorFloatRefColors.length <= screen) { + float[][] cfData = new float[screen + 1][]; + float[] cdata; + int refScreen = -1; + + for (int i = 0; i < mirrorFloatRefColors.length; i++) { + cfData[i] = mirrorFloatRefColors[i]; + if (Math.abs(lastAlpha[i] - alpha) < EPSILON) { + refScreen = i; + } + } + cdata = cfData[screen] = new float[4 * vertexCount]; + + // copy the data from a reference screen which has the closest + // alpha values + if (refScreen >= 0) { + System.arraycopy(cfData[refScreen], 0, cdata, 0, + 4 * vertexCount); + lastAlpha[screen] = lastAlpha[refScreen]; + } else { + float m = alpha / lastAlpha[0]; + float[] sdata = cfData[0]; + + int j = initialColorIndex * 4; + for (int i = initialColorIndex; i < validVertexCount; i++) { + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++] * m; + } + lastAlpha[screen] = alpha; + } + mirrorFloatRefColors = cfData; + + // reset the colorChanged bit + colorChanged &= ~(1 << screen); + dirtyFlag |= COLOR_CHANGED; + + return cdata; + } + + /* + System.out.println("updateAlphaInFloatRefColors ** : lastAlpha[screen] " + + lastAlpha[screen]); + + System.out.println("((colorChanged & (1<<screen)) == 0) " + + ((colorChanged & (1<<screen)) == 0)); + */ + + if ((colorChanged & (1<<screen)) == 0) { + // color data is not modified + if (Math.abs(lastAlpha[screen] - alpha) < EPSILON) { + // and if alpha is the same as the last one, + // just return the data + //System.out.println("updateAlphaInFloatRefColors 0 : alpha is the same as the last one " + alpha); + + return mirrorFloatRefColors[screen]; + } else { + + // if alpha is different, update the alpha values + //System.out.println("updateAlphaInFloatRefColors 1 : alpha is different, update the alpha values " + alpha); + + float m = alpha / lastAlpha[screen]; + + float[] cdata = mirrorFloatRefColors[screen]; + + // We've to traverse the whole due to BugId : 4676483 + for (int i = 0, j = 0; i < vertexCount; i++, j+=4) { + cdata[j+3] = cdata[j+3] * m; + } + } + } else { + // color data is modified + if (screen == 0) { + + // just update alpha values since screen 0 data is + // already updated in setupMirrorColorPointer + + //System.out.println("updateAlphaInFloatRefColors 2 : just update alpha = " + alpha); + + float[] cdata = mirrorFloatRefColors[screen]; + + + // This part is also incorrect due to BugId : 4676483 + // But traversing the whole array doesn't help either, as there + // isn't a mechanism to indicate the the alpha component has + // not changed by user. + int j = initialColorIndex * 4; + for (int i = initialColorIndex; i < validVertexCount; i++, j+=4) { + cdata[j+3] = cdata[j+3] * alpha; + } + } else { + // update color values from screen 0 data + //System.out.println("updateAlphaInFloatRefColors 3 : update color values from screen 0 data " + alpha); + + float m; + + if ((colorChanged & 1) == 0) { + // alpha is up to date in screen 0 + m = alpha / lastAlpha[0]; + } else { + m = alpha; + } + + float[] sdata = mirrorFloatRefColors[0]; + float[] cdata = mirrorFloatRefColors[screen]; + + int j = initialColorIndex * 4; + for (int i = initialColorIndex; i < validVertexCount; i++) { + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++] * m; + } + } + } + + lastAlpha[screen] = alpha; + colorChanged &= ~(1 << screen); + dirtyFlag |= COLOR_CHANGED; + return mirrorFloatRefColors[screen]; + } + + + byte[] updateAlphaInByteRefColors(Canvas3D cv, int screen, float alpha) { + + /* + System.out.println("updateAlphaInByteRefColors screen = " + screen + + " alpha " + alpha ); + */ + + // no need to update alpha values if canvas supports global alpha + + if (cv.supportGlobalAlpha()) { + cv.setGlobalAlpha(cv.ctx, alpha); + return mirrorUnsignedByteRefColors[0]; + } + + // update alpha only if vertex format includes alpha + if (((vertexFormat | c4fAllocated) & GeometryArray.WITH_ALPHA) == 0) + return mirrorUnsignedByteRefColors[0]; + + // if alpha is smaller than EPSILON, set it to EPSILON, so that + // even if alpha is equal to 0, we will not completely lose + // the original alpha value + if (alpha <= EPSILON) { + alpha = (float)EPSILON; + } + + // allocate an entry for the last alpha of the screen if needed + if (lastAlpha == null) { + lastAlpha = new float[screen + 1]; + lastAlpha[screen] = -1.0f; + } else if (lastAlpha.length <= screen) { + float[] la = new float[screen + 1]; + for (int i = 0; i < lastAlpha.length; i++) { + la[i] = lastAlpha[i]; + } + lastAlpha = la; + lastAlpha[screen] = -1.0f; + } + + // allocate a copy of the color data for the screen if needed. + // this piece of code is mainly for multi-screens case + if (mirrorUnsignedByteRefColors.length <= screen) { + byte[][] cfData = new byte[screen + 1][]; + byte[] cdata; + int refScreen = -1; + for (int i = 0; i < mirrorUnsignedByteRefColors.length; i++) { + cfData[i] = mirrorUnsignedByteRefColors[i]; + if (Math.abs(lastAlpha[i] - alpha) < EPSILON) { + refScreen = i; + } + } + cdata = cfData[screen] = new byte[4 * vertexCount]; + + // copy the data from a reference screen which has the closest + // alpha values + if (refScreen >= 0) { + System.arraycopy(cfData[refScreen], 0, cdata, 0, + 4 * vertexCount); + lastAlpha[screen] = lastAlpha[refScreen]; + } else { + float m = alpha / lastAlpha[0]; + byte[] sdata = cfData[0]; + + int j = initialColorIndex * 4; + for (int i = initialColorIndex; i < validVertexCount; i++) { + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++]; + cdata[j] = (byte)(((int)sdata[j++]& 0xff) * m); + } + lastAlpha[screen] = alpha; + } + mirrorUnsignedByteRefColors = cfData; + colorChanged &= ~(1 << screen); + dirtyFlag |= COLOR_CHANGED; + return cdata; + } + + /* + System.out.println("updateAlphaInByteRefColors ## : lastAlpha[screen] " + + lastAlpha[screen]); + + System.out.println("((colorChanged & (1<<screen)) == 0) " + + ((colorChanged & (1<<screen)) == 0)); + */ + + if ((colorChanged & (1<<screen)) == 0) { + // color data is not modified + if (Math.abs(lastAlpha[screen] - alpha) < EPSILON) { + // and if alpha is the same as the last one, + // just return the data + //System.out.println("updateAlphaInByteRefColors 0 : alpha is the same as the last one " + alpha); + + return mirrorUnsignedByteRefColors[screen]; + } else { + // if alpha is different, update the alpha values + + //System.out.println("updateAlphaInByteRefColors 1 : alpha is different, update the alpha values " + alpha); + + float m = alpha / lastAlpha[screen]; + + byte[] cdata = mirrorUnsignedByteRefColors[screen]; + + // We've to traverse the whole due to BugId : 4676483 + for (int i = 0, j = 0; i < vertexCount; i++, j+=4) { + cdata[j+3] = (byte)( ((int)cdata[j+3] & 0xff) * m); + } + } + } else { + // color data is modified + if (screen == 0) { + //System.out.println("updateAlphaInByteRefColors 2 : just update alpha =" + alpha); + + // just update alpha values since screen 0 data is + // already updated in setupMirrorColorPointer + + byte[] cdata = mirrorUnsignedByteRefColors[screen]; + + // This part is also incorrect due to BugId : 4676483 + // But traversing the whole array doesn't help either, as there + // isn't a mechanism to indicate the the alpha component has + // not changed by user. + int j = initialColorIndex * 4; + for (int i = initialColorIndex; i < validVertexCount; i++, j+=4) { + cdata[j+3] = (byte)(((int)cdata[j+3] & 0xff) * alpha); + } + } else { + // update color values from screen 0 data + float m; + + //System.out.println("updateAlphaInByteRefColors 3 : update color values from screen 0 data " + alpha); + + if ((colorChanged & 1) == 0) { + // alpha is up to date in screen 0 + m = alpha / lastAlpha[0]; + } else { + m = alpha; + } + byte[] sdata = mirrorUnsignedByteRefColors[0]; + byte[] cdata = mirrorUnsignedByteRefColors[screen]; + + int j = initialColorIndex * 4; + for (int i = initialColorIndex; i < validVertexCount; i++) { + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++]; + cdata[j] = sdata[j++]; + cdata[j] = (byte)(((int)sdata[j++]& 0xff) * m); + } + } + } + + lastAlpha[screen] = alpha; + colorChanged &= ~(1 << screen); + dirtyFlag |= COLOR_CHANGED; + return mirrorUnsignedByteRefColors[screen]; + } + + + Object[] updateAlphaInVertexData(Canvas3D cv, int screen, float alpha) { + + Object[] retVal = new Object[2]; + retVal[0] = Boolean.FALSE; + + // no need to update alpha values if canvas supports global alpha + if (cv.supportGlobalAlpha()) { + cv.setGlobalAlpha(cv.ctx, alpha); + retVal[1] = vertexData; + return retVal; + } + + // update alpha only if vertex format includes alpha + if ((vertexFormat & GeometryArray.COLOR) == 0) { + retVal[1] = vertexData; + return retVal; + } + + // if alpha is smaller than EPSILON, set it to EPSILON, so that + // even if alpha is equal to 0, we will not completely lose + // the original alpha value + if (alpha <= EPSILON) { + alpha = (float)EPSILON; + } + retVal[0] = Boolean.TRUE; + + // allocate an entry for the last alpha of the screen if needed + if (lastAlpha == null) { + lastAlpha = new float[screen + 1]; + lastAlpha[screen] = 1.0f; + } else if (lastAlpha.length <= screen) { + float[] la = new float[screen + 1]; + for (int i = 0; i < lastAlpha.length; i++) { + la[i] = lastAlpha[i]; + } + lastAlpha = la; + lastAlpha[screen] = 1.0f; + } + + // allocate a copy of the vertex data for the screen if needed. + // this piece of code is mainly for multi-screens case + // TODO: this might not too much data for just to update alpha + if (mvertexData == null || mvertexData.length <= screen) { + + float[][] cfData = new float[screen + 1][]; + float[] cdata; + int refScreen = -1; + + if (mvertexData != null) { + for (int i = 0; i < mvertexData.length; i++) { + cfData[i] = mvertexData[i]; + if (Math.abs(lastAlpha[i] - alpha) < EPSILON) { + refScreen = i; + } + } + } + + if (cfData[0] == null) { + cfData[screen] = vertexData; + } + + if (screen > 0) + cfData[screen] = new float[stride * vertexCount]; + + cdata = cfData[screen]; + + // copy the data from a reference screen which has the closest + // alpha values + if (refScreen >= 0) { + System.arraycopy(cfData[refScreen], 0, cdata, 0, + stride * vertexCount); + lastAlpha[screen] = lastAlpha[refScreen]; + } else { + float m = alpha / lastAlpha[0]; + float[] sdata = cfData[0]; + + /* + // screen 0 data is always up-to-date + if (screen > 0) { + System.arraycopy(cfData[0], 0, cdata, 0, + stride * vertexCount); + } + */ + + for (int i = 0, j = colorOffset; i < vertexCount; + i++, j+=stride) { + cdata[j+3] = sdata[j+3] * m; + } + lastAlpha[screen] = alpha; + } + mvertexData = cfData; + dirtyFlag |= COLOR_CHANGED; + // reset the colorChanged bit + colorChanged &= ~(1 << screen); + retVal[1] = cdata; + return retVal; + } + + if ((colorChanged & (1<<screen)) == 0) { + // color data is not modified + if (Math.abs(lastAlpha[screen] - alpha) < EPSILON) { + // and if alpha is the same as the last one, + // just return the data + retVal[1] = mvertexData[screen]; + return retVal; + } else { + // if alpha is different, update the alpha values + float m = alpha / lastAlpha[screen]; + + float[] cdata = mvertexData[screen]; + for (int i = 0, j = colorOffset; i < vertexCount; + i++, j+=stride) { + cdata[j+3] *= m; + } + } + } else { + // color data is modified + if (screen == 0) { + // just update alpha values since screen 0 data is + // already updated in setupMirrorColorPointer + + float[] cdata = mvertexData[screen]; + double m = alpha / lastAlpha[0]; + + for (int i = 0, j = colorOffset; i < vertexCount; + i++, j+=stride) { + cdata[j+3] *= m; + } + } else { + // update color values from screen 0 data + + float m = alpha / lastAlpha[0]; + float[] sdata = mvertexData[0]; + float[] cdata = mvertexData[screen]; + + for (int i = 0, j = colorOffset; i < vertexCount; + i++, j+=stride) { + System.arraycopy(sdata, j, cdata, j, 3); + cdata[j+3] = sdata[j+3] * m; + } + } + } + + lastAlpha[screen] = alpha; + colorChanged &= ~(1 << screen); + dirtyFlag |= COLOR_CHANGED; + retVal[1] = mvertexData[screen]; + return retVal; + } + + Object[] updateAlphaInInterLeavedData(Canvas3D cv, int screen, float alpha) { + + Object[] retVal = new Object[2]; + retVal[0] = Boolean.FALSE; + + // no need to update alpha values if canvas supports global alpha + if (cv.supportGlobalAlpha()) { + cv.setGlobalAlpha(cv.ctx, alpha); + retVal[1] = null; + return retVal; + } + + // update alpha only if vertex format includes alpha + if (((vertexFormat | c4fAllocated) & GeometryArray.COLOR) == 0) { + retVal[1] = mirrorInterleavedColorPointer[0]; + return retVal; + } + int coffset = initialColorIndex << 2; // Each color is 4 floats + + // if alpha is smaller than EPSILON, set it to EPSILON, so that + // even if alpha is equal to 0, we will not completely lose + // the original alpha value + if (alpha <= EPSILON) { + alpha = (float)EPSILON; + } + retVal[0] = Boolean.TRUE; + + // allocate an entry for the last alpha of the screen if needed + if (lastAlpha == null) { + lastAlpha = new float[screen + 1]; + lastAlpha[screen] = 1.0f; + } else if (lastAlpha.length <= screen) { + float[] la = new float[screen + 1]; + for (int i = 0; i < lastAlpha.length; i++) { + la[i] = lastAlpha[i]; + } + lastAlpha = la; + lastAlpha[screen] = 1.0f; + } + + // allocate a copy of the vertex data for the screen if needed. + // this piece of code is mainly for multi-screens case + // TODO: this might not too much data for just to update alpha + if (mirrorInterleavedColorPointer.length <= screen) { + + float[][] cfData = new float[screen + 1][]; + float[] cdata; + int refScreen = -1; + + for (int i = 0; i < mirrorInterleavedColorPointer.length; i++) { + cfData[i] = mirrorInterleavedColorPointer[i]; + if (Math.abs(lastAlpha[i] - alpha) < EPSILON) { + refScreen = i; + } + } + + //cdata = cfData[screen] = new float[stride * vertexCount]; + cdata = cfData[screen] = new float[4 * vertexCount]; + + // copy the data from a reference screen which has the closest + // alpha values + if (refScreen >= 0) { + System.arraycopy(cfData[refScreen], 0, cdata, 0, + 4 * vertexCount); + lastAlpha[screen] = lastAlpha[refScreen]; + } else { + float m = alpha / lastAlpha[0]; + float[] sdata = cfData[0]; + + for (int i = coffset; i < coffset + (vertexCount << 2); i+=4) { + cdata[i+3] = sdata[i+3] * m; + } + + lastAlpha[screen] = alpha; + } + mirrorInterleavedColorPointer = cfData; + + // reset the colorChanged bit + colorChanged &= ~(1 << screen); + dirtyFlag |= COLOR_CHANGED; + retVal[1] = cdata; + return retVal; + } + + if ((colorChanged & (1<<screen)) == 0) { + // color data is not modified + if (Math.abs(lastAlpha[screen] - alpha) < EPSILON) { + // and if alpha is the same as the last one, + // just return the data + retVal[1] = mirrorInterleavedColorPointer[screen]; + return retVal; + } else { + + // if alpha is different, update the alpha values + + float m = alpha / lastAlpha[screen]; + + float[] cdata = mirrorInterleavedColorPointer[screen]; + + coffset = initialColorIndex << 2; + for (int i = coffset; i < coffset + (vertexCount << 2); i+=4) { + cdata[i+3] = cdata[i+3] * m; + } + } + } else { + // color data is modified + if (screen == 0) { + + // just update alpha values since screen 0 data is + // already updated in setupMirrorInterleavedColorPointer + + float[] cdata = mirrorInterleavedColorPointer[screen]; + + for (int i = coffset; i < coffset + (vertexCount << 2); i+=4) { + cdata[i+3] = cdata[i+3] * alpha; + } + } else { + // update color values from screen 0 data + + float m; + + if ((colorChanged & 1) == 0) { + // alpha is up to date in screen 0 + m = alpha / lastAlpha[0]; + } else { + m = alpha; + } + + float[] sdata = mirrorInterleavedColorPointer[0]; + float[] cdata = mirrorInterleavedColorPointer[screen]; + + for (int i = coffset; i < coffset + (vertexCount << 2);) { + // System.arraycopy(sdata, i, cdata, i, 3); + cdata[i] = sdata[i++]; + cdata[i] = sdata[i++]; + cdata[i] = sdata[i++]; + cdata[i] = sdata[i++] * m; + } + } + } + + lastAlpha[screen] = alpha; + colorChanged &= ~(1 << screen); + dirtyFlag |= COLOR_CHANGED; + retVal[1] = mirrorInterleavedColorPointer[screen]; + return retVal; + } + + + // pass < 0 implies underlying library supports multiTexture, so + // use the multiTexture extension to send all texture units + // data in one pass + // pass >= 0 implies one pass for one texture unit state + + void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha, + boolean multiScreen, int screen, + boolean ignoreVertexColors, int pass) { + + int cdirty; + boolean useAlpha = false; + Object[] retVal; + + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + float[] vdata; + + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + // update the alpha values + retVal = updateAlphaInVertexData(cv, screen, alpha); + useAlpha = (retVal[0] == Boolean.TRUE); + vdata = (float[])retVal[1]; + + // D3D only + if (alpha != lastScreenAlpha) { + // handle multiple screen case + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + vdata = vertexData; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + // geomLock is get in MasterControl when + // RenderBin render the geometry. So it is safe + // just to set the dirty flag here + dirtyFlag = 0; + } + + execute(cv.ctx, this, geoType, isNonUniformScale, + useAlpha, + multiScreen, + ignoreVertexColors, + initialVertexIndex, + validVertexCount, + ((vertexFormat & GeometryArray.COLOR) != 0)?(vertexFormat|GeometryArray.COLOR_4):vertexFormat, + texCoordSetCount, texCoordSetMap, + (texCoordSetMap == null) ? 0 : texCoordSetMap.length, + texCoordSetMapOffset, + cv.numActiveTexUnit, cv.texUnitStateMap, + vdata, null, + pass, cdirty); + } + + //By reference with java array + else if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + // interleaved data + if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + if(interLeavedVertexData == null) + return; + + float[] cdata = null; + + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + // update the alpha values + retVal = updateAlphaInInterLeavedData(cv, screen, alpha); + useAlpha = (retVal[0] == Boolean.TRUE); + cdata = (float[])retVal[1]; + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + dirtyFlag = 0; + } + + execute(cv.ctx, this, geoType, isNonUniformScale, + useAlpha, + multiScreen, + ignoreVertexColors, + initialVertexIndex, + validVertexCount, + vertexFormat, + texCoordSetCount, texCoordSetMap, + (texCoordSetMap == null) ? 0 : texCoordSetMap.length, + texCoordSetMapOffset, + cv.numActiveTexUnit, cv.texUnitStateMap, + interLeavedVertexData, cdata, + pass, cdirty); + + } // end of interleaved case + + // non interleaved data + else { + + // Check if a vertexformat is set, but the array is null + // if yes, don't draw anything + if ((vertexType == 0) || + ((vertexType & VERTEX_DEFINED) == 0) || + (((vertexFormat & GeometryArray.COLOR) != 0) && + (vertexType & COLOR_DEFINED) == 0) || + (((vertexFormat & GeometryArray.NORMALS) != 0) && + (vertexType & NORMAL_DEFINED) == 0) || + (((vertexFormat& GeometryArray.TEXTURE_COORDINATE) != 0) && + (vertexType & TEXCOORD_DEFINED) == 0)) { + return; + } else { + byte[] cbdata = null; + float[] cfdata = null; + + if ((vertexType & (CF | C3F | C4F )) != 0) { + + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + cfdata = updateAlphaInFloatRefColors(cv, screen, alpha); + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + cfdata = mirrorFloatRefColors[0]; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + + } + dirtyFlag = 0; + } + } // end of color in float format + else if ((vertexType & (CUB| C3UB | C4UB)) != 0) { + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + cbdata = updateAlphaInByteRefColors(cv, screen, alpha); + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + cbdata = mirrorUnsignedByteRefColors[0]; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + dirtyFlag = 0; + } + } // end of color in byte format + else { + cdirty = dirtyFlag; + } + // setup vdefined to passed to native code + int vdefined = 0; + if((vertexType & (PF | P3F)) != 0) + vdefined |= COORD_FLOAT; + if((vertexType & (PD | P3D)) != 0) + vdefined |= COORD_DOUBLE; + if((vertexType & (CF | C3F | C4F)) != 0) + vdefined |= COLOR_FLOAT; + if((vertexType & (CUB| C3UB | C4UB)) != 0) + vdefined |= COLOR_BYTE; + if((vertexType & NORMAL_DEFINED) != 0) + vdefined |= NORMAL_FLOAT; + if((vertexType & TEXCOORD_DEFINED) != 0) + vdefined |= TEXCOORD_FLOAT; + + executeVA(cv.ctx, this, geoType, isNonUniformScale, + multiScreen, + ignoreVertexColors, + validVertexCount, + (vertexFormat | c4fAllocated), + vdefined, + initialCoordIndex, + mirrorFloatRefCoords, mirrorDoubleRefCoords, + initialColorIndex, cfdata, cbdata, + initialNormalIndex, mirrorFloatRefNormals, + pass, + ((texCoordSetMap == null) ? 0:texCoordSetMap.length), + texCoordSetMap, + cv.numActiveTexUnit, + cv.texUnitStateMap, + initialTexCoordIndex,texCoordStride, + mirrorRefTexCoords, cdirty); + }// end of all vertex data being set + }// end of non interleaved case + }// end of by reference with java array + + //By reference with nio buffer + else { + // interleaved data + if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + + if ( interleavedFloatBufferImpl == null) + return; + + float[] cdata = null; + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + // update the alpha values + // TODO: to handle alpha case + retVal = updateAlphaInInterLeavedData(cv, screen, alpha); + useAlpha = (retVal[0] == Boolean.TRUE); + cdata = (float[])retVal[1]; + + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + // TODO: to handle alpha case + cdata = null; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + dirtyFlag = 0; + } + + + executeInterleavedBuffer(cv.ctx, this, geoType, isNonUniformScale, + useAlpha, + multiScreen, + ignoreVertexColors, + initialVertexIndex, + validVertexCount, + vertexFormat, + texCoordSetCount, texCoordSetMap, + (texCoordSetMap == null) ? 0 : texCoordSetMap.length, + texCoordSetMapOffset, + cv.numActiveTexUnit, cv.texUnitStateMap, + interleavedFloatBufferImpl.getBufferAsObject(), cdata, + pass, cdirty); + + } // end of interleaved case + + // non interleaved data + else { + // Check if a vertexformat is set, but the array is null + // if yes, don't draw anything + if ((vertexType == 0) || + ((vertexType & VERTEX_DEFINED) == 0) || + (((vertexFormat & GeometryArray.COLOR) != 0) && + (vertexType & COLOR_DEFINED) == 0) || + (((vertexFormat & GeometryArray.NORMALS) != 0) && + (vertexType & NORMAL_DEFINED) == 0) || + (((vertexFormat& GeometryArray.TEXTURE_COORDINATE) != 0) && + (vertexType & TEXCOORD_DEFINED) == 0)) { + return; + } else { + byte[] cbdata = null; + float[] cfdata = null; + + + if ((vertexType & CF ) != 0) { + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + cfdata = updateAlphaInFloatRefColors(cv, + screen, alpha); + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + // TODO: handle transparency case + //cfdata = null; + cfdata = mirrorFloatRefColors[0]; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + + } + dirtyFlag = 0; + } + } // end of color in float format + else if ((vertexType & CUB) != 0) { + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + cbdata = updateAlphaInByteRefColors( + cv, screen, alpha); + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + // TODO: handle transparency case + //cbdata = null; + cbdata = mirrorUnsignedByteRefColors[0]; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + dirtyFlag = 0; + } + } // end of color in byte format + else { + cdirty = dirtyFlag; + } + + Object vcoord = null, cdataBuffer=null, normal=null; + + int vdefined = 0; + if((vertexType & PF) != 0) { + vdefined |= COORD_FLOAT; + vcoord = floatBufferRefCoords.getBufferAsObject(); + } else if((vertexType & PD ) != 0) { + vdefined |= COORD_DOUBLE; + vcoord = doubleBufferRefCoords.getBufferAsObject(); + } + + if((vertexType & CF ) != 0) { + vdefined |= COLOR_FLOAT; + cdataBuffer = floatBufferRefColors.getBufferAsObject(); + } else if((vertexType & CUB) != 0) { + vdefined |= COLOR_BYTE; + cdataBuffer = byteBufferRefColors.getBufferAsObject(); + } + + if((vertexType & NORMAL_DEFINED) != 0) { + vdefined |= NORMAL_FLOAT; + normal = floatBufferRefNormals.getBufferAsObject(); + } + + if((vertexType & TEXCOORD_DEFINED) != 0) + vdefined |= TEXCOORD_FLOAT; + + executeVABuffer(cv.ctx, this, geoType, isNonUniformScale, + multiScreen, + ignoreVertexColors, + validVertexCount, + (vertexFormat | c4fAllocated), + vdefined, + initialCoordIndex, + vcoord, + initialColorIndex, + cdataBuffer, + cfdata, cbdata, + initialNormalIndex, + normal, + pass, + ((texCoordSetMap == null) ? 0:texCoordSetMap.length), + texCoordSetMap, + cv.numActiveTexUnit, + cv.texUnitStateMap, + initialTexCoordIndex,texCoordStride, + refTexCoords, cdirty); + }// end of all vertex data being set + }// end of non interleaved case + }// end of by reference with nio-buffer case + } + + // used for GeometryArrays + native void buildGA(long ctx, GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, boolean updateAlpha, + float alpha, + boolean ignoreVertexColors, + int startVIndex, + int vcount, int vformat, + int texCoordSetCount, int texCoordSetMap[], + int texCoordSetMapLen, + int[] texCoordSetMapOffset, + double[] xform, double[] nxform, + float[] varray); + + // used to Build Dlist GeometryArray by Reference with java arrays + native void buildGAForByRef(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, boolean updateAlpha, + float alpha, + boolean ignoreVertexColors, + int vcount, + int vformat, + int vdefined, + int coordIndex, float[] vfcoords, double[] vdcoords, + int colorIndex, float[] cfdata, byte[] cbdata, + int normalIndex, float[] ndata, + int texcoordmaplength, + int[] texcoordoffset, + int[] texIndex, int texstride, Object[] texCoords, + double[] xform, double[] nxform); + + + // used to Build Dlist GeometryArray by Reference with java arrays + native void buildGAForBuffer(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, boolean updateAlpha, + float alpha, + boolean ignoreVertexColors, + int vcount, + int vformat, + int vdefined, + int coordIndex, Object vcoords, + int colorIndex, Object cdata, + int normalIndex, Object ndata, + int texcoordmaplength, + int[] texcoordoffset, + int[] texIndex, int texstride, Object[] texCoords, + double[] xform, double[] nxform); + + + + + void buildGA(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha, boolean ignoreVertexColors, + Transform3D xform, Transform3D nxform) { + float[] vdata = null; + + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + vdata = vertexData; + } + else if ((vertexFormat & GeometryArray.INTERLEAVED) != 0 && + ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0)) { + vdata = interLeavedVertexData; + } + if (vdata != null) { + /* + System.out.println("calling native buildGA()"); + System.out.println("geoType = "+geoType+" initialVertexIndex = "+initialVertexIndex+" validVertexCount = "+validVertexCount+" vertexFormat = "+vertexFormat+" vertexData = "+vertexData); + */ + buildGA(cv.ctx, this, geoType, isNonUniformScale, + updateAlpha, alpha, ignoreVertexColors, + initialVertexIndex, + validVertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + (texCoordSetMap == null) ? 0 : texCoordSetMap.length, + texCoordSetMapOffset, + (xform == null) ? null : xform.mat, + (nxform == null) ? null : nxform.mat, + vdata); + } + else { + // Either non-interleaved, by-ref or nio buffer + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + // setup vdefined to passed to native code + int vdefined = 0; + if((vertexType & (PF | P3F)) != 0) + vdefined |= COORD_FLOAT; + if((vertexType & (PD | P3D)) != 0) + vdefined |= COORD_DOUBLE; + if((vertexType & (CF | C3F | C4F)) != 0) + vdefined |= COLOR_FLOAT; + if((vertexType & (CUB| C3UB | C4UB)) != 0) + vdefined |= COLOR_BYTE; + if((vertexType & NORMAL_DEFINED) != 0) + vdefined |= NORMAL_FLOAT; + if((vertexType & TEXCOORD_DEFINED) != 0) + vdefined |= TEXCOORD_FLOAT; + buildGAForByRef(cv.ctx, this, geoType, isNonUniformScale, + updateAlpha, alpha, + ignoreVertexColors, + validVertexCount, + vertexFormat, + vdefined, + initialCoordIndex, + mirrorFloatRefCoords, mirrorDoubleRefCoords, + initialColorIndex, mirrorFloatRefColors[0], mirrorUnsignedByteRefColors[0], + initialNormalIndex, mirrorFloatRefNormals, + ((texCoordSetMap == null) ? 0:texCoordSetMap.length), + texCoordSetMap, + initialTexCoordIndex,texCoordStride, + mirrorRefTexCoords, + (xform == null) ? null : xform.mat, + (nxform == null) ? null : nxform.mat); + } + else { + Object vcoord = null, cdataBuffer=null, normal=null; + + int vdefined = 0; + if((vertexType & PF) != 0) { + vdefined |= COORD_FLOAT; + vcoord = floatBufferRefCoords.getBufferAsObject(); + } else if((vertexType & PD ) != 0) { + vdefined |= COORD_DOUBLE; + vcoord = doubleBufferRefCoords.getBufferAsObject(); + } + + if((vertexType & CF ) != 0) { + vdefined |= COLOR_FLOAT; + cdataBuffer = floatBufferRefColors.getBufferAsObject(); + } else if((vertexType & CUB) != 0) { + vdefined |= COLOR_BYTE; + cdataBuffer = byteBufferRefColors.getBufferAsObject(); + } + + if((vertexType & NORMAL_DEFINED) != 0) { + vdefined |= NORMAL_FLOAT; + normal = floatBufferRefNormals.getBufferAsObject(); + } + + if((vertexType & TEXCOORD_DEFINED) != 0) + vdefined |= TEXCOORD_FLOAT; + buildGAForBuffer(cv.ctx, this, geoType, isNonUniformScale, + updateAlpha, alpha, + ignoreVertexColors, + validVertexCount, + vertexFormat, + vdefined, + initialCoordIndex, + vcoord, + initialColorIndex,cdataBuffer, + initialNormalIndex, normal, + ((texCoordSetMap == null) ? 0:texCoordSetMap.length), + texCoordSetMap, + initialTexCoordIndex,texCoordStride, + refTexCoords, + (xform == null) ? null : xform.mat, + (nxform == null) ? null : nxform.mat); + } + + } + + } + + void unIndexify(IndexedGeometryArrayRetained src) { + if ((src.vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + unIndexifyJavaArray(src); + } + else { + unIndexifyNIOBuffer(src); + } + } + + void unIndexifyJavaArray(IndexedGeometryArrayRetained src) { + int vOffset = 0, srcOffset, tOffset = 0; + int index, colorStride = 0; + float[] vdata = null; + int i; + int start, end; + start = src.initialIndexIndex; + end = src.initialIndexIndex + src.validIndexCount; + // If its either "normal" data or interleaved data then .. + if (((src.vertexFormat & GeometryArray.BY_REFERENCE) == 0) || + ((src.vertexFormat & GeometryArray.INTERLEAVED) != 0)) { + + if ((src.vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + vdata = src.vertexData; + if ((src.vertexFormat & GeometryArray.COLOR) != 0) + colorStride = 4; + } + else if ((src.vertexFormat & GeometryArray.INTERLEAVED) != 0) { + vdata = src.interLeavedVertexData; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + colorStride = 4; + else if ((src.vertexFormat & GeometryArray.COLOR) != 0) + colorStride = 3; + } + + // System.out.println("===> start = "+start+" end = "+end); + for (index= start; index < end; index++) { + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + System.arraycopy(vdata, + src.indexNormal[index]*src.stride + src.normalOffset, + vertexData, vOffset + normalOffset, 3); + } + if (colorStride == 4){ + // System.out.println("===> copying color3"); + System.arraycopy(vdata, + src.indexColor[index]*src.stride + src.colorOffset, + vertexData, vOffset + colorOffset, colorStride); + } else if (colorStride == 3) { + // System.out.println("===> copying color4"); + System.arraycopy(vdata, + src.indexColor[index]*src.stride + src.colorOffset, + vertexData, vOffset + colorOffset, colorStride); + vertexData[vOffset + colorOffset + 3] = 1.0f; + } + + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + int tcOffset = vOffset + textureOffset; + int interleavedOffset = 0; + + for (i = 0; i < texCoordSetCount; + i++, tcOffset += texCoordStride) { + + if ((src.vertexFormat & GeometryArray.INTERLEAVED) != 0) { + interleavedOffset = i * texCoordStride; + } + + System.arraycopy(vdata, + (((int[])src.indexTexCoord[i])[index])*src.stride + src.textureOffset + interleavedOffset, + vertexData, tcOffset, texCoordStride); + } + } + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + // System.out.println("===> copying coords"); + System.arraycopy(vdata, + src.indexCoord[index]*src.stride + + src.coordinateOffset, + vertexData, + vOffset + coordinateOffset, 3); + } + vOffset += stride; + } + + } else { + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + vOffset = normalOffset; + switch ((src.vertexType & NORMAL_DEFINED)) { + case NF: + for (index=start; index < end; index++) { + System.arraycopy(src.floatRefNormals, + src.indexNormal[index]*3, + vertexData, + vOffset, 3); + vOffset += stride; + } + break; + case N3F: + for (index=start; index < end; index++) { + srcOffset = src.indexNormal[index]; + vertexData[vOffset] = src.v3fRefNormals[srcOffset].x; + vertexData[vOffset+1] = src.v3fRefNormals[srcOffset].y; + vertexData[vOffset+2] = src.v3fRefNormals[srcOffset].z; + vOffset += stride; + } + break; + default: + break; + } + } + if ((vertexFormat & GeometryArray.COLOR) != 0){ + vOffset = colorOffset; + int multiplier = 3; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + multiplier = 4; + + switch ((src.vertexType & COLOR_DEFINED)) { + case CF: + for (index=start; index < end; index++) { + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + System.arraycopy(src.floatRefColors, + src.indexColor[index]*multiplier, + vertexData, + vOffset, 4); + } + else { + System.arraycopy(src.floatRefColors, + src.indexColor[index]*multiplier, + vertexData, + vOffset, 3); + vertexData[vOffset+3] = 1.0f; + } + vOffset += stride; + } + break; + case CUB: + for (index=start; index < end; index++) { + srcOffset = src.indexColor[index] * multiplier; + vertexData[vOffset] = (src.byteRefColors[srcOffset] & 0xff) * ByteToFloatScale; + vertexData[vOffset+1] = (src.byteRefColors[srcOffset+1] & 0xff) * ByteToFloatScale; + vertexData[vOffset+2] = (src.byteRefColors[srcOffset+2] & 0xff) * ByteToFloatScale; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + vertexData[vOffset+3] = (src.byteRefColors[srcOffset+3] & 0xff) * ByteToFloatScale; + } + else { + vertexData[vOffset+3] = 1.0f; + } + vOffset += stride; + } + break; + case C3F: + for (index=start; index < end; index++) { + srcOffset = src.indexColor[index]; + vertexData[vOffset] = src.c3fRefColors[srcOffset].x; + vertexData[vOffset+1] = src.c3fRefColors[srcOffset].y; + vertexData[vOffset+2] = src.c3fRefColors[srcOffset].z; + vertexData[vOffset+3] = 1.0f; + vOffset += stride; + } + break; + case C4F: + for (index=start; index < end; index++) { + srcOffset = src.indexColor[index]; + vertexData[vOffset] = src.c4fRefColors[srcOffset].x; + vertexData[vOffset+1] = src.c4fRefColors[srcOffset].y; + vertexData[vOffset+2] = src.c4fRefColors[srcOffset].z; + vertexData[vOffset+3] = src.c4fRefColors[srcOffset].w; + vOffset += stride; + } + break; + case C3UB: + for (index=start; index < end; index++) { + srcOffset = src.indexColor[index]; + vertexData[vOffset] = (src.c3bRefColors[srcOffset].x & 0xff) * ByteToFloatScale; + vertexData[vOffset+1] = (src.c3bRefColors[srcOffset].y & 0xff) * ByteToFloatScale; + vertexData[vOffset+2] = (src.c3bRefColors[srcOffset].z & 0xff) * ByteToFloatScale; + vertexData[vOffset+3] = 1.0f; + vOffset += stride; + } + break; + case C4UB: + for (index=start; index < end; index++) { + srcOffset = src.indexColor[index]; + vertexData[vOffset] = (src.c4bRefColors[srcOffset].x & 0xff) * ByteToFloatScale; + vertexData[vOffset+1] = (src.c4bRefColors[srcOffset].y & 0xff) * ByteToFloatScale; + vertexData[vOffset+2] = (src.c4bRefColors[srcOffset].z & 0xff) * ByteToFloatScale; + vertexData[vOffset+3] = (src.c4bRefColors[srcOffset].w & 0xff) * ByteToFloatScale; + vOffset += stride; + } + break; + default: + break; + } + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + vOffset = textureOffset; + switch ((src.vertexType & TEXCOORD_DEFINED)) { + case TF: + for (index=start; index < end; index++) { + for (i = 0, tOffset = vOffset; + i < texCoordSetCount; i++) { + System.arraycopy(src.refTexCoords[i], + ((int[])src.indexTexCoord[i])[index]*texCoordStride, + vertexData, tOffset, texCoordStride); + tOffset += texCoordStride; + } + vOffset += stride; + } + break; + case T2F: + for (index=start; index < end; index++) { + for (i = 0, tOffset = vOffset; + i < texCoordSetCount; i++) { + srcOffset = ((int[])src.indexTexCoord[i])[index]; + vertexData[tOffset] = + ((TexCoord2f[])src.refTexCoords[i])[srcOffset].x; + vertexData[tOffset+1] = + ((TexCoord2f[])src.refTexCoords[i])[srcOffset].y; + tOffset += texCoordStride; + } + vOffset += stride; + } + break; + case T3F: + for (index=start; index < end; index++) { + for (i = 0, tOffset = vOffset; + i < texCoordSetCount; i++) { + srcOffset = ((int[])src.indexTexCoord[i])[index]; + vertexData[tOffset] = + ((TexCoord3f[])src.refTexCoords[i])[srcOffset].x; + vertexData[tOffset+1] = + ((TexCoord3f[])src.refTexCoords[i])[srcOffset].y; + vertexData[tOffset+2] = + ((TexCoord3f[])src.refTexCoords[i])[srcOffset].z; + tOffset += texCoordStride; + } + vOffset += stride; + } + break; + default: + break; + } + } + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + vOffset = coordinateOffset; + switch ((src.vertexType & VERTEX_DEFINED)) { + case PF: + for (index=start; index < end; index++) { + System.arraycopy(src.floatRefCoords, + src.indexCoord[index]*3, + vertexData, + vOffset, 3); + vOffset += stride; + } + break; + case PD: + for (index=start; index < end; index++) { + srcOffset = src.indexCoord[index] * 3; + vertexData[vOffset] = (float)src.doubleRefCoords[srcOffset]; + vertexData[vOffset+1] = (float)src.doubleRefCoords[srcOffset+1]; + vertexData[vOffset+2] = (float)src.doubleRefCoords[srcOffset+2]; + vOffset += stride; + } + break; + case P3F: + for (index=start; index < end; index++) { + srcOffset = src.indexCoord[index]; + vertexData[vOffset] = src.p3fRefCoords[srcOffset].x; + vertexData[vOffset+1] = src.p3fRefCoords[srcOffset].y; + vertexData[vOffset+2] = src.p3fRefCoords[srcOffset].z; + vOffset += stride; + } + break; + case P3D: + for (index=start; index < end; index++) { + srcOffset = src.indexCoord[index]; + vertexData[vOffset] = (float)src.p3dRefCoords[srcOffset].x; + vertexData[vOffset+1] = (float)src.p3dRefCoords[srcOffset].y; + vertexData[vOffset+2] = (float)src.p3dRefCoords[srcOffset].z; + vOffset += stride; + } + break; + default: + break; + } + } + + } + } + + + void unIndexifyNIOBuffer(IndexedGeometryArrayRetained src) { + int vOffset = 0, srcOffset, tOffset = 0; + int index, colorStride = 0; + float[] vdata = null; + int i; + int start, end; + start = src.initialIndexIndex; + end = src.initialIndexIndex + src.validIndexCount; + // If its interleaved data then .. + if ((src.vertexFormat & GeometryArray.INTERLEAVED) != 0) { + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + colorStride = 4; + else if ((src.vertexFormat & GeometryArray.COLOR) != 0) + colorStride = 3; + + // System.out.println("===> start = "+start+" end = "+end); + for (index= start; index < end; index++) { + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + src.interleavedFloatBufferImpl.position(src.indexNormal[index]*src.stride + src.normalOffset); + src.interleavedFloatBufferImpl.get(vertexData, vOffset + normalOffset, 3); + } + + if (colorStride == 4){ + src.interleavedFloatBufferImpl.position(src.indexColor[index]*src.stride + src.colorOffset); + src.interleavedFloatBufferImpl.get(vertexData, vOffset + colorOffset, colorStride); + } else if (colorStride == 3) { + src.interleavedFloatBufferImpl.position(src.indexColor[index]*src.stride + src.colorOffset); + src.interleavedFloatBufferImpl.get(vertexData, vOffset + colorOffset, colorStride); + vertexData[vOffset + colorOffset + 3] = 1.0f; + } + + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + int tcOffset = vOffset + textureOffset; + for (i = 0; i < texCoordSetCount; + i++, tcOffset += texCoordStride) { + + src.interleavedFloatBufferImpl.position((((int[])src.indexTexCoord[i])[index])*src.stride + + src.textureOffset); + src.interleavedFloatBufferImpl.get(vertexData, tcOffset, texCoordStride); + } + } + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + src.interleavedFloatBufferImpl.position(src.indexCoord[index]*src.stride + src.coordinateOffset ); + src.interleavedFloatBufferImpl.get(vertexData, vOffset + coordinateOffset, 3); + } + vOffset += stride; + } + + } else { + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + vOffset = normalOffset; + if ((src.vertexType & NORMAL_DEFINED) != 0) { + for (index=start; index < end; index++) { + src.floatBufferRefNormals.position(src.indexNormal[index]*3); + src.floatBufferRefNormals.get(vertexData, vOffset, 3); + vOffset += stride; + } + } + } + if ((vertexFormat & GeometryArray.COLOR) != 0){ + vOffset = colorOffset; + int multiplier = 3; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + multiplier = 4; + + switch ((src.vertexType & COLOR_DEFINED)) { + case CF: + for (index=start; index < end; index++) { + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + src.floatBufferRefColors.position(src.indexColor[index]*multiplier); + src.floatBufferRefColors.get(vertexData, vOffset, 4); + } + else { + src.floatBufferRefColors.position(src.indexColor[index]*multiplier); + src.floatBufferRefColors.get(vertexData, vOffset, 3); + vertexData[vOffset+3] = 1.0f; + } + vOffset += stride; + } + break; + case CUB: + for (index=start; index < end; index++) { + srcOffset = src.indexColor[index] * multiplier; + vertexData[vOffset] = (src.byteBufferRefColors.get(srcOffset) & 0xff) * ByteToFloatScale; + vertexData[vOffset+1] = (src.byteBufferRefColors.get(srcOffset+1) & 0xff) * ByteToFloatScale; + vertexData[vOffset+2] = (src.byteBufferRefColors.get(srcOffset+2) & 0xff) * ByteToFloatScale; + + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + vertexData[vOffset+3] = (src.byteBufferRefColors.get(srcOffset+3) & 0xff) * ByteToFloatScale; + } + else { + vertexData[vOffset+3] = 1.0f; + } + vOffset += stride; + } + break; + default: + break; + } + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + vOffset = textureOffset; + FloatBufferWrapper texBuffer; + if ((src.vertexType & TEXCOORD_DEFINED) != 0) { + for (index=start; index < end; index++) { + for (i = 0, tOffset = vOffset; + i < texCoordSetCount; i++) { + texBuffer = (FloatBufferWrapper)(((J3DBuffer) (src.refTexCoordsBuffer[i])).getBufferImpl()); + texBuffer.position(((int[])src.indexTexCoord[i])[index]*texCoordStride); + texBuffer.get(vertexData, tOffset, texCoordStride); + tOffset += texCoordStride; + } + vOffset += stride; + } + } + } + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + vOffset = coordinateOffset; + switch ((src.vertexType & VERTEX_DEFINED)) { + case PF: + for (index=start; index < end; index++) { + src.floatBufferRefCoords.position(src.indexCoord[index]*3); + src.floatBufferRefCoords.get(vertexData, vOffset, 3); + vOffset += stride; + } + break; + case PD: + for (index=start; index < end; index++) { + srcOffset = src.indexCoord[index] * 3; + vertexData[vOffset] = (float)src.doubleBufferRefCoords.get(srcOffset); + vertexData[vOffset+1] = (float)src.doubleBufferRefCoords.get(srcOffset+1); + vertexData[vOffset+2] = (float)src.doubleBufferRefCoords.get(srcOffset+2); + vOffset += stride; + } + break; + default: + break; + } + } + + } + } + + /** + * Returns the vertex stride in numbers of floats as a function + * of the vertexFormat. + * @return the stride in floats for this vertex array + */ + int stride() + { + int stride = 0; + + if((this.vertexFormat & GeometryArray.COORDINATES) != 0) stride += 3; + if((this.vertexFormat & GeometryArray.NORMALS) != 0) stride += 3; + + if ((this.vertexFormat & GeometryArray.COLOR) != 0) { + if ((this.vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + // By copy + stride += 4; + } else { + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) == 0) { + stride += 3; + } + else { + stride += 4; + } + } + } + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + + if ((this.vertexFormat & + GeometryArray.TEXTURE_COORDINATE_2) != 0) { + texCoordStride = 2; + } else if ((this.vertexFormat & + GeometryArray.TEXTURE_COORDINATE_3) != 0) { + texCoordStride = 3; + } else if ((this.vertexFormat & + GeometryArray.TEXTURE_COORDINATE_4) != 0) { + texCoordStride = 4; + } + + stride += texCoordStride * texCoordSetCount; + } + + return stride; + } + + int[] texCoordSetMapOffset() + { + if (texCoordSetMap == null) + return null; + + texCoordSetMapOffset = new int[texCoordSetMap.length]; + for (int i = 0; i < texCoordSetMap.length; i++) { + if (texCoordSetMap[i] == -1) { + texCoordSetMapOffset[i] = -1; + } else { + texCoordSetMapOffset[i] = texCoordSetMap[i] * texCoordStride; + } + } + return texCoordSetMapOffset; + } + + /** + * Returns the offset in number of floats from the start of a vertex to + * the per-vertex color data. + * color data always follows texture data + * @param vertexFormat the vertex format for this array + * @return the offset in floats vertex start to the color data + */ + int colorOffset() + { + int offset = textureOffset; + + if((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) + offset += 2 * texCoordSetCount; + else if((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) + offset += 3 * texCoordSetCount; + else if((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) + offset += 4 * texCoordSetCount; + + return offset; + } + + /** + * Returns the offset in number of floats from the start of a vertex to + * the per-vertex normal data. + * normal data always follows color data + * @return the offset in floats from the start of a vertex to the normal + */ + int normalOffset() + { + int offset = colorOffset; + + if ((this.vertexFormat & GeometryArray.COLOR) != 0) { + if ((this.vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + offset += 4; + } else { + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) == 0) { + offset += 3; + } + else { + offset += 4; + } + } + } + return offset; + } + + /** + * Returns the offset in number of floats from the start of a vertex to + * the per vertex coordinate data. + * @return the offset in floats vertex start to the coordinate data + */ + int coordinateOffset() + { + int offset = normalOffset; + + if ((this.vertexFormat & GeometryArray.NORMALS) != 0) offset += 3; + return offset; + } + + /** + * Returns number of vertices in the GeometryArray + * @return vertexCount number of vertices in the GeometryArray + */ + int getVertexCount(){ + return vertexCount; + } + + /** + * Returns vertexFormat in the GeometryArray + * @return vertexFormat format of vertices in the GeometryArray + */ + int getVertexFormat(){ + return vertexFormat; + } + + + + void sendDataChangedMessage(boolean coordinatesChanged) { + J3dMessage[] m; + int i, j, k, index, numShapeMessages, numMorphMessages; + ArrayList shapeList; + Shape3DRetained s; + ArrayList morphList; + MorphRetained morph; + + synchronized(liveStateLock) { + if (source != null && source.isLive()) { + // System.out.println("In GeometryArrayRetained - "); + + // Send a message to renderBin to rebuild the display list or + // process the vertex array accordingly + // TODO: Should I send one per universe, isn't display list + // shared by all context/universes? + int threads = J3dThread.UPDATE_RENDER; + // If the geometry type is Indexed then we need to clone the geometry + // We also need to update the cachedChangedFrequent flag + threads |= J3dThread.UPDATE_RENDERING_ATTRIBUTES; + + synchronized (universeList) { + numShapeMessages = universeList.size(); + m = new J3dMessage[numShapeMessages]; + + k = 0; + + for (i = 0; i < numShapeMessages; i++, k++) { + gaList.clear(); + + shapeList = (ArrayList)userLists.get(i); + for (j=0; j<shapeList.size(); j++) { + s = (Shape3DRetained)shapeList.get(j); + LeafRetained src = (LeafRetained)s.sourceNode; + // Should only need to update distinct localBounds. + if (coordinatesChanged && src.boundsAutoCompute) { + src.boundsDirty = true; + } + } + + for (j=0; j<shapeList.size(); j++) { + s = (Shape3DRetained)shapeList.get(j); + LeafRetained src = (LeafRetained)s.sourceNode; + if (src.boundsDirty) { + // update combine bounds of mirrorShape3Ds. So we need to + // use its bounds and not localBounds. + // bounds is actually a reference to + // mirrorShape3D.source.localBounds. + src.updateBounds(); + src.boundsDirty = false; + } + gaList.add(Shape3DRetained.getGeomAtom(s)); + } + + m[k] = VirtualUniverse.mc.getMessage(); + + m[k].type = J3dMessage.GEOMETRY_CHANGED; + // Who to send this message to ? + m[k].threads = threads; + m[k].args[0] = gaList.toArray(); + m[k].args[1] = this; + m[k].args[2]= null; + m[k].args[3] = new Integer(changedFrequent); + m[k].universe=(VirtualUniverse)universeList.get(i); + } + VirtualUniverse.mc.processMessage(m); + } + + if (morphUniverseList != null) { + synchronized (morphUniverseList) { + numMorphMessages = morphUniverseList.size(); + + // take care of morph that is referencing this geometry + if (numMorphMessages > 0) { + synchronized (morphUniverseList) { + for (i = 0; i < numMorphMessages; i++, k++) { + morphList = (ArrayList)morphUserLists.get(i); + for (j=0; j<morphList.size(); j++) { + morph = (MorphRetained)morphList.get(j); + morph.updateMorphedGeometryArray(this, coordinatesChanged); + } + } + } + } + } + } + } + } + + } + /** + * Sets the coordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param coordinate an array of 3 values containing the new coordinate + */ + void setCoordinate(int index, float coordinate[]) { + int offset = this.stride * index + coordinateOffset; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + this.vertexData[offset] = coordinate[0]; + this.vertexData[offset+1]= coordinate[1]; + this.vertexData[offset+2]= coordinate[2]; + + geomLock.unLock(); + + if (inUpdater || (source == null)) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + // Compute geo's bounds + processCoordsChanged(false); + sendDataChangedMessage(true); + + } + + /** + * Sets the coordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param coordinate an array of 3 values containing the new coordinate + */ + void setCoordinate(int index, double coordinate[]) { + int offset = this.stride * index + coordinateOffset; + + + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + this.vertexData[offset] = (float)coordinate[0]; + this.vertexData[offset+1]= (float)coordinate[1]; + this.vertexData[offset+2]= (float)coordinate[2]; + geomLock.unLock(); + + if (inUpdater || (source == null)) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + // Compute geo's bounds + processCoordsChanged(false); + sendDataChangedMessage(true); + } + + /** + * Sets the coordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param coordinate a vector containing the new coordinate + */ + void setCoordinate(int index, Point3f coordinate) { + int offset = this.stride * index + coordinateOffset; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + this.vertexData[offset] = coordinate.x; + this.vertexData[offset+1]= coordinate.y; + this.vertexData[offset+2]= coordinate.z; + + geomLock.unLock(); + + if (inUpdater || (source == null)) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + // Compute geo's bounds + processCoordsChanged(false); + sendDataChangedMessage(true); + } + + /** + * Sets the coordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param coordinate a vector containing the new coordinate + */ + void setCoordinate(int index, Point3d coordinate) { + int offset = this.stride * index + coordinateOffset; + + geomLock.getLock(); + + dirtyFlag |= COORDINATE_CHANGED; + this.vertexData[offset] = (float)coordinate.x; + this.vertexData[offset+1]= (float)coordinate.y; + this.vertexData[offset+2]= (float)coordinate.z; + + geomLock.unLock(); + + if (inUpdater ||source == null ) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + + // Compute geo's bounds + processCoordsChanged(false); + sendDataChangedMessage(true); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param coordinates an array of 3*n values containing n new coordinates + */ + void setCoordinates(int index, float coordinates[]) { + int offset = this.stride * index + coordinateOffset; + int i, j, num = coordinates.length; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + for (i=0, j= offset;i < num; i+=3, j+= this.stride) + { + this.vertexData[j] = coordinates[i]; + this.vertexData[j+1]= coordinates[i+1]; + this.vertexData[j+2]= coordinates[i+2]; + } + + geomLock.unLock(); + if (inUpdater ||source == null ) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + // Compute geo's bounds + processCoordsChanged(false); + + sendDataChangedMessage(true); + + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param coordinates an array of 3*n values containing n new coordinates + */ + void setCoordinates(int index, double coordinates[]) { + int offset = this.stride * index + coordinateOffset; + int i, j, num = coordinates.length; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + for (i=0, j= offset;i < num; i+=3, j+= this.stride) + { + this.vertexData[j] = (float)coordinates[i]; + this.vertexData[j+1]= (float)coordinates[i+1]; + this.vertexData[j+2]= (float)coordinates[i+2]; + } + + geomLock.unLock(); + + if (inUpdater ||source == null ) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + + // Compute geo's bounds + processCoordsChanged(false); + + sendDataChangedMessage(true); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param coordinates an array of vectors containing new coordinates + */ + void setCoordinates(int index, Point3f coordinates[]) { + int offset = this.stride * index + coordinateOffset; + int i, j, num = coordinates.length; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + this.vertexData[j] = coordinates[i].x; + this.vertexData[j+1]= coordinates[i].y; + this.vertexData[j+2]= coordinates[i].z; + } + + geomLock.unLock(); + + if (inUpdater ||source == null ) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + // Compute geo's bounds + processCoordsChanged(false); + + sendDataChangedMessage(true); + + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param coordinates an array of vectors containing new coordinates + */ + void setCoordinates(int index, Point3d coordinates[]) { + int offset = this.stride * index + coordinateOffset; + int i, j, num = coordinates.length; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + this.vertexData[j] = (float)coordinates[i].x; + this.vertexData[j+1]= (float)coordinates[i].y; + this.vertexData[j+2]= (float)coordinates[i].z; + } + + geomLock.unLock(); + + if (inUpdater ||source == null ) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + // Compute geo's bounds + processCoordsChanged(false); + + sendDataChangedMessage(true); + } + + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object using coordinate data starting + * from vertex index <code>start</code> for <code>length</code> vertices. + * @param index the vertex index + * @param coordinates an array of vectors containing new coordinates + * @param start starting vertex index of data in <code>coordinates</code> . + * @param length number of vertices to be copied. + */ + void setCoordinates(int index, float coordinates[], int start, int length) { + int offset = this.stride * index + coordinateOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + for (i= start * 3, j= offset; i < (start+length) * 3; + i+=3, j+= this.stride) { + this.vertexData[j] = coordinates[i]; + this.vertexData[j+1]= coordinates[i+1]; + this.vertexData[j+2]= coordinates[i+2]; + } + + geomLock.unLock(); + if (inUpdater ||source == null ) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + // Compute geo's bounds + processCoordsChanged(false); + + sendDataChangedMessage(true); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object using coordinate data starting + * from vertex index <code>start</code> for <code>length</code> vertices. + * @param index the vertex index + * @param coordinates an array of 3*n values containing n new coordinates + * @param start starting vertex index of data in <code>coordinates</code> . + * @param length number of vertices to be copied. + */ + void setCoordinates(int index, double coordinates[], int start, int length) { + int offset = this.stride * index + coordinateOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + for (i= start*3, j= offset; i < (start+length)*3; + i+=3, j+= this.stride) { + this.vertexData[j] = (float)coordinates[i]; + this.vertexData[j+1]= (float)coordinates[i+1]; + this.vertexData[j+2]= (float)coordinates[i+2]; + } + + geomLock.unLock(); + + if (inUpdater || (source == null)) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + + // Compute geo's bounds + processCoordsChanged(false); + + sendDataChangedMessage(true); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object using coordinate data starting + * from vertex index <code>start</code> for <code>length</code> vertices. + * @param index the vertex index + * @param coordinates an array of vectors containing new coordinates + * @param start starting vertex index of data in <code>coordinates</code> . + * @param length number of vertices to be copied. + */ + void setCoordinates(int index, Point3f coordinates[], int start, + int length) { + int offset = this.stride * index + coordinateOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + for (i=start, j= offset;i < start + length; i++, j+= this.stride) { + this.vertexData[j] = coordinates[i].x; + this.vertexData[j+1]= coordinates[i].y; + this.vertexData[j+2]= coordinates[i].z; + } + + geomLock.unLock(); + + + if (inUpdater || (source == null)) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + + // Compute geo's bounds + processCoordsChanged(false); + + sendDataChangedMessage(true); + } + + /** + * Sets the coordinates associated with the vertices starting at + * the specified index for this object using coordinate data starting + * from vertex index <code>start</code> for <code>length</code> vertices. + * @param index the vertex index + * @param coordinates an array of vectors containing new coordinates + * @param start starting vertex index of data in <code>coordinates</code> . + * @param length number of vertices to be copied. + */ + void setCoordinates(int index, Point3d coordinates[], int start, + int length) { + int offset = this.stride * index + coordinateOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + for (i=start, j= offset;i < start + length; i++, j+= this.stride) { + this.vertexData[j] = (float)coordinates[i].x; + this.vertexData[j+1]= (float)coordinates[i].y; + this.vertexData[j+2]= (float)coordinates[i].z; + } + + geomLock.unLock(); + + + if (inUpdater || (source == null)) { + return; + } + if (!source.isLive()) { + boundsDirty = true; + return; + } + + // Compute geo's bounds + processCoordsChanged(false); + + sendDataChangedMessage(true); + } + + /** + * Sets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color an array of 3 or 4 values containing the new color + */ + void setColor(int index, float color[]) { + int offset = this.stride*index + colorOffset; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + this.vertexData[offset] = color[0]; + this.vertexData[offset+1] = color[1]; + this.vertexData[offset+2] = color[2]; + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + this.vertexData[offset+3] = color[3]*lastAlpha[0]; + else + this.vertexData[offset+3] = lastAlpha[0]; + + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color an array of 3 or 4 values containing the new color + */ + void setColor(int index, byte color[]) { + int offset = this.stride*index + colorOffset; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + this.vertexData[offset] = (color[0] & 0xff) * ByteToFloatScale; + this.vertexData[offset+1] = (color[1] & 0xff) * ByteToFloatScale; + this.vertexData[offset+2] = (color[2] & 0xff) * ByteToFloatScale; + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + this.vertexData[offset+3] = ((color[3] & 0xff)* ByteToFloatScale)*lastAlpha[0]; + else + this.vertexData[offset+3] = lastAlpha[0]; + + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color a vector containing the new color + */ + void setColor(int index, Color3f color) { + int offset = this.stride*index + colorOffset; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + this.vertexData[offset] = color.x; + this.vertexData[offset+1] = color.y; + this.vertexData[offset+2] = color.z; + this.vertexData[offset+3] = lastAlpha[0]; + + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color a vector containing the new color + */ + void setColor(int index, Color4f color) { + int offset = this.stride*index + colorOffset; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + this.vertexData[offset] = color.x; + this.vertexData[offset+1] = color.y; + this.vertexData[offset+2] = color.z; + this.vertexData[offset+3] = color.w*lastAlpha[0]; + + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color a vector containing the new color + */ + void setColor(int index, Color3b color) { + int offset = this.stride*index + colorOffset; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + this.vertexData[offset] = (color.x & 0xff) * ByteToFloatScale; + this.vertexData[offset+1] = (color.y & 0xff) * ByteToFloatScale; + this.vertexData[offset+2] = (color.z & 0xff) * ByteToFloatScale; + this.vertexData[offset+3] = lastAlpha[0]; + + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color a vector containing the new color + */ + void setColor(int index, Color4b color) { + int offset = this.stride*index + colorOffset; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + this.vertexData[offset] = (color.x * 0xff) * ByteToFloatScale; + this.vertexData[offset+1] = (color.y * 0xff) * ByteToFloatScale; + this.vertexData[offset+2] = (color.z * 0xff) * ByteToFloatScale; + this.vertexData[offset+3] = ((color.w & 0xff) * ByteToFloatScale)*lastAlpha[0]; + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values containing n new colors + */ + void setColors(int index, float colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + { + for (i=0, j= offset;i < num; i+= 4, j+= this.stride) + { + this.vertexData[j] = colors[i]; + this.vertexData[j+1] = colors[i+1]; + this.vertexData[j+2] = colors[i+2]; + this.vertexData[j+3] = colors[i+3]*lastAlpha[0]; + } + } + else + { + for (i=0, j= offset;i < num; i+= 3, j+= this.stride) + { + this.vertexData[j] = colors[i]; + this.vertexData[j+1] = colors[i+1]; + this.vertexData[j+2] = colors[i+2]; + this.vertexData[j+3] = lastAlpha[0]; + } + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values containing n new colors + */ + void setColors(int index, byte colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + { + for (i=0, j= offset;i < num; i+= 4, j+= this.stride) + { + this.vertexData[j] = (colors[i] & 0xff) * ByteToFloatScale; + this.vertexData[j+1] = (colors[i+1] & 0xff) * ByteToFloatScale; + this.vertexData[j+2] = (colors[i+2] & 0xff) * ByteToFloatScale; + this.vertexData[j+3] = ((colors[i+3] & 0xff) * ByteToFloatScale)*lastAlpha[0]; + } + } + else + { + for (i=0, j= offset;i < num; i+= 3, j+= this.stride) + { + this.vertexData[j] = (colors[i] & 0xff) * ByteToFloatScale; + this.vertexData[j+1] = (colors[i+1] & 0xff) * ByteToFloatScale; + this.vertexData[j+2] = (colors[i+2] & 0xff) * ByteToFloatScale; + this.vertexData[j+3] = lastAlpha[0]; + } + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of vectors containing new colors + */ + void setColors(int index, Color3f colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + this.vertexData[j] = colors[i].x; + this.vertexData[j+1] = colors[i].y; + this.vertexData[j+2] = colors[i].z; + this.vertexData[j+3] = lastAlpha[0]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of vectors containing new colors + */ + void setColors(int index, Color4f colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + this.vertexData[j] = colors[i].x; + this.vertexData[j+1] = colors[i].y; + this.vertexData[j+2] = colors[i].z; + this.vertexData[j+3] = colors[i].w*lastAlpha[0]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of vectors containing new colors + */ + void setColors(int index, Color3b colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + this.vertexData[j] = (colors[i].x & 0xff) * ByteToFloatScale; + this.vertexData[j+1] = (colors[i].y & 0xff) * ByteToFloatScale; + this.vertexData[j+2] = (colors[i].z & 0xff) * ByteToFloatScale; + this.vertexData[j+3] = lastAlpha[0]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + } + + + /** + * Sets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of vectors containing new colors + */ + void setColors(int index, Color4b colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + this.vertexData[j] = (colors[i].x & 0xff) * ByteToFloatScale; + this.vertexData[j+1] = (colors[i].y & 0xff) * ByteToFloatScale; + this.vertexData[j+2] = (colors[i].z & 0xff) * ByteToFloatScale; + this.vertexData[j+3] = ((colors[i].w & 0xff) * ByteToFloatScale)*lastAlpha[0]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>color</code>s + * starting at index <code>start</code> for <code>length</code> colors. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values containing n new colors + * @param start starting color index of data in <code>colors</code>. + * @param length number of colors to be copied. + */ + void setColors(int index, float colors[], int start, int length) { + int offset = this.stride*index + colorOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + for (i = start * 4, j = offset; i < (start + length) * 4; + i += 4, j += this.stride) { + this.vertexData[j] = colors[i]; + this.vertexData[j+1] = colors[i+1]; + this.vertexData[j+2] = colors[i+2]; + this.vertexData[j+3] = colors[i+3]*lastAlpha[0]; + } + } else { + for (i = start * 3, j = offset; i < (start + length) * 3; + i += 3, j += this.stride) { + this.vertexData[j] = colors[i]; + this.vertexData[j+1] = colors[i+1]; + this.vertexData[j+2] = colors[i+2]; + this.vertexData[j+3] = lastAlpha[0]; + } + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>color</code>s + * starting at index <code>start</code> for <code>length</code> colors. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values containing n new colors + * @param start starting color index of data in <code>colors</code>. + * @param length number of colors to be copied. + */ + void setColors(int index, byte colors[], int start, int length) { + int offset = this.stride*index + colorOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + for (i = start * 4, j = offset; i < (start + length) * 4; + i += 4, j += this.stride) { + this.vertexData[j] = (colors[i] & 0xff) * ByteToFloatScale; + this.vertexData[j+1] = (colors[i+1] & 0xff) * ByteToFloatScale; + this.vertexData[j+2] = (colors[i+2] & 0xff) * ByteToFloatScale; + this.vertexData[j+3] = ((colors[i+3] & 0xff) * ByteToFloatScale)*lastAlpha[0]; + } + } else { + for (i = start * 3, j = offset; i < (start + length) * 3; + i += 3, j += this.stride) { + this.vertexData[j] = (colors[i] & 0xff) * ByteToFloatScale; + this.vertexData[j+1] = (colors[i+1] & 0xff) * ByteToFloatScale; + this.vertexData[j+2] = (colors[i+2] & 0xff) * ByteToFloatScale; + this.vertexData[j+3] = lastAlpha[0]; + } + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>color</code>s + * starting at index <code>start</code> for <code>length</code> colors. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values containing n new colors + * @param start starting color index of data in <code>colors</code>. + * @param length number of colors to be copied. + */ + void setColors(int index, Color3f colors[], int start, int length) { + int offset = this.stride*index + colorOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = colors[i].x; + this.vertexData[j+1] = colors[i].y; + this.vertexData[j+2] = colors[i].z; + this.vertexData[j+3] = lastAlpha[0]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>color</code>s + * starting at index <code>start</code> for <code>length</code> colors. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values containing n new colors + * @param start starting color index of data in <code>colors</code>. + * @param length number of colors to be copied. + */ + void setColors(int index, Color4f colors[], int start, int length) { + int offset = this.stride*index + colorOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = colors[i].x; + this.vertexData[j+1] = colors[i].y; + this.vertexData[j+2] = colors[i].z; + this.vertexData[j+3] = colors[i].w*lastAlpha[0]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>color</code>s + * starting at index <code>start</code> for <code>length</code> colors. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values containing n new colors + * @param start starting color index of data in <code>colors</code>. + * @param length number of colors to be copied. + */ + void setColors(int index, Color3b colors[], int start, int length) { + int offset = this.stride*index + colorOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = (colors[i].x & 0xff) * ByteToFloatScale; + this.vertexData[j+1] = (colors[i].y & 0xff) * ByteToFloatScale; + this.vertexData[j+2] = (colors[i].z & 0xff) * ByteToFloatScale; + this.vertexData[j+3] = lastAlpha[0]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the colors associated with the vertices starting at + * the specified index for this object using data in <code>color</code>s + * starting at index <code>start</code> for <code>length</code> colors. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values containing n new colors + * @param start starting color index of data in <code>colors</code>. + * @param length number of colors to be copied. + */ + void setColors(int index, Color4b colors[], int start, int length) { + int offset = this.stride*index + colorOffset; + int i, j; + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = (colors[i].x & 0xff) * ByteToFloatScale; + this.vertexData[j+1] = (colors[i].y & 0xff) * ByteToFloatScale; + this.vertexData[j+2] = (colors[i].z & 0xff) * ByteToFloatScale; + this.vertexData[j+3] = ((colors[i].w & 0xff) * ByteToFloatScale)*lastAlpha[0]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the normal associated with the vertex at + * the specified index. + * @param index the vertex index + * @param normal the new normal + */ + void setNormal(int index, float normal[]) { + int offset = this.stride*index + normalOffset; + + geomLock.getLock(); + + + this.vertexData[offset] = normal[0]; + this.vertexData[offset+1] = normal[1]; + this.vertexData[offset+2] = normal[2]; + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the normal associated with the vertex at + * the specified index. + * @param index the vertex index + * @param normal the vector containing the new normal + */ + void setNormal(int index, Vector3f normal) { + int offset = this.stride*index + normalOffset; + + geomLock.getLock(); + + dirtyFlag |= NORMAL_CHANGED; + this.vertexData[offset] = normal.x; + this.vertexData[offset+1] = normal.y; + this.vertexData[offset+2] = normal.z; + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the normals associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param normals the new normals + */ + void setNormals(int index, float normals[]) { + int offset = this.stride*index + normalOffset; + int i, j, num = normals.length; + + geomLock.getLock(); + + dirtyFlag |= NORMAL_CHANGED; + for (i=0, j= offset;i < num;i += 3, j+= this.stride) + { + this.vertexData[j] = normals[i]; + this.vertexData[j+1] = normals[i+1]; + this.vertexData[j+2] = normals[i+2]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the normals associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param normals the vector containing the new normals + */ + void setNormals(int index, Vector3f normals[]) { + int offset = this.stride*index + normalOffset; + int i, j, num = normals.length; + + geomLock.getLock(); + + dirtyFlag |= NORMAL_CHANGED; + for (i=0, j= offset;i < num;i++, j+= this.stride) + { + this.vertexData[j] = normals[i].x; + this.vertexData[j+1] = normals[i].y; + this.vertexData[j+2] = normals[i].z; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the normals associated with the vertices starting at + * the specified index for this object using data in <code>normals</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index the vertex index + * @param normals the new normals + * @param start starting normal index of data in <code>colors</code> . + * @param length number of normals to be copied. + */ + void setNormals(int index, float normals[], int start, int length) { + int offset = this.stride*index + normalOffset; + int i, j; + + geomLock.getLock(); + + dirtyFlag |= NORMAL_CHANGED; + for (i = start * 3, j = offset; i < (start + length) * 3; + i+=3, j += this.stride) { + this.vertexData[j] = normals[i]; + this.vertexData[j+1] = normals[i+1]; + this.vertexData[j+2] = normals[i+2]; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the normals associated with the vertices starting at + * the specified index for this object using data in <code>normals</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index the vertex index + * @param normals the new normals + * @param start starting normal index of data in <code>colors</code> . + * @param length number of normals to be copied. + */ + void setNormals(int index, Vector3f normals[], int start, int length) { + int offset = this.stride*index + normalOffset; + int i, j; + + geomLock.getLock(); + + dirtyFlag |= NORMAL_CHANGED; + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = normals[i].x; + this.vertexData[j+1] = normals[i].y; + this.vertexData[j+2] = normals[i].z; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index for this object using data in <code>texCoords</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index the vertex index + * @param texCoords the new texture coordinates + * @param start starting texture coordinate index of data in <code>texCoords</code> . + * @param length number of texture Coordinates to be copied. + */ + void setTextureCoordinates(int texCoordSet, int index, float texCoords[], + int start, int length) { + + if ((this.vertexFormat & GeometryArray.BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j, k; + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + for (i = start * 4, j = offset, k = 0; k < length; + j += this.stride, k++) { + this.vertexData[j] = texCoords[i++]; + this.vertexData[j+1] = texCoords[i++]; + this.vertexData[j+2] = texCoords[i++]; + this.vertexData[j+3] = texCoords[i++]; + } + } else if ((this.vertexFormat & + GeometryArray.TEXTURE_COORDINATE_3) != 0) { + for (i = start * 3, j = offset, k = 0; k < length; + j += this.stride, k++) { + this.vertexData[j] = texCoords[i++]; + this.vertexData[j+1] = texCoords[i++]; + this.vertexData[j+2] = texCoords[i++]; + } + } else { + for (i = start * 2, j = offset, k = 0; k < length; + j += this.stride, k++) { + this.vertexData[j] = texCoords[i++]; + this.vertexData[j+1] = texCoords[i++]; + } + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index for this object using data in <code>texCoords</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index the vertex index + * @param texCoords the new texture coordinates + * @param start starting texture coordinate index of data in <code>texCoords</code> . + * @param length number of texture Coordinates to be copied. + */ + void setTextureCoordinates(int texCoordSet, int index, Point2f texCoords[], + int start, int length) { + + if ((this.vertexFormat & GeometryArray.BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j; + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = texCoords[i].x; + this.vertexData[j+1] = texCoords[i].y; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index for this object using data in <code>texCoords</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index the vertex index + * @param texCoords the new texture coordinates + * @param start starting texture coordinate index of data in <code>texCoords</code> . + * @param length number of texture Coordinates to be copied. + */ + void setTextureCoordinates(int texCoordSet, int index, Point3f texCoords[], + int start, int length) { + + if ((this.vertexFormat & GeometryArray.BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j; + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = texCoords[i].x; + this.vertexData[j+1] = texCoords[i].y; + this.vertexData[j+2] = texCoords[i].z; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index for this object using data in <code>texCoords</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index the vertex index + * @param texCoords the new texture coordinates + * @param start starting texture coordinate index of data in <code>texCoords</code> . + * @param length number of texture Coordinates to be copied. + */ + void setTextureCoordinates(int texCoordSet, int index, TexCoord2f texCoords[], + int start, int length) { + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + + if ((this.vertexFormat & GeometryArray.BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = texCoords[i].x; + this.vertexData[j+1] = texCoords[i].y; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index for this object using data in <code>texCoords</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index the vertex index + * @param texCoords the new texture coordinates + * @param start starting texture coordinate index of data in <code>texCoords</code> . + * @param length number of texture Coordinates to be copied. + */ + void setTextureCoordinates(int texCoordSet, int index, + TexCoord3f texCoords[], + int start, int length) { + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + + if ((this.vertexFormat & GeometryArray.BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = texCoords[i].x; + this.vertexData[j+1] = texCoords[i].y; + this.vertexData[j+2] = texCoords[i].z; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + } + + /** + * Sets the texture coordinates associated with the vertices starting at + * the specified index for this object using data in <code>texCoords</code> + * starting at index <code>start</code> and ending at index <code>start+length</code>. + * @param index the vertex index + * @param texCoords the new texture coordinates + * @param start starting texture coordinate index of data in <code>texCoords</code> . + * @param length number of texture Coordinates to be copied. + */ + void setTextureCoordinates(int texCoordSet, int index, + TexCoord4f texCoords[], + int start, int length) { + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + + if ((this.vertexFormat & GeometryArray.BY_REFERENCE) != 0) + throw new IllegalStateException(J3dI18N.getString("GeometryArray82")); + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE ) == 0) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray79")); + + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j; + + for (i = start, j = offset; i < start+length; i++, j += this.stride) { + this.vertexData[j] = texCoords[i].x; + this.vertexData[j+1] = texCoords[i].y; + this.vertexData[j+2] = texCoords[i].z; + this.vertexData[j+3] = texCoords[i].w; + } + if (source == null || !source.isLive()) { + geomLock.unLock(); + return; + } + + geomLock.unLock(); + sendDataChangedMessage(false); + } + + /** + * Gets the coordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param coordinate an array of 3 values that will receive the new coordinate + */ + void getCoordinate(int index, float coordinate[]) { + int offset = this.stride*index + coordinateOffset; + + coordinate[0]= this.vertexData[offset]; + coordinate[1]= this.vertexData[offset+1]; + coordinate[2]= this.vertexData[offset+2]; + } + + /** + * Gets the coordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param coordinate an array of 3 values that will receive the new coordinate + */ + void getCoordinate(int index, double coordinate[]) { + int offset = this.stride*index + coordinateOffset; + + coordinate[0]= (double)this.vertexData[offset]; + coordinate[1]= (double)this.vertexData[offset+1]; + coordinate[2]= (double)this.vertexData[offset+2]; + } + + /** + * Gets the coordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param coordinate a vector that will receive the new coordinate + */ + void getCoordinate(int index, Point3f coordinate) { + int offset = this.stride*index + coordinateOffset; + + coordinate.x = this.vertexData[offset]; + coordinate.y = this.vertexData[offset+1]; + coordinate.z = this.vertexData[offset+2]; + } + + /** + * Gets the coordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param coordinate a vector that will receive the new coordinate + */ + void getCoordinate(int index, Point3d coordinate) { + int offset = this.stride*index + coordinateOffset; + + coordinate.x = (double)this.vertexData[offset]; + coordinate.y = (double)this.vertexData[offset+1]; + coordinate.z = (double)this.vertexData[offset+2]; + } + + /** + * Gets the coordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param coordinates an array of 3*n values that will receive new coordinates + */ + void getCoordinates(int index, float coordinates[]) { + int offset = this.stride*index + coordinateOffset; + int i, j, num = coordinates.length; + + for (i=0,j= offset;i < num;i +=3, j += this.stride) + { + coordinates[i] = this.vertexData[j]; + coordinates[i+1]= this.vertexData[j+1]; + coordinates[i+2]= this.vertexData[j+2]; + } + } + + + /** + * Gets the coordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param coordinates an array of 3*n values that will receive new coordinates + */ + void getCoordinates(int index, double coordinates[]) { + int offset = this.stride*index + coordinateOffset; + int i, j, num = coordinates.length; + + for (i=0,j= offset;i < num;i +=3, j += this.stride) + { + coordinates[i] = (double)this.vertexData[j]; + coordinates[i+1]= (double)this.vertexData[j+1]; + coordinates[i+2]= (double)this.vertexData[j+2]; + } + } + + /** + * Gets the coordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param coordinates an array of vectors that will receive new coordinates + */ + void getCoordinates(int index, Point3f coordinates[]) { + int offset = this.stride*index + coordinateOffset; + int i, j, num = coordinates.length; + + for (i=0,j= offset;i < num;i++, j += this.stride) + { + coordinates[i].x = this.vertexData[j]; + coordinates[i].y = this.vertexData[j+1]; + coordinates[i].z = this.vertexData[j+2]; + } + } + + /** + * Gets the coordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param coordinates an array of vectors that will receive new coordinates + */ + void getCoordinates(int index, Point3d coordinates[]) { + int offset = this.stride*index + coordinateOffset; + int i, j, num = coordinates.length; + + for (i=0,j= offset;i < num;i++, j += this.stride) + { + coordinates[i].x = (double)this.vertexData[j]; + coordinates[i].y = (double)this.vertexData[j+1]; + coordinates[i].z = (double)this.vertexData[j+2]; + } + } + + /** + * Gets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color an array of 3 or 4 values that will receive the new color + */ + void getColor(int index, float color[]) { + int offset = this.stride*index + colorOffset; + + color[0]= this.vertexData[offset]; + color[1]= this.vertexData[offset+1]; + color[2]= this.vertexData[offset+2]; + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + color[3]= this.vertexData[offset+3]/lastAlpha[0]; + } + + /** + * Gets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color an array of 3 or 4 values that will receive the new color + */ + void getColor(int index, byte color[]) { + int offset = this.stride*index + colorOffset; + + color[0]= (byte)(this.vertexData[offset] * FloatToByteScale); + color[1]= (byte)(this.vertexData[offset+1] * FloatToByteScale); + color[2]= (byte)(this.vertexData[offset+2] * FloatToByteScale); + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + color[3]= (byte)((this.vertexData[offset+3]/lastAlpha[0]) * FloatToByteScale); + } + + /** + * Gets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color a vector that will receive the new color + */ + void getColor(int index, Color3f color) { + int offset = this.stride*index + colorOffset; + + color.x = this.vertexData[offset]; + color.y = this.vertexData[offset+1]; + color.z = this.vertexData[offset+2]; + } + + /** + * Gets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color a vector that will receive the new color + */ + void getColor(int index, Color4f color) { + int offset = this.stride*index + colorOffset; + + color.x = this.vertexData[offset]; + color.y = this.vertexData[offset+1]; + color.z = this.vertexData[offset+2]; + color.w= this.vertexData[offset+3]/lastAlpha[0]; + } + + /** + * Gets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color a vector that will receive the new color + */ + void getColor(int index, Color3b color) { + int offset = this.stride*index + colorOffset; + + color.x = (byte)(this.vertexData[offset] * FloatToByteScale); + color.y = (byte)(this.vertexData[offset+1] * FloatToByteScale); + color.z = (byte)(this.vertexData[offset+2] * FloatToByteScale); + } + + /** + * Gets the color associated with the vertex at + * the specified index. + * @param index the vertex index + * @param color a vector that will receive the new color + */ + void getColor(int index, Color4b color) { + int offset = this.stride*index + colorOffset; + + color.x = (byte)(this.vertexData[offset] * FloatToByteScale); + color.y = (byte)(this.vertexData[offset+1] * FloatToByteScale); + color.z = (byte)(this.vertexData[offset+2] * FloatToByteScale); + color.w = (byte)((this.vertexData[offset+3]/lastAlpha[0]) * FloatToByteScale); + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values that will receive n new colors + */ + void getColors(int index, float colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + float val = 1.0f/lastAlpha[0]; + + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + { + for (i=0, j= offset;i < num; i+= 4, j+= this.stride) + { + colors[i] = this.vertexData[j]; + colors[i+1]= this.vertexData[j+1]; + colors[i+2]= this.vertexData[j+2]; + colors[i+3]= this.vertexData[j+3] * val; + } + } + else + { + for (i=0, j= offset;i < num; i+= 3, j+= this.stride) + { + colors[i] = this.vertexData[j]; + colors[i+1]= this.vertexData[j+1]; + colors[i+2]= this.vertexData[j+2]; + } + } + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of 3*n or 4*n values that will receive new colors + */ + void getColors(int index, byte colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + float val = 1.0f/lastAlpha[0]; + + + if ((this.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + { + for (i=0, j= offset;i < num; i+= 4, j+= this.stride) + { + colors[i] = (byte)(this.vertexData[j] * FloatToByteScale); + colors[i+1]= (byte)(this.vertexData[j+1] * FloatToByteScale); + colors[i+2]= (byte)(this.vertexData[j+2] * FloatToByteScale); + colors[i+3]= (byte)((this.vertexData[j+3] * val) * FloatToByteScale); + } + } + else + { + for (i=0, j= offset;i < num; i+= 3, j+= this.stride) + { + colors[i] = (byte)(this.vertexData[j] * FloatToByteScale); + colors[i+1]= (byte)(this.vertexData[j+1] * FloatToByteScale); + colors[i+2]= (byte)(this.vertexData[j+2] * FloatToByteScale); + } + } + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of vectors that will receive new colors + */ + void getColors(int index, Color3f colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + colors[i].x = this.vertexData[j]; + colors[i].y = this.vertexData[j+1]; + colors[i].z = this.vertexData[j+2]; + } + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of vectors that will receive new colors + */ + void getColors(int index, Color4f colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + float val = 1.0f/lastAlpha[0]; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + colors[i].x = this.vertexData[j]; + colors[i].y = this.vertexData[j+1]; + colors[i].z = this.vertexData[j+2]; + colors[i].w = this.vertexData[j+3] * val; + } + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of vectors that will receive new colors + */ + void getColors(int index, Color3b colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + colors[i].x = (byte)(this.vertexData[j] * FloatToByteScale); + colors[i].y = (byte)(this.vertexData[j+1] * FloatToByteScale); + colors[i].z = (byte)(this.vertexData[j+2] * FloatToByteScale); + } + } + + /** + * Gets the colors associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param colors an array of vectors that will receive new colors + */ + void getColors(int index, Color4b colors[]) { + int offset = this.stride*index + colorOffset; + int i, j, num = colors.length; + float val = 1.0f/lastAlpha[0]; + + for (i=0, j= offset;i < num; i++, j+= this.stride) + { + colors[i].x = (byte)(this.vertexData[j] * FloatToByteScale); + colors[i].y = (byte)(this.vertexData[j+1] * FloatToByteScale); + colors[i].z = (byte)(this.vertexData[j+2] * FloatToByteScale); + colors[i].w = (byte)(this.vertexData[j+3] * val * FloatToByteScale); + } + } + + /** + * Gets the normal associated with the vertex at + * the specified index. + * @param index the vertex index + * @param normal array that will receive the new normal + */ + void getNormal(int index, float normal[]) { + int offset = this.stride*index + normalOffset; + + normal[0]= this.vertexData[offset]; + normal[1]= this.vertexData[offset+1]; + normal[2]= this.vertexData[offset+2]; + } + + /** + * Gets the normal associated with the vertex at + * the specified index. + * @param index the vertex index + * @param normal the vector that will receive the new normal + */ + void getNormal(int index, Vector3f normal) { + int offset = this.stride*index + normalOffset; + + normal.x= this.vertexData[offset]; + normal.y= this.vertexData[offset+1]; + normal.z= this.vertexData[offset+2]; + } + + /** + * Gets the normals associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param normals array that will receive the new normals + */ + void getNormals(int index, float normals[]) { + int offset = this.stride*index + normalOffset; + int i, j, num = normals.length; + + for (i=0, j= offset;i < num;i+=3, j+= this.stride) + { + normals[i] = this.vertexData[j]; + normals[i+1]= this.vertexData[j+1]; + normals[i+2]= this.vertexData[j+2]; + } + } + + /** + * Gets the normals associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param normals the vector that will receive the new normals + */ + void getNormals(int index, Vector3f normals[]) { + int offset = this.stride*index + normalOffset; + int i, j, num = normals.length; + + for (i=0, j= offset;i < num;i++, j+= this.stride) + { + normals[i].x= this.vertexData[j]; + normals[i].y= this.vertexData[j+1]; + normals[i].z= this.vertexData[j+2]; + } + } + + /** + * Gets the texture co-ordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param texCoord array that will receive the new texture co-ordinate + */ + void getTextureCoordinate(int texCoordSet, int index, float texCoord[]) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + + texCoord[0]= this.vertexData[offset]; + texCoord[1]= this.vertexData[offset+1]; + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + texCoord[2]= this.vertexData[offset+2]; + + } else if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) + != 0) { + texCoord[2]= this.vertexData[offset+2]; + texCoord[3]= this.vertexData[offset+3]; + } + } + + /** + * Gets the texture co-ordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param texCoord the vector that will receive the new texture co-ordinates + */ + void getTextureCoordinate(int texCoordSet, int index, TexCoord2f texCoord) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + + texCoord.x= this.vertexData[offset]; + texCoord.y= this.vertexData[offset+1]; + } + + /** + * Gets the texture co-ordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param texCoord the vector that will receive the new texture co-ordinates + */ + void getTextureCoordinate(int texCoordSet, int index, TexCoord3f texCoord) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + + texCoord.x= this.vertexData[offset]; + texCoord.y= this.vertexData[offset+1]; + texCoord.z= this.vertexData[offset+2]; + } + + /** + * Gets the texture co-ordinate associated with the vertex at + * the specified index. + * @param index the vertex index + * @param texCoord the vector that will receive the new texture co-ordinates + */ + void getTextureCoordinate(int texCoordSet, int index, TexCoord4f texCoord) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + + texCoord.x= this.vertexData[offset]; + texCoord.y= this.vertexData[offset+1]; + texCoord.z= this.vertexData[offset+2]; + texCoord.w= this.vertexData[offset+3]; + } + + /** + * Gets the texture co-ordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param texCoords array that will receive the new texture co-ordinates + */ + void getTextureCoordinates(int texCoordSet, int index, float texCoords[]) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j, num = texCoords.length; + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + for (i=0, j= offset;i < num;i+=4, j+= this.stride) + { + texCoords[i]= this.vertexData[j]; + texCoords[i+1]= this.vertexData[j+1]; + texCoords[i+2]= this.vertexData[j+2]; + texCoords[i+3]= this.vertexData[j+3]; + } + } else if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) + != 0) { + for (i=0, j= offset;i < num;i+=3, j+= this.stride) + { + texCoords[i]= this.vertexData[j]; + texCoords[i+1]= this.vertexData[j+1]; + texCoords[i+2]= this.vertexData[j+2]; + } + } else { + for (i=0, j= offset;i < num;i+=2, j+= this.stride) + { + texCoords[i]= this.vertexData[j]; + texCoords[i+1]= this.vertexData[j+1]; + } + } + } + + /** + * Gets the texture co-ordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param texCoords the vector that will receive the new texture co-ordinates + */ + void getTextureCoordinates(int texCoordSet, int index, + TexCoord2f texCoords[]) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j, num = texCoords.length; + + for (i=0, j= offset;i < num;i++, j+= this.stride) + { + texCoords[i].x= this.vertexData[j]; + texCoords[i].y= this.vertexData[j+1]; + } + } + + /** + * Gets the texture co-ordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param texCoords the vector that will receive the new texture co-ordinates + */ + void getTextureCoordinates(int texCoordSet, int index, TexCoord3f texCoords[]) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j, num = texCoords.length; + + for (i=0, j= offset;i < num;i++, j+= this.stride) + { + texCoords[i].x= this.vertexData[j]; + texCoords[i].y= this.vertexData[j+1]; + texCoords[i].z= this.vertexData[j+2]; + } + } + + /** + * Gets the texture co-ordinates associated with the vertices starting at + * the specified index. + * @param index the vertex index + * @param texCoords the vector that will receive the new texture co-ordinates + */ + void getTextureCoordinates(int texCoordSet, int index, TexCoord4f texCoords[]) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j, num = texCoords.length; + + for (i=0, j= offset;i < num;i++, j+= this.stride) + { + texCoords[i].x= this.vertexData[j]; + texCoords[i].y= this.vertexData[j+1]; + texCoords[i].z= this.vertexData[j+2]; + texCoords[i].w= this.vertexData[j+3]; + } + } + + void getTextureCoordinates(int texCoordSet, int index, + Point2f texCoords[]) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j, num = texCoords.length; + + for (i=0, j= offset;i < num;i++, j+= this.stride) + { + texCoords[i].x= this.vertexData[j]; + texCoords[i].y= this.vertexData[j+1]; + } + } + + void getTextureCoordinates(int texCoordSet, int index, Point3f texCoords[]) { + int offset = this.stride*index + textureOffset + + texCoordSet * texCoordStride; + int i, j, num = texCoords.length; + + for (i=0, j= offset;i < num;i++, j+= this.stride) + { + texCoords[i].x= this.vertexData[j]; + texCoords[i].y= this.vertexData[j+1]; + texCoords[i].z= this.vertexData[j+2]; + } + } + + + /** + * Updates geometry array data. + */ + void updateData(GeometryUpdater updater) { + boolean nullGeo = false; + + // Add yourself to obtain the geometry lock + // and Thread.currentThread().sleep until you get the lock + geomLock.getLock(); + + inUpdater = true; + updater.updateData((Geometry)source); + inUpdater = false; + if ((vertexFormat & GeometryArray.BY_REFERENCE) != 0) { + if((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { + // TODO: handle the nio buffer + if (!(this instanceof IndexedGeometryArrayRetained) || + (vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0) { + if (((vertexFormat & GeometryArray.INTERLEAVED) != 0)) { + setupMirrorInterleavedColorPointer(false); + nullGeo = (interleavedFloatBufferImpl == null); + } + else { + setupMirrorColorPointer((vertexType & COLOR_DEFINED), false); + nullGeo = ((vertexType & GeometryArrayRetained.VERTEX_DEFINED) == 0); + } + } + } + else { + if (!(this instanceof IndexedGeometryArrayRetained) || + (vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0) { + if (((vertexFormat & GeometryArray.INTERLEAVED) != 0)) { + setupMirrorInterleavedColorPointer(false); + nullGeo = (interLeavedVertexData == null); + } + else { + setupMirrorVertexPointer((vertexType & VERTEX_DEFINED)); + setupMirrorColorPointer((vertexType & COLOR_DEFINED), false); + setupMirrorNormalPointer((vertexType & NORMAL_DEFINED)); + setupMirrorTexCoordPointer((vertexType & TEXCOORD_DEFINED)); + nullGeo = ((vertexType & GeometryArrayRetained.VERTEX_DEFINED) == 0); + } + } + } + } + dirtyFlag |= VERTEX_CHANGED; + colorChanged = 0xffff; + geomLock.unLock(); + + if (source != null && source.isLive()) { + processCoordsChanged(nullGeo); + sendDataChangedMessage(true); + } + } + + boolean intersectBoundingBox( Point3d coordinates[], + BoundingBox box, + double dist[], + Point3d iPnt) { + int i, j; + int out[] = new int[6]; + + //Do trivial vertex test. + for(i=0; i<6; i++) + out[i] = 0; + for(i=0; i<coordinates.length; i++) { + if((coordinates[i].x >= box.lower.x) && (coordinates[i].x <= box.upper.x) && + (coordinates[i].y >= box.lower.y) && (coordinates[i].y <= box.upper.y) && + (coordinates[i].z >= box.lower.z) && (coordinates[i].z <= box.upper.z)) + // We're done! It's inside the boundingbox. + return true; + else { + if(coordinates[i].x < box.lower.x) + out[0]++; // left + if(coordinates[i].y < box.lower.y) + out[1]++; // bottom + if(coordinates[i].z < box.lower.z) + out[2]++; // back + if(coordinates[i].x > box.upper.x) + out[3]++; // right + if(coordinates[i].y > box.upper.y) + out[4]++; // top + if(coordinates[i].z > box.upper.z) + out[5]++; // front + } + + } + + if((out[0] == coordinates.length) || (out[1] == coordinates.length) || + (out[2] == coordinates.length) || (out[3] == coordinates.length) || + (out[4] == coordinates.length) || (out[5] == coordinates.length)) + // we're done. primitive is outside of boundingbox. + return false; + + // Setup bounding planes. + Point3d pCoor[] = new Point3d[4]; + for(i=0; i<4; i++) + pCoor[i] = getPoint3d(); + + // left plane. + pCoor[0].set(box.lower.x, box.lower.y, box.lower.z); + pCoor[1].set(box.lower.x, box.lower.y, box.upper.z); + pCoor[2].set(box.lower.x, box.upper.y, box.upper.z); + pCoor[3].set(box.lower.x, box.upper.y, box.lower.z); + + + if (intersectPolygon(pCoor, coordinates)) { + if (dist != null) { + computeMinDistance(pCoor, box.getCenter(), + null, + dist, iPnt); + } + // free points + for (i=0; i<4; i++) freePoint3d(pCoor[i]); + return true; + } + + // right plane. + pCoor[0].set(box.upper.x, box.lower.y, box.lower.z); + pCoor[1].set(box.upper.x, box.upper.y, box.lower.z); + pCoor[2].set(box.upper.x, box.upper.y, box.upper.z); + pCoor[3].set(box.upper.x, box.lower.y, box.upper.z); + if (intersectPolygon(pCoor, coordinates)) { + if (dist != null) { + computeMinDistance(pCoor, box.getCenter(), + null, + dist, iPnt); + } + for (i=0; i<4; i++) freePoint3d(pCoor[i]); + return true; + } + + // bottom plane. + pCoor[0].set(box.upper.x, box.lower.y, box.upper.z); + pCoor[1].set(box.lower.x, box.lower.y, box.upper.z); + pCoor[2].set(box.lower.x, box.lower.y, box.lower.z); + pCoor[3].set(box.upper.x, box.lower.y, box.lower.z); + if (intersectPolygon(pCoor, coordinates)) { + if (dist != null) { + computeMinDistance(pCoor, box.getCenter(), + null, + dist, iPnt); + } + for (i=0; i<4; i++) freePoint3d(pCoor[i]); + return true; + } + // top plane. + pCoor[0].set(box.upper.x, box.upper.y, box.upper.z); + pCoor[1].set(box.upper.x, box.upper.y, box.lower.z); + pCoor[2].set(box.lower.x, box.upper.y, box.lower.z); + pCoor[3].set(box.lower.x, box.upper.y, box.upper.z); + if (intersectPolygon(pCoor, coordinates)) { + if (dist != null) { + computeMinDistance(pCoor, box.getCenter(), + null, + dist, iPnt); + } + for (i=0; i<4; i++) freePoint3d(pCoor[i]); + return true; + } + + // front plane. + pCoor[0].set(box.upper.x, box.upper.y, box.upper.z); + pCoor[1].set(box.lower.x, box.upper.y, box.upper.z); + pCoor[2].set(box.lower.x, box.lower.y, box.upper.z); + pCoor[3].set(box.upper.x, box.lower.y, box.upper.z); + if (intersectPolygon(pCoor, coordinates)) { + if (dist != null) { + computeMinDistance(pCoor, box.getCenter(), + null, + dist, iPnt); + } + for (i=0; i<4; i++) freePoint3d(pCoor[i]); + return true; + } + + // back plane. + pCoor[0].set(box.upper.x, box.upper.y, box.lower.z); + pCoor[1].set(box.upper.x, box.lower.y, box.lower.z); + pCoor[2].set(box.lower.x, box.lower.y, box.lower.z); + pCoor[3].set(box.lower.x, box.upper.y, box.lower.z); + if (intersectPolygon(pCoor, coordinates)) { + if (dist != null) { + computeMinDistance(pCoor, box.getCenter(), + null, + dist, iPnt); + } + for (i=0; i<4; i++) freePoint3d(pCoor[i]); + return true; + } + + for (i=0; i<4; i++) freePoint3d(pCoor[i]); + return false; + } + + + boolean intersectBoundingSphere(Point3d coordinates[], + BoundingSphere sphere, + double dist[], + Point3d iPnt) + { + int i, j; + Vector3d tempV3D = getVector3d(); + boolean esFlag; + + //Do trivial vertex test. + + for (i=0; i<coordinates.length; i++) { + tempV3D.x = coordinates[i].x - sphere.center.x; + tempV3D.y = coordinates[i].y - sphere.center.y; + tempV3D.z = coordinates[i].z - sphere.center.z; + + if (tempV3D.length() <= sphere.radius) { + // We're done! It's inside the boundingSphere. + if (dist != null) { + computeMinDistance(coordinates, + sphere.getCenter(), + null, dist, iPnt); + } + + freeVector3d(tempV3D); + return true; + } + } + + for (i=0; i<coordinates.length; i++) { + if (i < (coordinates.length-1)) + esFlag = edgeIntersectSphere(sphere, coordinates[i], + coordinates[i+1]); + else + esFlag = edgeIntersectSphere(sphere, coordinates[i], + coordinates[0]); + if (esFlag == true) { + if (dist != null) { + computeMinDistance(coordinates, + sphere.getCenter(), + null, + dist, iPnt); + } + + freeVector3d(tempV3D); + return true; + } + } + + + if (coordinates.length < 3) { + + freeVector3d(tempV3D); + return false; // We're done with line. + } + + // Find rho. + // Compute plane normal. + Vector3d vec0 = getVector3d(); // Edge vector from point 0 to point 1; + Vector3d vec1 = getVector3d(); // Edge vector from point 0 to point 2 or 3; + Vector3d pNrm = getVector3d(); + Vector3d pa = getVector3d(); + Point3d q = getPoint3d(); + double nLenSq, pqLen, pNrmDotPa, tq; + + // compute plane normal for coordinates. + for(i=0; i<coordinates.length-1;) { + vec0.x = coordinates[i+1].x - coordinates[i].x; + vec0.y = coordinates[i+1].y - coordinates[i].y; + vec0.z = coordinates[i+1].z - coordinates[i++].z; + if(vec0.length() > 0.0) + break; + } + + for(j=i; j<coordinates.length-1; j++) { + vec1.x = coordinates[j+1].x - coordinates[j].x; + vec1.y = coordinates[j+1].y - coordinates[j].y; + vec1.z = coordinates[j+1].z - coordinates[j].z; + if(vec1.length() > 0.0) + break; + } + + if(j == (coordinates.length-1)) { + // System.out.println("(1) Degenerate polygon."); + freeVector3d(tempV3D); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(pa); + freePoint3d(q); + return false; // Degenerate polygon. + } + + /* + for(i=0; i<coordinates.length; i++) + System.out.println("coordinates P" + i + " " + coordinates[i]); + for(i=0; i<coord2.length; i++) + System.out.println("coord2 P" + i + " " + coord2[i]); + */ + + pNrm.cross(vec0,vec1); + + nLenSq = pNrm.lengthSquared(); + if( nLenSq == 0.0) { + // System.out.println("(2) Degenerate polygon."); + freeVector3d(tempV3D); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(pa); + freePoint3d(q); + return false; // Degenerate polygon. + } + + pa.x = coordinates[0].x - sphere.center.x; + pa.y = coordinates[0].y - sphere.center.y; + pa.z = coordinates[0].z - sphere.center.z; + + pNrmDotPa = pNrm.dot(pa); + + pqLen = Math.sqrt(pNrmDotPa * pNrmDotPa/ nLenSq); + + if(pqLen > sphere.radius) { + freeVector3d(tempV3D); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(pa); + freePoint3d(q); + return false; + } + + tq = pNrmDotPa / nLenSq; + + q.x = sphere.center.x + tq * pNrm.x; + q.y = sphere.center.y + tq * pNrm.y; + q.z = sphere.center.z + tq * pNrm.z; + + // PolyPnt2D Test. + if (pointIntersectPolygon2D( pNrm, coordinates, q)) { + if (dist != null) { + computeMinDistance(coordinates, + sphere.getCenter(), + pNrm, + dist, iPnt); + } + freeVector3d(tempV3D); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(pa); + freePoint3d(q); + return true; + } + freeVector3d(tempV3D); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(pa); + freePoint3d(q); + return false; + + } + + + boolean intersectBoundingPolytope(Point3d coordinates[], + BoundingPolytope polytope, + double dist[], + Point3d iPnt) + { + boolean debug = false; + + Point4d tP4d = new Point4d(); + + // this is a multiplier to the halfplane distance coefficients + double distanceSign = -1.0; + + if(coordinates.length == 2) { + // we'll handle line separately. + if (polytope.intersect( coordinates[0], + coordinates[1], tP4d)) { + if (dist != null) { + iPnt.x = tP4d.x; + iPnt.y = tP4d.y; + iPnt.z = tP4d.z; + Point3d pc = polytope.getCenter(); + double x = iPnt.x - pc.x; + double y = iPnt.y - pc.y; + double z = iPnt.z - pc.z; + dist[0] = Math.sqrt(x*x + y*y + z*z); + } + return true; + } + return false; + } + + // It is a triangle or a quad. + + // first test to see if any of the coordinates are all inside of the + // intersection polytope's half planes + // essentially do a matrix multiply of the constraintMatrix K*3 with + // the input coordinates 3*1 = K*1 vector + + if (debug) { + System.out.println("The value of the input vertices are: "); + for(int i=0; i < coordinates.length; i++) { + System.out.println("The " +i+ " th vertex is: " + coordinates[i]); + } + + System.out.println("The value of the input bounding Polytope's planes ="); + for(int i=0; i < polytope.planes.length; i++) { + System.out.println("The " +i+ " th plane is: " + polytope.planes[i]); + } + + } + + // the direction for the intersection cost function + double centers[] = new double[4]; + centers[0] = 0.8; centers[1] = 0.9; centers[2] = 1.1; centers[3] = 1.2; + + boolean intersection = true; + boolean PreTest = false; + + if(PreTest) { + // for each coordinate, test it with each half plane + for( int i=0; i < coordinates.length; i++) { + for (int j=0; j < polytope.planes.length; j++) { + if ( ( polytope.planes[j].x * coordinates[i].x + + polytope.planes[j].y * coordinates[i].y + + polytope.planes[j].z*coordinates[i].z) <= + (distanceSign)*polytope.planes[j].w){ + // the point satisfies this particular hyperplane + intersection = true; + } else { + // the point fails this hyper plane try with a new hyper plane + intersection = false; + break; + } + } + if(intersection) { + // a point was found to be completely inside the bounding hull + if (dist != null) { + computeMinDistance(coordinates, + polytope.getCenter(), + null, + dist, iPnt); + } + return true; + } + } + } // end of pretest + + // at this point all points are outside of the bounding hull + // build the problem tableau for the linear program + + int numberCols = polytope.planes.length + 2 + coordinates.length + 1; + int numberRows = 1 + coordinates.length; + + double problemTableau[][] = new double[numberRows][numberCols]; + + // compute -Mtrans = -A*P + + for( int i = 0; i < polytope.planes.length; i++) { + for( int j=0; j < coordinates.length; j++) { + problemTableau[j][i] = (-1.0)* (polytope.planes[i].x*coordinates[j].x+ + polytope.planes[i].y*coordinates[j].y+ + polytope.planes[i].z*coordinates[j].z); + } + } + + // add the other rows + for(int i = 0; i < coordinates.length; i++) { + problemTableau[i][polytope.planes.length] = -1.0; + problemTableau[i][polytope.planes.length + 1] = 1.0; + + for(int j=0; j < coordinates.length; j++) { + if ( i==j ) { + problemTableau[i][j + polytope.planes.length + 2] = 1.0; + } else { + problemTableau[i][j + polytope.planes.length + 2] = 0.0; + } + + // place the last column elements the Ci's + problemTableau[i][numberCols - 1] = centers[i]; + } + } + + // place the final rows value + for(int j = 0; j < polytope.planes.length; j++) { + problemTableau[numberRows - 1][j] = + (distanceSign)*polytope.planes[j].w; + } + problemTableau[numberRows - 1][polytope.planes.length] = 1.0; + problemTableau[numberRows - 1][polytope.planes.length+1] = -1.0; + for(int j = 0; j < coordinates.length; j++) { + problemTableau[numberRows - 1][polytope.planes.length+2+j] = 0.0; + } + + if(debug) { + System.out.println("The value of the problem tableau is: " ); + for(int i=0; i < problemTableau.length; i++) { + for(int j=0; j < problemTableau[0].length; j++) { + System.out.print(problemTableau[i][j] + " "); + } + System.out.println(); + } + } + + double distance = generalStandardSimplexSolver(problemTableau, + Float.NEGATIVE_INFINITY); + if(debug) { + System.out.println("The value returned by the general standard simplex = " + + distance); + } + if (distance == Float.POSITIVE_INFINITY) { + return false; + } + if (dist != null) { + computeMinDistance(coordinates, + polytope.getCenter(), + null, + dist, iPnt); + } + return true; + + } + + + // optimized version using arrays of doubles, but using the standard simplex + // method to solve the LP tableau. This version has not been optimized to + // work with a particular size input tableau and is much slower than some + // of the other variants...supposedly + double generalStandardSimplexSolver(double problemTableau[][], + double stopingValue) { + boolean debug = false; + int numRow = problemTableau.length; + int numCol = problemTableau[0].length; + boolean optimal = false; + int i, pivotRowIndex, pivotColIndex; + double maxElement, element, endElement, ratio, prevRatio; + int count = 0; + double multiplier; + + if(debug) { + System.out.println("The number of rows is : " + numRow); + System.out.println("The number of columns is : " + numCol); + } + + // until the optimal solution is found continue to do + // iterations of the simplex method + while(!optimal) { + + if(debug) { + System.out.println("input problem tableau is:"); + for(int k=0; k < numRow; k++) { + for(int j=0; j < numCol; j++) { + System.out.println("kth, jth value is:" +k+" "+j+" : " + + problemTableau[k][j]); + } + } + } + + // test to see if the current solution is optimal + // check all bottom row elements except the right most one and + // if all positive or zero its optimal + for(i = 0, maxElement = 0, pivotColIndex = -1; i < numCol - 1; i++) { + // a bottom row element + element = problemTableau[numRow - 1][i]; + if( element < maxElement) { + maxElement = element; + pivotColIndex = i; + } + } + + // if there is no negative non-zero element then we + // have found an optimal solution (the last row of the tableau) + if(pivotColIndex == -1) { + // found an optimal solution + //System.out.println("Found an optimal solution"); + optimal = true; + } + + //System.out.println("The value of maxElement is:" + maxElement); + + if(!optimal) { + // Case when the solution is not optimal but not known to be + // either unbounded or infeasable + + // from the above we have found the maximum negative element in + // bottom row, we have also found the column for this value + // the pivotColIndex represents this + + // initialize the values for the algorithm, -1 for pivotRowIndex + // indicates no solution + + prevRatio = Float.POSITIVE_INFINITY; + ratio = 0.0; + pivotRowIndex = -1; + + // note if all of the elements in the pivot column are zero or + // negative the problem is unbounded. + for(i = 0; i < numRow - 1; i++) { + element = problemTableau[i][pivotColIndex]; // r value + endElement = problemTableau[i][numCol-1]; // s value + + // pivot according to the rule that we want to choose the row + // with smallest s/r ratio see third case + // currently we ignore valuse of r==0 (case 1) and cases where the + // ratio is negative, i.e. either r or s are negative (case 2) + if(element == 0) { + if(debug) { + System.out.println("Division by zero has occurred"); + System.out.println("Within the linear program solver"); + System.out.println("Ignoring the zero as a potential pivot"); + } + } else if ( (element < 0.0) || (endElement < 0.0) ){ + if(debug) { + System.out.println("Ignoring cases where element is negative"); + System.out.println("The value of element is: " + element); + System.out.println("The value of end Element is: " + endElement); + } + } else { + ratio = endElement/element; // should be s/r + if(debug) { + System.out.println("The value of element is: " + element); + System.out.println("The value of endElement is: " + endElement); + System.out.println("The value of ratio is: " + ratio); + System.out.println("The value of prevRatio is: " + prevRatio); + System.out.println("Value of ratio <= prevRatio is :" + + (ratio <= prevRatio)); + } + if(ratio <= prevRatio) { + if(debug) { + System.out.println("updating prevRatio with ratio"); + } + prevRatio = ratio; + pivotRowIndex = i; + } + } + } + + // if the pivotRowIndex is still -1 then we know the pivotColumn + // has no viable pivot points and the solution is unbounded or + // infeasable (all pivot elements were either zero or negative or + // the right most value was negative (the later shouldn't happen?) + if(pivotRowIndex == -1) { + if(debug) { + System.out.println("UNABLE TO FIND SOLUTION"); + System.out.println("The system is infeasable or unbounded"); + } + return(Float.POSITIVE_INFINITY); + } + + // we now have the pivot row and col all that remains is + // to divide through by this value and subtract the appropriate + // multiple of the pivot row from all other rows to obtain + // a tableau which has a column of all zeros and one 1 in the + // intersection of pivot row and col + + // divide through by the pivot value + double pivotValue = problemTableau[pivotRowIndex][pivotColIndex]; + + if(debug) { + System.out.println("The value of row index is: " + pivotRowIndex); + System.out.println("The value of col index is: " + pivotColIndex); + System.out.println("The value of pivotValue is: " + pivotValue); + } + // divide through by s on the pivot row to obtain a 1 in pivot col + for(i = 0; i < numCol; i++) { + problemTableau[pivotRowIndex][i] = + problemTableau[pivotRowIndex][i] / pivotValue; + } + + // subtract appropriate multiple of pivot row from all other rows + // to zero out all rows except the final row and the pivot row + for(i = 0; i < numRow; i++) { + if(i != pivotRowIndex) { + multiplier = problemTableau[i][pivotColIndex]; + for(int j=0; j < numCol; j++) { + problemTableau[i][j] = problemTableau[i][j] - + multiplier * problemTableau[pivotRowIndex][j]; + } + } + } + } + // case when the element is optimal + } + return(problemTableau[numRow - 1][numCol - 1]); + } + + + + boolean edgeIntersectSphere(BoundingSphere sphere, Point3d start, + Point3d end) + { + double abLenSq, acLenSq, apLenSq, abDotAp, radiusSq; + Vector3d ab = getVector3d(); + Vector3d ap = getVector3d(); + + ab.x = end.x - start.x; + ab.y = end.y - start.y; + ab.z = end.z - start.z; + + ap.x = sphere.center.x - start.x; + ap.y = sphere.center.y - start.y; + ap.z = sphere.center.z - start.z; + + abDotAp = ab.dot(ap); + + if(abDotAp < 0.0) { + freeVector3d(ab); + freeVector3d(ap); + return false; // line segment points away from sphere. + } + + abLenSq = ab.lengthSquared(); + acLenSq = abDotAp * abDotAp / abLenSq; + + if(acLenSq < abLenSq) { + freeVector3d(ab); + freeVector3d(ap); + return false; // C doesn't lies between end points of edge. + } + + radiusSq = sphere.radius * sphere.radius; + apLenSq = ap.lengthSquared(); + + if((apLenSq - acLenSq) <= radiusSq) { + freeVector3d(ab); + freeVector3d(ap); + return true; + } + + freeVector3d(ab); + freeVector3d(ap); + return false; + + } + + + double det2D(Point2d a, Point2d b, Point2d p) + { + return (((p).x - (a).x) * ((a).y - (b).y) + + ((a).y - (p).y) * ((a).x - (b).x)); + } + + // Assume coord is CCW. + boolean pointIntersectPolygon2D(Vector3d normal, Point3d[] coord, + Point3d point) + { + + double absNrmX, absNrmY, absNrmZ; + Point2d coord2D[] = new Point2d[coord.length]; + Point2d pnt = new Point2d(); + + int i, j, axis; + + // Project 3d points onto 2d plane. + // Note : Area of polygon is not preserve in this projection, but + // it doesn't matter here. + + // Find the axis of projection. + absNrmX = Math.abs(normal.x); + absNrmY = Math.abs(normal.y); + absNrmZ = Math.abs(normal.z); + + if(absNrmX > absNrmY) + axis = 0; + else + axis = 1; + + if(axis == 0) { + if(absNrmX < absNrmZ) + axis = 2; + } + else if(axis == 1) { + if(absNrmY < absNrmZ) + axis = 2; + } + + // System.out.println("Normal " + normal + " axis " + axis ); + + for(i=0; i<coord.length; i++) { + coord2D[i] = new Point2d(); + + switch (axis) { + case 0: + coord2D[i].x = coord[i].y; + coord2D[i].y = coord[i].z; + break; + + case 1: + coord2D[i].x = coord[i].x; + coord2D[i].y = coord[i].z; + break; + + case 2: + coord2D[i].x = coord[i].x; + coord2D[i].y = coord[i].y; + break; + } + + // System.out.println("i " + i + " u " + uCoor[i] + " v " + vCoor[i]); + } + + + switch (axis) { + case 0: + pnt.x = point.y; + pnt.y = point.z; + break; + + case 1: + pnt.x = point.x; + pnt.y = point.z; + break; + + case 2: + pnt.x = point.x; + pnt.y = point.y; + break; + } + + // Do determinant test. + for(j=0; j<coord.length; j++) { + if(j<(coord.length-1)) + if(det2D(coord2D[j], coord2D[j+1], pnt)>0.0) + ; + else + return false; + else + if(det2D(coord2D[j], coord2D[0], pnt)>0.0) + ; + else + return false; + } + + return true; + + } + + + boolean edgeIntersectPlane(Vector3d normal, Point3d pnt, Point3d start, + Point3d end, Point3d iPnt) + { + + Vector3d tempV3d = getVector3d(); + Vector3d direction = getVector3d(); + double pD, pNrmDotrDir, tr; + + // Compute plane D. + tempV3d.set((Tuple3d) pnt); + pD = normal.dot(tempV3d); + + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + + pNrmDotrDir = normal.dot(direction); + + // edge is parallel to plane. + if(pNrmDotrDir== 0.0) { + // System.out.println("Edge is parallel to plane."); + freeVector3d(tempV3d); + freeVector3d(direction); + return false; + } + + tempV3d.set((Tuple3d) start); + + tr = (pD - normal.dot(tempV3d))/ pNrmDotrDir; + + // Edge intersects the plane behind the edge's start. + // or exceed the edge's length. + if((tr < 0.0 ) || (tr > 1.0 )) { + // System.out.println("Edge intersects the plane behind the start or exceed end."); + freeVector3d(tempV3d); + freeVector3d(direction); + return false; + } + + iPnt.x = start.x + tr * direction.x; + iPnt.y = start.y + tr * direction.y; + iPnt.z = start.z + tr * direction.z; + + freeVector3d(tempV3d); + freeVector3d(direction); + return true; + + } + + // Assume coord is CCW. + boolean edgeIntersectPolygon2D(Vector3d normal, Point3d[] coord, + Point3d[] seg) + { + + double absNrmX, absNrmY, absNrmZ; + Point2d coord2D[] = new Point2d[coord.length]; + Point2d seg2D[] = new Point2d[2]; + + int i, j, axis; + + // Project 3d points onto 2d plane. + // Note : Area of polygon is not preserve in this projection, but + // it doesn't matter here. + + // Find the axis of projection. + absNrmX = Math.abs(normal.x); + absNrmY = Math.abs(normal.y); + absNrmZ = Math.abs(normal.z); + + if(absNrmX > absNrmY) + axis = 0; + else + axis = 1; + + if(axis == 0) { + if(absNrmX < absNrmZ) + axis = 2; + } + else if(axis == 1) { + if(absNrmY < absNrmZ) + axis = 2; + } + + // System.out.println("Normal " + normal + " axis " + axis ); + + for(i=0; i<coord.length; i++) { + coord2D[i] = new Point2d(); + + switch (axis) { + case 0: + coord2D[i].x = coord[i].y; + coord2D[i].y = coord[i].z; + break; + + case 1: + coord2D[i].x = coord[i].x; + coord2D[i].y = coord[i].z; + break; + + case 2: + coord2D[i].x = coord[i].x; + coord2D[i].y = coord[i].y; + break; + } + + // System.out.println("i " + i + " u " + uCoor[i] + " v " + vCoor[i]); + } + + for(i=0; i<2; i++) { + seg2D[i] = new Point2d(); + switch (axis) { + case 0: + seg2D[i].x = seg[i].y; + seg2D[i].y = seg[i].z; + break; + + case 1: + seg2D[i].x = seg[i].x; + seg2D[i].y = seg[i].z; + break; + + case 2: + seg2D[i].x = seg[i].x; + seg2D[i].y = seg[i].y; + break; + } + + // System.out.println("i " + i + " u " + uSeg[i] + " v " + vSeg[i]); + } + + // Do determinant test. + boolean pntTest[][] = new boolean[2][coord.length]; + boolean testFlag; + + for(j=0; j<coord.length; j++) { + for(i=0; i<2; i++) { + if(j<(coord.length-1)) + pntTest[i][j] = (det2D(coord2D[j], coord2D[j+1], seg2D[i])<0.0); + else + pntTest[i][j] = (det2D(coord2D[j], coord2D[0], seg2D[i])<0.0); + } + + if((pntTest[0][j]==false) && (pntTest[1][j]==false)) + return false; + } + + testFlag = true; + for(i=0; i<coord.length; i++) { + if(pntTest[0][i]==false) { + testFlag = false; + break; + } + } + + if(testFlag == true) + return true; // start point is inside polygon. + + testFlag = true; + for(i=0; i<coord.length; i++) { + if(pntTest[1][i]==false) { + testFlag = false; + break; + } + } + + if(testFlag == true) + return true; // end point is inside polygon. + + + int cnt = 0; + for(i=0; i<coord.length; i++) { + if(det2D(seg2D[0], seg2D[1], coord2D[i])<0.0) + cnt++; + } + + if((cnt==0)||(cnt==coord.length)) + return false; + + return true; + + } + + + // New stuffs ..... + double getCompValue(Point3d v, int i) { + switch (i) { + case 0: return v.x; + case 1: return v.y; + } + // Has to return something, so set the default to z component. + return v.z; + } + + double getCompValue(Point3d v0, Point3d v1, int i) { + switch (i) { + case 0: return (v0.x - v1.x); + case 1: return (v0.y - v1.y); + } + // Has to return some, so set the default to z component. + return (v0.z - v1.z); + } + + + boolean pointInTri(Point3d v0, Point3d u0, Point3d u1, Point3d u2, + Vector3d normal) { + + double nAbsX, nAbsY, nAbsZ; + int i0, i1; + + // first project onto an axis-aligned plane, that maximizes the area + // of the triangles, compute indices i0, i1. + nAbsX = Math.abs(normal.x); + nAbsY = Math.abs(normal.y); + nAbsZ = Math.abs(normal.z); + + if (nAbsX > nAbsY) { + if(nAbsX > nAbsZ) { + i0 = 1; // nAbsX is greatest. + i1 = 2; + } + else { + i0 = 0; // nAbsZ is greatest. + i1 = 1; + } + } else { // nAbsX <= nAbsY + if(nAbsZ > nAbsY) { + i0 = 0; // nAbsZ is greatest. + i1 = 1; + } + else { + i0 = 0; // nAbsY is greatest. + i1 = 2; + } + } + return pointInTri(v0, u0, u1, u2, i0, i1); + } + + boolean pointInTri(Point3d v0, Point3d u0, Point3d u1, Point3d u2, + int i0, int i1) { + + double a, b, c, d0, d1, d2; + // is T1 completely inside T2 ? + // check if v0 is inside tri(u0,u1,u2) + + a = getCompValue(u1, u0, i1); + b = -(getCompValue(u1, u0, i0)); + c = -a * getCompValue(u0, i0) - b * getCompValue(u0, i1); + d0 = a * getCompValue(v0, i0) + b * getCompValue(v0, i1) + c; + + a = getCompValue(u2, u1, i1); + b = -(getCompValue(u2, u1, i0)); + c = -a * getCompValue(u1, i0) - b * getCompValue(u1, i1); + d1 = a * getCompValue(v0, i0) + b * getCompValue(v0, i1) + c; + + a = getCompValue(u0, u2, i1); + b = -(getCompValue(u0, u2, i0)); + c = -a * getCompValue(u2, i0) - b * getCompValue(u2, i1); + d2 = a * getCompValue(v0, i0) + b * getCompValue(v0, i1) + c; + + if(d0*d1>0.0) { + if(d0*d2>0.0) { + return true; + } + } + return false; + } + + + // this edge to edge test is based on Franlin Antonio's gem: + // "Faster line segment intersection", in Graphics Gems III, pp 199-202 + boolean edgeAgainstEdge(Point3d v0, Point3d u0, Point3d u1, double aX, double aY, + int i0, int i1) { + double bX, bY, cX, cY, e, d, f; + + bX = getCompValue(u0, u1,i0); + bY = getCompValue(u0, u1, i1); + cX = getCompValue(v0, u0, i0); + cY = getCompValue(v0, u0, i1); + + f = aY * bX - aX * bY; + d = bY * cX - bX * cY; + if((f>0 && d>=0 && d<=f) || (f<0 && d<=0 && d>=f)) { + e = aX * cY - aY * cX; + if(f>0) { + if(e>=0 && e<=f) + return true; + } + else { + if(e<=0 && e>=f) + return true; + } + } + + return false; + } + + + boolean edgeAgainstTriEdges(Point3d v0, Point3d v1, Point3d u0, + Point3d u1, Point3d u2, int i0, int i1) { + double aX, aY; + + // aX = v1[i0] - v0[i0]; + // aY = v1[i1] - v0[i1]; + aX = getCompValue(v1, v0, i0); + aY = getCompValue(v1, v0, i1); + + // test edge u0, u1 against v0, v1 + if(edgeAgainstEdge(v0, u0, u1, aX, aY, i0, i1)) + return true; + // test edge u1, u2 against v0, v1 + if(edgeAgainstEdge(v0, u1, u2, aX, aY, i0, i1)) + return true; + // test edge u2, u0 against v0, v1 + if(edgeAgainstEdge(v0, u2, u0, aX, aY, i0, i1)) + return true; + + return false; + + } + + boolean coplanarTriTri(Vector3d normal, Point3d v0, Point3d v1, Point3d v2, + Point3d u0, Point3d u1, Point3d u2) { + + double nAbsX, nAbsY, nAbsZ; + int i0, i1; + + // first project onto an axis-aligned plane, that maximizes the area + // of the triangles, compute indices i0, i1. + nAbsX = Math.abs(normal.x); + nAbsY = Math.abs(normal.y); + nAbsZ = Math.abs(normal.z); + + if(nAbsX > nAbsY) { + if(nAbsX > nAbsZ) { + i0 = 1; // nAbsX is greatest. + i1 = 2; + } + else { + i0 = 0; // nAbsZ is greatest. + i1 = 1; + } + } + else { // nAbsX <= nAbsY + if(nAbsZ > nAbsY) { + i0 = 0; // nAbsZ is greatest. + i1 = 1; + } + else { + i0 = 0; // nAbsY is greatest. + i1 = 2; + } + } + + // test all edges of triangle 1 against the edges of triangle 2 + if(edgeAgainstTriEdges(v0, v1, u0, u1, u2, i0, i1)) + return true; + + if(edgeAgainstTriEdges(v1, v2, u0, u1, u2, i0, i1)) + return true; + + if(edgeAgainstTriEdges(v2, v0, u0, u1, u2, i0, i1)) + return true; + + // finally, test if tri1 is totally contained in tri2 or vice versa. + if(pointInTri(v0, u0, u1, u2, i0, i1)) + return true; + + if(pointInTri(u0, v0, v1, v2, i0, i1)) + return true; + + return false; + } + + + + + + boolean intersectTriPnt(Point3d v0, Point3d v1, Point3d v2, Point3d u) { + + Vector3d e1 = getVector3d(); + Vector3d e2 = getVector3d(); + Vector3d n1 = getVector3d(); + Vector3d tempV3d = getVector3d(); + + double d1, du; + + // compute plane equation of triange(coord1) + e1.x = v1.x - v0.x; + e1.y = v1.y - v0.y; + e1.z = v1.z - v0.z; + + e2.x = v2.x - v0.x; + e2.y = v2.y - v0.y; + e2.z = v2.z - v0.z; + + n1.cross(e1,e2); + + if(n1.length() == 0.0) { + // System.out.println("(1) Degenerate triangle."); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(tempV3d); + return false; // Degenerate triangle. + } + + tempV3d.set((Tuple3d) v0); + d1 = - n1.dot(tempV3d); // plane equation 1: n1.x + d1 = 0 + + // put u to compute signed distance to the plane. + tempV3d.set((Tuple3d) u); + du = n1.dot(tempV3d) + d1; + + // coplanarity robustness check + if(Math.abs(du)<EPS) du = 0.0; + + // no intersection occurs + if(du != 0.0) { + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(tempV3d); + return false; + } + + double nAbsX, nAbsY, nAbsZ; + int i0, i1; + + // first project onto an axis-aligned plane, that maximizes the area + // of the triangles, compute indices i0, i1. + nAbsX = Math.abs(n1.x); + nAbsY = Math.abs(n1.y); + nAbsZ = Math.abs(n1.z); + + if(nAbsX > nAbsY) { + if(nAbsX > nAbsZ) { + i0 = 1; // nAbsX is greatest. + i1 = 2; + } + else { + i0 = 0; // nAbsZ is greatest. + i1 = 1; + } + } + else { // nAbsX <= nAbsY + if(nAbsZ > nAbsY) { + i0 = 0; // nAbsZ is greatest. + i1 = 1; + } + else { + i0 = 0; // nAbsY is greatest. + i1 = 2; + } + } + + + // finally, test if u is totally contained in tri. + if(pointInTri(u, v0, v1, v2, i0, i1)) { + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(tempV3d); + return true; + } + + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(tempV3d); + return false; + } + + + /** + * Return flag indicating whether two triangles intersect. This + * uses Tomas Moller's code for fast triangle-triangle + * intersection from his "Real-Time Rendering" book. + * + * The code is now divisionless. It tests for separating by planes + * parallel to either triangle. If neither separate the + * triangles, then two cases are considered. First case is if the + * normals to the triangles are parallel. In that case, the + * triangles are coplanar and a sequence of tests are made to see + * if edges of each triangle intersect the other triangle. If the + * normals are not parallel, then the two triangles can intersect + * only on the line of intersection of the two planes. The + * intervals of intersection of the triangles with the line of + * intersection of the two planes are computed and tested for + * overlap. + */ + boolean intersectTriTri(Point3d v0, Point3d v1, Point3d v2, + Point3d u0, Point3d u1, Point3d u2) { + + // System.out.println("In intersectTriTri ..."); + Vector3d e1 = getVector3d(); + Vector3d e2 = getVector3d(); + Vector3d n1 = getVector3d(); + Vector3d n2 = getVector3d(); + Vector3d tempV3d = getVector3d(); + + double d1, d2; + double du0, du1, du2, dv0, dv1, dv2; + double du0du1, du0du2, dv0dv1, dv0dv2; + int index; + double vp0=0.0, vp1=0.0, vp2=0.0; + double up0=0.0, up1=0.0, up2=0.0; + double bb, cc, max; + + // compute plane equation of triange(coord1) + e1.x = v1.x - v0.x; + e1.y = v1.y - v0.y; + e1.z = v1.z - v0.z; + + e2.x = v2.x - v0.x; + e2.y = v2.y - v0.y; + e2.z = v2.z - v0.z; + + n1.cross(e1,e2); + + if(n1.length() == 0.0) { + // System.out.println("(1) Degenerate triangle."); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(n2); + freeVector3d(tempV3d); + return false; // Degenerate triangle. + } + + tempV3d.set((Tuple3d) v0); + d1 = - n1.dot(tempV3d); // plane equation 1: n1.x + d1 = 0 + + // put u0, u1, and u2 into plane equation 1 + // to compute signed distance to the plane. + tempV3d.set((Tuple3d) u0); + du0 = n1.dot(tempV3d) + d1; + tempV3d.set((Tuple3d) u1); + du1 = n1.dot(tempV3d) + d1; + tempV3d.set((Tuple3d) u2); + du2 = n1.dot(tempV3d) + d1; + + // coplanarity robustness check + if(Math.abs(du0)<EPS) du0 = 0.0; + if(Math.abs(du1)<EPS) du1 = 0.0; + if(Math.abs(du2)<EPS) du2 = 0.0; + + du0du1 = du0 * du1; + du0du2 = du0 * du2; + + // same sign on all of them + not equal 0 ? + // no intersection occurs + if(du0du1>0.0 && du0du2>0.0) { + // System.out.println("In intersectTriTri : du0du1>0.0 && du0du2>0.0"); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(n2); + freeVector3d(tempV3d); + return false; + } + + // compute plane of triangle(coord2) + e1.x = u1.x - u0.x; + e1.y = u1.y - u0.y; + e1.z = u1.z - u0.z; + + e2.x = u2.x - u0.x; + e2.y = u2.y - u0.y; + e2.z = u2.z - u0.z; + + n2.cross(e1,e2); + + if(n2.length() == 0.0) { + // System.out.println("(2) Degenerate triangle."); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(n2); + freeVector3d(tempV3d); + return false; // Degenerate triangle. + } + + tempV3d.set((Tuple3d) u0); + d2 = - n2.dot(tempV3d); // plane equation 2: n2.x + d2 = 0 + + // put v0, v1, and v2 into plane equation 2 + // to compute signed distance to the plane. + tempV3d.set((Tuple3d) v0); + dv0 = n2.dot(tempV3d) + d2; + tempV3d.set((Tuple3d) v1); + dv1 = n2.dot(tempV3d) + d2; + tempV3d.set((Tuple3d) v2); + dv2 = n2.dot(tempV3d) + d2; + + // coplanarity robustness check + if(Math.abs(dv0)<EPS) dv0 = 0.0; + if(Math.abs(dv1)<EPS) dv1 = 0.0; + if(Math.abs(dv2)<EPS) dv2 = 0.0; + + dv0dv1 = dv0 * dv1; + dv0dv2 = dv0 * dv2; + + // same sign on all of them + not equal 0 ? + // no intersection occurs + if(dv0dv1>0.0 && dv0dv2>0.0) { + // System.out.println("In intersectTriTri : dv0dv1>0.0 && dv0dv2>0.0"); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(n2); + freeVector3d(tempV3d); + return false; + } + // compute direction of intersection line. + tempV3d.cross(n1, n2); + + // compute and index to the largest component of tempV3d. + max = Math.abs(tempV3d.x); + index = 0; + bb = Math.abs(tempV3d.y); + cc = Math.abs(tempV3d.z); + if(bb>max) { + max=bb; + index=1; + } + if(cc>max) { + max=cc; + index=2; + } + + // this is the simplified projection onto L. + + switch (index) { + case 0: + vp0 = v0.x; + vp1 = v1.x; + vp2 = v2.x; + + up0 = u0.x; + up1 = u1.x; + up2 = u2.x; + break; + case 1: + vp0 = v0.y; + vp1 = v1.y; + vp2 = v2.y; + + up0 = u0.y; + up1 = u1.y; + up2 = u2.y; + break; + case 2: + vp0 = v0.z; + vp1 = v1.z; + vp2 = v2.z; + + up0 = u0.z; + up1 = u1.z; + up2 = u2.z; + break; + } + + // compute intereval for triangle 1. + double a=0.0, b=0.0, c=0.0, x0=0.0, x1=0.0; + if(dv0dv1>0.0) { + // here we know that dv0dv2 <= 0.0 that is dv0 and dv1 are on the same side, + // dv2 on the other side or on the plane. + a = vp2; b = (vp0 - vp2) * dv2; c = (vp1 - vp2) * dv2; + x0 = dv2 - dv0; x1 = dv2 - dv1; + } + else if(dv0dv2>0.0) { + // here we know that dv0dv1<=0.0 + a = vp1; b = (vp0 - vp1) * dv1; c = (vp2 - vp1) * dv1; + x0 = dv1 - dv0; x1 = dv1 - dv2; + } + else if((dv1*dv2>0.0) || (dv0 != 0.0)) { + // here we know that dv0vd1<=0.0 or that dv0!=0.0 + a = vp0; b = (vp1 - vp0) * dv0; c = (vp2 - vp0) * dv0; + x0 = dv0 - dv1; x1 = dv0 - dv2; + } + else if(dv1 != 0.0) { + a = vp1; b = (vp0 - vp1) * dv1; c = (vp2 - vp1) * dv1; + x0 = dv1 - dv0; x1 = dv1 - dv2; + } + else if(dv2 != 0.0) { + a = vp2; b = (vp0 - vp2) * dv2; c = (vp1 - vp2) * dv2; + x0 = dv2 - dv0; x1 = dv2 - dv1; + } + else { + // triangles are coplanar + boolean toreturn = coplanarTriTri(n1, v0, v1, v2, u0, u1, u2); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(n2); + freeVector3d(tempV3d); + return toreturn; + } + + + // compute intereval for triangle 2. + double d=0.0, e=0.0, f=0.0, y0=0.0, y1=0.0; + if(du0du1>0.0) { + // here we know that du0du2 <= 0.0 that is du0 and du1 are on the same side, + // du2 on the other side or on the plane. + d = up2; e = (up0 - up2) * du2; f = (up1 - up2) * du2; + y0 = du2 - du0; y1 = du2 - du1; + } + else if(du0du2>0.0) { + // here we know that du0du1<=0.0 + d = up1; e = (up0 - up1) * du1; f = (up2 - up1) * du1; + y0 = du1 - du0; y1 = du1 - du2; + } + else if((du1*du2>0.0) || (du0 != 0.0)) { + // here we know that du0du1<=0.0 or that D0!=0.0 + d = up0; e = (up1 - up0) * du0; f = (up2 - up0) * du0; + y0 = du0 - du1; y1 = du0 - du2; + } + else if(du1 != 0.0) { + d = up1; e = (up0 - up1) * du1; f = (up2 - up1) * du1; + y0 = du1 - du0; y1 = du1 - du2; + } + else if(du2 != 0.0) { + d = up2; e = (up0 - up2) * du2; f = (up1 - up2) * du2; + y0 = du2 - du0; y1 = du2 - du1; + } + else { + // triangles are coplanar + // System.out.println("In intersectTriTri : coplanarTriTri test 2"); + boolean toreturn = coplanarTriTri(n2, v0, v1, v2, u0, u1, u2); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(n2); + freeVector3d(tempV3d); + return toreturn; + } + + double xx, yy, xxyy, tmp, isect1S, isect1E, isect2S, isect2E; + xx = x0 * x1; + yy = y0 * y1; + xxyy = xx * yy; + + tmp = a * xxyy; + isect1S = tmp + b * x1 * yy; + isect1E = tmp + c * x0 * yy; + + tmp = d * xxyy; + isect2S = tmp + e * y1 * xx; + isect2E = tmp + f * y0 * xx; + + // sort so that isect1S <= isect1E + if(isect1S > isect1E) { + tmp = isect1S; + isect1S = isect1E; + isect1E = tmp; + } + + // sort so that isect2S <= isect2E + if(isect2S > isect2E) { + tmp = isect2S; + isect2S = isect2E; + isect2E = tmp; + } + + if(isect1E<isect2S || isect2E<isect1S) { + // System.out.println("In intersectTriTri :isect1E<isect2S || isect2E<isect1S"); + // System.out.println("In intersectTriTri : return false"); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(n2); + freeVector3d(tempV3d); + return false; + } + + // System.out.println("In intersectTriTri : return true"); + freeVector3d(e1); + freeVector3d(e2); + freeVector3d(n1); + freeVector3d(n2); + freeVector3d(tempV3d); + return true; + + } + + + + boolean intersectPolygon(Point3d coord1[], Point3d coord2[]) { + int i, j; + Vector3d vec0 = getVector3d(); // Edge vector from point 0 to point 1; + Vector3d vec1 = getVector3d(); // Edge vector from point 0 to point 2 or 3; + Vector3d pNrm = getVector3d(); + boolean epFlag; + + + // compute plane normal for coord1. + for(i=0; i<coord1.length-1;) { + vec0.x = coord1[i+1].x - coord1[i].x; + vec0.y = coord1[i+1].y - coord1[i].y; + vec0.z = coord1[i+1].z - coord1[i++].z; + if(vec0.length() > 0.0) + break; + } + + for(j=i; j<coord1.length-1; j++) { + vec1.x = coord1[j+1].x - coord1[j].x; + vec1.y = coord1[j+1].y - coord1[j].y; + vec1.z = coord1[j+1].z - coord1[j].z; + if(vec1.length() > 0.0) + break; + } + + if(j == (coord1.length-1)) { + // System.out.println("(1) Degenerate polygon."); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + return false; // Degenerate polygon. + } + + /* + for(i=0; i<coord1.length; i++) + System.out.println("coord1 P" + i + " " + coord1[i]); + for(i=0; i<coord2.length; i++) + System.out.println("coord2 P" + i + " " + coord2[i]); + */ + + pNrm.cross(vec0,vec1); + + if(pNrm.length() == 0.0) { + // System.out.println("(2) Degenerate polygon."); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + return false; // Degenerate polygon. + } + + j = 0; + Point3d seg[] = new Point3d[2]; + seg[0] = getPoint3d(); + seg[1] = getPoint3d(); + + for(i=0; i<coord2.length; i++) { + if(i < (coord2.length-1)) + epFlag = edgeIntersectPlane(pNrm, coord1[0], coord2[i], + coord2[i+1], seg[j]); + else + epFlag = edgeIntersectPlane(pNrm, coord1[0], coord2[i], + coord2[0], seg[j]); + if (epFlag) { + if(++j>1) { + break; + } + } + } + + if (j==0) { + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freePoint3d(seg[0]); + freePoint3d(seg[1]); + return false; + } + + if (coord2.length < 3) { + boolean toreturn = pointIntersectPolygon2D(pNrm, coord1, seg[0]); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freePoint3d(seg[0]); + freePoint3d(seg[1]); + return toreturn; + } else { + boolean toreturn = edgeIntersectPolygon2D(pNrm, coord1, seg); + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freePoint3d(seg[0]); + freePoint3d(seg[1]); + return toreturn; + } + } + + + /** + * Return true if triangle or quad intersects with ray and the + * distance is stored in dist[0] and the intersect point in iPnt + * (if iPnt is not null). + */ + boolean intersectRay(Point3d coordinates[], PickRay ray, double dist[], + Point3d iPnt) { + + return intersectRayOrSegment(coordinates, ray.direction, ray.origin, + dist, iPnt, false); + + } + + /** + * Return true if triangle or quad intersects with segment and + * the distance is stored in dist[0]. + */ + boolean intersectSegment( Point3d coordinates[], Point3d start, Point3d end, + double dist[], Point3d iPnt ) { + boolean result; + Vector3d direction = getVector3d(); + direction.x = end.x - start.x; + direction.y = end.y - start.y; + direction.z = end.z - start.z; + result = intersectRayOrSegment(coordinates, direction, start, dist, iPnt, true); + freeVector3d(direction); + return result; + } + + + + /** + * Return true if triangle or quad intersects with ray and the distance is + * stored in pr. + */ + boolean intersectRayOrSegment(Point3d coordinates[], + Vector3d direction, Point3d origin, + double dist[], Point3d iPnt, boolean isSegment) { + Vector3d vec0, vec1, pNrm, tempV3d; + vec0 = getVector3d(); + vec1 = getVector3d(); + pNrm = getVector3d(); + + double absNrmX, absNrmY, absNrmZ, pD = 0.0; + double pNrmDotrDir = 0.0; + + boolean isIntersect = false; + int i, j, k=0, l = 0; + + // Compute plane normal. + for (i=0; i<coordinates.length; i++) { + if (i != coordinates.length-1) { + l = i+1; + } else { + l = 0; + } + vec0.x = coordinates[l].x - coordinates[i].x; + vec0.y = coordinates[l].y - coordinates[i].y; + vec0.z = coordinates[l].z - coordinates[i].z; + if (vec0.length() > 0.0) { + break; + } + } + + + for (j=l; j<coordinates.length; j++) { + if (j != coordinates.length-1) { + k = j+1; + } else { + k = 0; + } + vec1.x = coordinates[k].x - coordinates[j].x; + vec1.y = coordinates[k].y - coordinates[j].y; + vec1.z = coordinates[k].z - coordinates[j].z; + if (vec1.length() > 0.0) { + break; + } + } + + pNrm.cross(vec0,vec1); + + if ((vec1.length() == 0) || (pNrm.length() == 0)) { + // degenerate to line if vec0.length() == 0 + // or vec0.length > 0 and vec0 parallel to vec1 + k = (l == 0 ? coordinates.length-1: l-1); + isIntersect = intersectLineAndRay(coordinates[l], + coordinates[k], + origin, + direction, + dist, + iPnt); + + // put the Vectors on the freelist + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + return isIntersect; + } + + // It is possible that Quad is degenerate to Triangle + // at this point + + pNrmDotrDir = pNrm.dot(direction); + + // Ray is parallel to plane. + if (pNrmDotrDir == 0.0) { + // Ray is parallel to plane + // Check line/triangle intersection on plane. + for (i=0; i < coordinates.length ;i++) { + if (i != coordinates.length-1) { + k = i+1; + } else { + k = 0; + } + if (intersectLineAndRay(coordinates[i], + coordinates[k], + origin, + direction, + dist, + iPnt)) { + isIntersect = true; + break; + } + } + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + return isIntersect; + } + + // Plane equation: (p - p0)*pNrm = 0 or p*pNrm = pD; + tempV3d = getVector3d(); + tempV3d.set((Tuple3d) coordinates[0]); + pD = pNrm.dot(tempV3d); + tempV3d.set((Tuple3d) origin); + + // Substitute Ray equation: + // p = origin + pi.distance*direction + // into the above Plane equation + + dist[0] = (pD - pNrm.dot(tempV3d))/ pNrmDotrDir; + + // Ray intersects the plane behind the ray's origin. + if ((dist[0] < -EPS ) || + (isSegment && (dist[0] > 1.0+EPS))) { + // Ray intersects the plane behind the ray's origin + // or intersect point not fall in Segment + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(tempV3d); + return false; + } + + // Now, one thing for sure the ray intersect the plane. + // Find the intersection point. + if (iPnt == null) { + iPnt = new Point3d(); + } + iPnt.x = origin.x + direction.x * dist[0]; + iPnt.y = origin.y + direction.y * dist[0]; + iPnt.z = origin.z + direction.z * dist[0]; + + // Project 3d points onto 2d plane + // Find the axis so that area of projection is maximize. + absNrmX = Math.abs(pNrm.x); + absNrmY = Math.abs(pNrm.y); + absNrmZ = Math.abs(pNrm.z); + + // All sign of (y - y0) (x1 - x0) - (x - x0) (y1 - y0) + // must agree. + double sign, t, lastSign = 0; + Point3d p0 = coordinates[coordinates.length-1]; + Point3d p1 = coordinates[0]; + + isIntersect = true; + + if (absNrmX > absNrmY) { + if (absNrmX < absNrmZ) { + for (i=0; i < coordinates.length; i++) { + p0 = coordinates[i]; + p1 = (i != coordinates.length-1 ? coordinates[i+1]: coordinates[0]); + sign = (iPnt.y - p0.y)*(p1.x - p0.x) - + (iPnt.x - p0.x)*(p1.y - p0.y); + if (isNonZero(sign)) { + if (sign*lastSign < 0) { + isIntersect = false; + break; + } + lastSign = sign; + } else { // point on line, check inside interval + t = p1.y - p0.y; + if (isNonZero(t)) { + t = (iPnt.y - p0.y)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + t = p1.x - p0.x; + if (isNonZero(t)) { + t = (iPnt.x - p0.x)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + // Ignore degenerate line=>point happen when Quad => Triangle. + // Note that by next round sign*lastSign = 0 so it will + // not pass the interest test. This should only happen once in the + // loop because we already check for degenerate geometry before. + lastSign = 0; + } + } + } + } + } else { + for (i=0; i<coordinates.length; i++) { + p0 = coordinates[i]; + p1 = (i != coordinates.length-1 ? coordinates[i+1]: coordinates[0]); + sign = (iPnt.y - p0.y)*(p1.z - p0.z) - + (iPnt.z - p0.z)*(p1.y - p0.y); + if (isNonZero(sign)) { + if (sign*lastSign < 0) { + isIntersect = false; + break; + } + lastSign = sign; + } else { // point on line, check inside interval + t = p1.y - p0.y; + + if (isNonZero(t)) { + t = (iPnt.y - p0.y)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + + } else { + t = p1.z - p0.z; + if (isNonZero(t)) { + t = (iPnt.z - p0.z)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + lastSign = 0; //degenerate line=>point + } + } + } + } + } + } else { + if (absNrmY < absNrmZ) { + for (i=0; i<coordinates.length; i++) { + p0 = coordinates[i]; + p1 = (i != coordinates.length-1 ? coordinates[i+1]: coordinates[0]); + sign = (iPnt.y - p0.y)*(p1.x - p0.x) - + (iPnt.x - p0.x)*(p1.y - p0.y); + if (isNonZero(sign)) { + if (sign*lastSign < 0) { + isIntersect = false; + break; + } + lastSign = sign; + } else { // point on line, check inside interval + t = p1.y - p0.y; + if (isNonZero(t)) { + t = (iPnt.y - p0.y)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + t = p1.x - p0.x; + if (isNonZero(t)) { + t = (iPnt.x - p0.x)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + lastSign = 0; //degenerate line=>point + } + } + } + } + } else { + for (i=0; i<coordinates.length; i++) { + p0 = coordinates[i]; + p1 = (i != coordinates.length-1 ? coordinates[i+1]: coordinates[0]); + sign = (iPnt.x - p0.x)*(p1.z - p0.z) - + (iPnt.z - p0.z)*(p1.x - p0.x); + if (isNonZero(sign)) { + if (sign*lastSign < 0) { + isIntersect = false; + break; + } + lastSign = sign; + } else { // point on line, check inside interval + t = p1.x - p0.x; + if (isNonZero(t)) { + t = (iPnt.x - p0.x)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + t = p1.z - p0.z; + if (isNonZero(t)) { + t = (iPnt.z - p0.z)/t; + isIntersect = ((t > -EPS) && (t < 1+EPS)); + break; + } else { + lastSign = 0; //degenerate line=>point + } + } + } + } + } + } + + if (isIntersect) { + dist[0] *= direction.length(); + } + freeVector3d(vec0); + freeVector3d(vec1); + freeVector3d(pNrm); + freeVector3d(tempV3d); + return isIntersect; + } + + + + static final boolean isNonZero(double v) { + return ((v > EPS) || (v < -EPS)); + + } + + /** + * Return true if point is on the inside of halfspace test. The + * halfspace is partition by the plane of triangle or quad. + */ + boolean inside( Point3d coordinates[], PickPoint point, int ccw ) { + + Vector3d vec0 = new Vector3d(); // Edge vector from point 0 to point 1; + Vector3d vec1 = new Vector3d(); // Edge vector from point 0 to point 2 or 3; + Vector3d pNrm = new Vector3d(); + double absNrmX, absNrmY, absNrmZ, pD = 0.0; + Vector3d tempV3d = new Vector3d(); + double pNrmDotrDir = 0.0; + + double tempD; + + int i, j; + + // Compute plane normal. + for(i=0; i<coordinates.length-1;) { + vec0.x = coordinates[i+1].x - coordinates[i].x; + vec0.y = coordinates[i+1].y - coordinates[i].y; + vec0.z = coordinates[i+1].z - coordinates[i++].z; + if(vec0.length() > 0.0) + break; + } + + for(j=i; j<coordinates.length-1; j++) { + vec1.x = coordinates[j+1].x - coordinates[j].x; + vec1.y = coordinates[j+1].y - coordinates[j].y; + vec1.z = coordinates[j+1].z - coordinates[j].z; + if(vec1.length() > 0.0) + break; + } + + if(j == (coordinates.length-1)) { + // System.out.println("(1) Degenerate polygon."); + return false; // Degenerate polygon. + } + + /* + System.out.println("Ray orgin : " + ray.origin + " dir " + ray.direction); + System.out.println("Triangle/Quad :"); + for(i=0; i<coordinates.length; i++) + System.out.println("P" + i + " " + coordinates[i]); + */ + + if( ccw == 0x1) + pNrm.cross(vec0,vec1); + else + pNrm.cross(vec1,vec0); + + if(pNrm.length() == 0.0) { + // System.out.println("(2) Degenerate polygon."); + return false; // Degenerate polygon. + } + // Compute plane D. + tempV3d.set((Tuple3d) coordinates[0]); + pD = pNrm.dot(tempV3d); + tempV3d.set((Tuple3d) point.location); + + return ((pD - pNrm.dot(tempV3d)) <= 0); + } + + boolean intersectPntAndPnt( Point3d pnt1, Point3d pnt2 ) { + return ((pnt1.x == pnt2.x) && + (pnt1.y == pnt2.y) && + (pnt1.z == pnt2.z)); + } + + boolean intersectPntAndRay(Point3d pnt, Point3d ori, Vector3d dir, + double dist[]) { + int flag = 0; + double temp; + + if(dir.x != 0.0) { + flag = 0; + dist[0] = (pnt.x - ori.x)/dir.x; + } + else if(dir.y != 0.0) { + if(pnt.x != ori.x) + return false; + flag = 1; + dist[0] = (pnt.y - ori.y)/dir.y; + } + else if(dir.z != 0.0) { + if((pnt.x != ori.x)||(pnt.y != ori.y)) + return false; + flag = 2; + dist[0] = (pnt.z - ori.z)/dir.z; + + } + else + return false; + + if(dist[0] < 0.0) + return false; + + if(flag == 0) { + temp = ori.y + dist[0] * dir.y; + if((pnt.y < (temp - EPS)) || (pnt.y > (temp + EPS))) + return false; + } + + if(flag < 2) { + temp = ori.z + dist[0] * dir.z; + if((pnt.z < (temp - EPS)) || (pnt.z > (temp + EPS))) + return false; + } + + return true; + + } + + boolean intersectLineAndRay(Point3d start, Point3d end, + Point3d ori, Vector3d dir, double dist[], + Point3d iPnt) { + + double m00, m01, m10, m11; + double mInv00, mInv01, mInv10, mInv11; + double dmt, t, s, tmp1, tmp2; + Vector3d lDir; + + // System.out.println("GeometryArrayRetained : intersectLineAndRay"); + // System.out.println("start " + start + " end " + end ); + // System.out.println("ori " + ori + " dir " + dir); + + lDir = getVector3d(); + lDir.x = (end.x - start.x); + lDir.y = (end.y - start.y); + lDir.z = (end.z - start.z); + + m00 = lDir.x; + m01 = -dir.x; + m10 = lDir.y; + m11 = -dir.y; + + // Get the determinant. + dmt = (m00 * m11) - (m10 * m01); + + if (dmt==0.0) { // No solution, check degenerate line + boolean isIntersect = false; + if ((lDir.x == 0) && (lDir.y == 0) && (lDir.z == 0)) { + isIntersect = intersectPntAndRay(start, ori, dir, dist); + if (isIntersect && (iPnt != null)) { + iPnt.set(start); + } + } + freeVector3d(lDir); + return isIntersect; + } + // Find the inverse. + tmp1 = 1/dmt; + + mInv00 = tmp1 * m11; + mInv01 = tmp1 * (-m01); + mInv10 = tmp1 * (-m10); + mInv11 = tmp1 * m00; + + tmp1 = ori.x - start.x; + tmp2 = ori.y - start.y; + + t = mInv00 * tmp1 + mInv01 * tmp2; + s = mInv10 * tmp1 + mInv11 * tmp2; + + if(s<0.0) { // Before the origin of ray. + // System.out.println("Before the origin of ray " + s); + freeVector3d(lDir); + return false; + } + if((t<0)||(t>1.0)) {// Before or after the end points of line. + // System.out.println("Before or after the end points of line. " + t); + freeVector3d(lDir); + return false; + } + + tmp1 = ori.z + s * dir.z; + tmp2 = start.z + t * lDir.z; + + if((tmp1 < (tmp2 - EPS)) || (tmp1 > (tmp2 + EPS))) { + // System.out.println("No intersection : tmp1 " + tmp1 + " tmp2 " + tmp2); + freeVector3d(lDir); + return false; + } + + dist[0] = s; + + if (iPnt != null) { + // compute point of intersection. + iPnt.x = ori.x + dir.x * dist[0]; + iPnt.y = ori.y + dir.y * dist[0]; + iPnt.z = ori.z + dir.z * dist[0]; + } + + // System.out.println("Intersected : tmp1 " + tmp1 + " tmp2 " + tmp2); + freeVector3d(lDir); + return true; + } + + /** + Return true if triangle or quad intersects with cylinder. The + distance is stored in dist. + */ + boolean intersectCylinder(Point3d coordinates[], PickCylinder cyl, + double dist[], Point3d iPnt) { + + Point3d origin = getPoint3d(); + Point3d end = getPoint3d(); + Vector3d direction = getVector3d(); + Point3d iPnt1 = getPoint3d(); + Vector3d originToIpnt = getVector3d(); + + if (iPnt == null) { + iPnt = new Point3d(); + } + + // Get cylinder information + cyl.getOrigin (origin); + cyl.getDirection (direction); + double radius = cyl.getRadius (); + + if (cyl instanceof PickCylinderSegment) { + ((PickCylinderSegment)cyl).getEnd (end); + } + + // If the ray intersects, we're good (do not do this if we only have + // a segment + if (coordinates.length > 2) { + if (cyl instanceof PickCylinderRay) { + if (intersectRay(coordinates, + new PickRay(origin, direction), + dist, iPnt)) { + + freePoint3d(origin); + freePoint3d(end); + freeVector3d(direction); + freeVector3d(originToIpnt); + freePoint3d(iPnt1); + + return true; + } + } + else { + if (intersectSegment(coordinates, origin, end, dist, iPnt)) { + freePoint3d(origin); + freePoint3d(end); + freeVector3d(direction); + freeVector3d(originToIpnt); + freePoint3d(iPnt1); + return true; + } + } + } + + // Ray doesn't intersect, check distance to edges + double sqDistToEdge; + int j; + for (int i=0; i<coordinates.length;i++) { + j = (i < coordinates.length-1 ? i+1: 0); + if (cyl instanceof PickCylinderSegment) { + sqDistToEdge = + Distance.segmentToSegment(origin, end, + coordinates[i], coordinates[j], + iPnt1, iPnt, null); + } + else { + sqDistToEdge = + Distance.rayToSegment(origin, direction, + coordinates[i], coordinates[j], + iPnt1, iPnt, null); + } + if (sqDistToEdge <= radius*radius) { + originToIpnt.sub (iPnt1, origin); + dist[0] = originToIpnt.length(); + freePoint3d(origin); + freePoint3d(end); + freeVector3d(direction); + freeVector3d(originToIpnt); + freePoint3d(iPnt1); + return true; + } + } + freePoint3d(origin); + freePoint3d(end); + freeVector3d(direction); + freeVector3d(originToIpnt); + freePoint3d(iPnt1); + return false; + } + + /** + Return true if triangle or quad intersects with cone. The + distance is stored in dist. + */ + boolean intersectCone(Point3d coordinates[], PickCone cone, + double[] dist, Point3d iPnt) { + + Point3d origin = getPoint3d(); + Point3d end = getPoint3d(); + Vector3d direction = getVector3d(); + Vector3d originToIpnt = getVector3d(); + double distance; + + Point3d iPnt1 = getPoint3d(); + Vector3d vector = getVector3d(); + + if (iPnt == null) { + iPnt = new Point3d(); + } + // Get cone information + cone.getOrigin (origin); + cone.getDirection (direction); + double radius; + + if (cone instanceof PickConeSegment) { + ((PickConeSegment)cone).getEnd (end); + } + + // If the ray intersects, we're good (do not do this if we only have + // a segment + if (coordinates.length > 2) { + if (cone instanceof PickConeRay) { + if (intersectRay(coordinates, + new PickRay (origin, direction), + dist, iPnt)) { + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt1); + freeVector3d(direction); + freeVector3d(originToIpnt); + freeVector3d(vector); + return true; + } + } + else { + if (intersectSegment(coordinates, origin, end, dist, iPnt)) { + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt1); + freeVector3d(direction); + freeVector3d(originToIpnt); + freeVector3d(vector); + return true; + } + } + } + + // Ray doesn't intersect, check distance to edges + double sqDistToEdge; + int j = 0; + for (int i=0; i<coordinates.length;i++) { + j = (i < coordinates.length-1 ? i+1: 0); + if (cone instanceof PickConeSegment) { + sqDistToEdge = + Distance.segmentToSegment (origin, end, + coordinates[i], coordinates[j], + iPnt1, iPnt, null); + } + else { + sqDistToEdge = + Distance.rayToSegment (origin, direction, + coordinates[i], coordinates[j], + iPnt1, iPnt, null); + } + originToIpnt.sub(iPnt1, origin); + distance = originToIpnt.length(); + radius = Math.tan (cone.getSpreadAngle()) * distance; + if (sqDistToEdge <= radius*radius) { + // System.out.println ("intersectCone: edge "+i+" intersected"); + dist[0] = distance; + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt1); + freeVector3d(direction); + freeVector3d(originToIpnt); + freeVector3d(vector); + return true; + } + } + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt1); + freeVector3d(direction); + freeVector3d(originToIpnt); + freeVector3d(vector); + return false; + } + + + /** + Return true if point intersects with cylinder and the distance is + stored in dist. + */ + boolean intersectCylinder(Point3d pt, PickCylinder cyl, + double[] dist) { + + Point3d origin = getPoint3d(); + Point3d end = getPoint3d(); + Vector3d direction = getVector3d(); + Point3d iPnt = getPoint3d(); + Vector3d originToIpnt = getVector3d(); + + // Get cylinder information + cyl.getOrigin (origin); + cyl.getDirection (direction); + double radius = cyl.getRadius (); + double sqDist; + + if (cyl instanceof PickCylinderSegment) { + ((PickCylinderSegment)cyl).getEnd (end); + sqDist = Distance.pointToSegment(pt, origin, end, iPnt, null); + } + else { + sqDist = Distance.pointToRay(pt, origin, direction, iPnt, null); + } + if (sqDist <= radius*radius) { + originToIpnt.sub (iPnt, origin); + dist[0] = originToIpnt.length(); + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt); + freeVector3d(originToIpnt); + freeVector3d(direction); + return true; + } + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt); + freeVector3d(originToIpnt); + freeVector3d(direction); + return false; + } + + /** + Return true if point intersects with cone and the + distance is stored in pi. + */ + boolean intersectCone(Point3d pt, PickCone cone, double[] dist) + { + Point3d origin = getPoint3d(); + Point3d end = getPoint3d(); + Vector3d direction = getVector3d(); + Point3d iPnt = getPoint3d(); + Vector3d originToIpnt = getVector3d(); + + // Get cone information + cone.getOrigin (origin); + cone.getDirection (direction); + double radius; + double distance; + double sqDist; + + if (iPnt == null) { + iPnt = new Point3d(); + } + + if (cone instanceof PickConeSegment) { + ((PickConeSegment)cone).getEnd (end); + sqDist = Distance.pointToSegment (pt, origin, end, iPnt, null); + } + else { + sqDist = Distance.pointToRay (pt, origin, direction, iPnt, null); + } + originToIpnt.sub(iPnt, origin); + distance = originToIpnt.length(); + radius = Math.tan (cone.getSpreadAngle()) * distance; + if (sqDist <= radius*radius) { + dist[0] = distance; + freePoint3d(origin); + freePoint3d(end); + freePoint3d(iPnt); + freeVector3d(direction); + freeVector3d(originToIpnt); + return true; + } + return false; + } + + + void setCoordRefBuffer(J3DBuffer coords) { + if (coords != null) { + switch (coords.getBufferType()) { + case J3DBuffer.TYPE_FLOAT: + if ( !((FloatBufferWrapper)coords.getBufferImpl()).isDirect()) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray120")); + + // TODO: may need to check whether it is direct and if so, + // whether it is consistent with native byte order + break; + case J3DBuffer.TYPE_DOUBLE: + if ( !((DoubleBufferWrapper)coords.getBufferImpl()).isDirect()) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray120")); + + // TODO: may need to check whether it is direct and if so, + // whether it is consistent with native byte order + break; + case J3DBuffer.TYPE_NULL: + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray115")); + + default: + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray116")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + if (3 * idx.maxCoordIndex >= coords.getBufferImpl().limit()) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } else if (coords.getBufferImpl().limit() < (3*(initialCoordIndex+validVertexCount))) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + } + + //throw new RuntimeException("method not implemeted"); + // lock the geometry and start to do real work + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + coordRefBuffer = coords; + if(coords == null) { + floatBufferRefCoords = null; + doubleBufferRefCoords = null; + // TODO: if not mix java array with nio buffer + // vertexType can be used as vertexTypeBuffer + vertexType &= ~PD; + vertexType &= ~PF; + }else { + switch (coords.getBufferType()) { + case J3DBuffer.TYPE_FLOAT: + floatBufferRefCoords = + (FloatBufferWrapper)coords.getBufferImpl(); + doubleBufferRefCoords = null; + vertexType |= PF; + vertexType &= ~PD; + break; + case J3DBuffer.TYPE_DOUBLE: + floatBufferRefCoords = null; + doubleBufferRefCoords = + (DoubleBufferWrapper)coords.getBufferImpl(); + vertexType |= PD; + vertexType &= ~PF; + break; + default: + break; + } + } + + // need not call setupMirrorVertexPointer() since + // we are not going to set mirror in NIO buffer case + // TODO: if we need to mix java array with buffer, + // we may need to consider setupMirrorVertexPointer() + + geomLock.unLock(); + + if (!inUpdater && source != null) { + if (source.isLive()) { + processCoordsChanged((coords == null)); + sendDataChangedMessage(true); + } else { + boundsDirty = true; + } + } + + } + + + J3DBuffer getCoordRefBuffer() { + return coordRefBuffer; + } + + + void setCoordRefFloat(float[] coords) { + + // If non-null coordinate and vertType is either defined + // to be something other than PF, then issue an error + if (coords != null) { + if ((vertexType & VERTEX_DEFINED) != 0 && + (vertexType & VERTEX_DEFINED) != PF) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (3 * idx.maxCoordIndex >= coords.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } else if (coords.length < 3 * (initialCoordIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + } + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + + floatRefCoords = coords; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (coords == null) + vertexType &= ~PF; + else + vertexType |= PF; + } + else { + setupMirrorVertexPointer(PF); + } + + geomLock.unLock(); + + if (!inUpdater && source != null) { + if (source.isLive()) { + processCoordsChanged(coords == null); + sendDataChangedMessage(true); + } else { + boundsDirty = true; + } + } + } + + + float[] getCoordRefFloat() { + return floatRefCoords; + } + + + void setCoordRefDouble(double[] coords) { + + if (coords != null) { + if ((vertexType & VERTEX_DEFINED) != 0 && + (vertexType & VERTEX_DEFINED) != PD) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + if (3 * idx.maxCoordIndex >= coords.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } else if (coords.length < 3 * (initialCoordIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + } + + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + doubleRefCoords = coords; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (coords == null) + vertexType &= ~PD; + else + vertexType |= PD; + } + else { + setupMirrorVertexPointer(PD); + } + geomLock.unLock(); + + if (!inUpdater && source != null) { + if (source.isLive()) { + processCoordsChanged(coords == null); + sendDataChangedMessage(true); + } else { + boundsDirty = true; + } + } + } + + double[] getCoordRefDouble() { + return doubleRefCoords; + } + + void setCoordRef3f(Point3f[] coords) { + + if (coords != null) { + if ((vertexType & VERTEX_DEFINED) != 0 && + (vertexType & VERTEX_DEFINED) != P3F) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (idx.maxCoordIndex >= coords.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } else if (coords.length < (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + } + + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + p3fRefCoords = coords; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (coords == null) + vertexType &= ~P3F; + else + vertexType |= P3F; + } + else { + setupMirrorVertexPointer(P3F); + } + geomLock.unLock(); + + if (!inUpdater && source != null) { + if (source.isLive()) { + processCoordsChanged(coords == null); + sendDataChangedMessage(true); + } else { + boundsDirty = true; + } + } + } + + Point3f[] getCoordRef3f() { + return p3fRefCoords; + + } + + void setCoordRef3d(Point3d[] coords) { + + if (coords != null) { + if ((vertexType & VERTEX_DEFINED) != 0 && + (vertexType & VERTEX_DEFINED) != P3D) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (idx.maxCoordIndex >= coords.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } else if (coords.length < (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + } + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + p3dRefCoords = coords; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (coords == null) + vertexType &= ~P3D; + else + vertexType |= P3D; + } else { + setupMirrorVertexPointer(P3D); + } + geomLock.unLock(); + + if (!inUpdater && source != null) { + if (source.isLive()) { + processCoordsChanged(coords == null); + sendDataChangedMessage(true); + } else { + boundsDirty = true; + } + } + } + + Point3d[] getCoordRef3d() { + return p3dRefCoords; + } + + void setColorRefFloat(float[] colors) { + + if (colors != null) { + if ((vertexType & COLOR_DEFINED) != 0 && + (vertexType & COLOR_DEFINED) != CF) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.COLOR) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray123")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (getColorStride() * idx.maxColorIndex >= colors.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } else if (colors.length < getColorStride() * (initialColorIndex+ validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + floatRefColors = colors; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (colors == null) + vertexType &= ~CF; + else + vertexType |= CF; + } + else { + setupMirrorColorPointer(CF, false); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + + } + + float[] getColorRefFloat() { + return floatRefColors; + } + + + // set the color with nio buffer + void setColorRefBuffer(J3DBuffer colors) { + if (colors != null) { + switch(colors.getBufferType()) { + case J3DBuffer.TYPE_FLOAT: + if ( !((FloatBufferWrapper)colors.getBufferImpl()).isDirect()) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray120")); + break; + case J3DBuffer.TYPE_BYTE: + if ( !((ByteBufferWrapper)colors.getBufferImpl()).isDirect()) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray120")); + break; + case J3DBuffer.TYPE_NULL: + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray115")); + + default: + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray116")); + } + + if ((vertexFormat & GeometryArray.COLOR) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray123")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (getColorStride() * idx.maxColorIndex >= colors.getBufferImpl().limit()) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } else if (colors.getBufferImpl().limit() < + getColorStride() * (initialColorIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + colorRefBuffer = colors; + if(colors == null) { + floatBufferRefColors = null; + byteBufferRefColors = null; + } else { + switch (colors.getBufferType()) { + case J3DBuffer.TYPE_FLOAT: + floatBufferRefColors = (FloatBufferWrapper)colors.getBufferImpl(); + byteBufferRefColors = null; + break; + + case J3DBuffer.TYPE_BYTE: + byteBufferRefColors = (ByteBufferWrapper)colors.getBufferImpl(); + floatBufferRefColors = null; + break; + default: + break; + } + } + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if(colors == null) { + vertexType &= ~CF; + vertexType &= ~CUB; + } else { + switch (colors.getBufferType()) { + case J3DBuffer.TYPE_FLOAT: + vertexType |= CF; + vertexType &= ~CUB; + break; + + case J3DBuffer.TYPE_BYTE: + vertexType |= CUB; + vertexType &= ~CF; + break; + default: + break; + } + } + } + else { + setupMirrorColorPointer(CF|CUB, false); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + // return the color data in nio buffer format + J3DBuffer getColorRefBuffer() { + return colorRefBuffer; + } + + void setColorRefByte(byte[] colors) { + + if (colors != null) { + if ((vertexType & COLOR_DEFINED) != 0 && + (vertexType & COLOR_DEFINED) != CUB) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.COLOR) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray123")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (getColorStride() * idx.maxColorIndex >= colors.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } else if (colors.length < getColorStride() * (initialColorIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + byteRefColors = colors; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (colors == null) + vertexType &= ~CUB; + else + vertexType |= CUB; + } + else { + setupMirrorColorPointer(CUB, false); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + + } + + byte[] getColorRefByte() { + return byteRefColors; + } + + void setColorRef3f(Color3f[] colors) { + + if (colors != null) { + if ((vertexType & COLOR_DEFINED) != 0 && + (vertexType & COLOR_DEFINED) != C3F) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.COLOR_3) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + if (idx.maxColorIndex >= colors.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } else if (colors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + c3fRefColors = colors; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (colors == null) + vertexType &= ~C3F; + else + vertexType |= C3F; + } + else { + setupMirrorColorPointer(C3F, false); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + + } + + Color3f[] getColorRef3f() { + return c3fRefColors; + } + + + void setColorRef4f(Color4f[] colors) { + + if (colors != null) { + if ((vertexType & COLOR_DEFINED) != 0 && + (vertexType & COLOR_DEFINED) != C4F) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + if ((vertexFormat & GeometryArray.COLOR_4) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + if (idx.maxColorIndex >= colors.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } else if (colors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + c4fRefColors = colors; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (colors == null) + vertexType &= ~C4F; + else + vertexType |= C4F; + } + else { + setupMirrorColorPointer(C4F, false); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + Color4f[] getColorRef4f() { + return c4fRefColors; + } + + + void setColorRef3b(Color3b[] colors) { + + if (colors != null) { + + if ((vertexType & COLOR_DEFINED) != 0 && + (vertexType & COLOR_DEFINED) != C3UB) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.COLOR_3) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray92")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (idx.maxColorIndex >= colors.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } else if (colors.length < (initialColorIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + c3bRefColors = colors; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (colors == null) + vertexType &= ~C3UB; + else + vertexType |= C3UB; + } + else { + setupMirrorColorPointer(C3UB, false); + } + + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + + Color3b[] getColorRef3b() { + return c3bRefColors; + } + + void setColorRef4b(Color4b[] colors) { + + if (colors != null) { + if ((vertexType & COLOR_DEFINED) != 0 && + (vertexType & COLOR_DEFINED) != C4UB) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.COLOR_4) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray93")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained) this; + + if (idx.maxColorIndex >= colors.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } else if (colors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + c4bRefColors = colors; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (colors == null) + vertexType &= ~C4UB; + else + vertexType |= C4UB; + } + else { + setupMirrorColorPointer(C4UB, false); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + + Color4b[] getColorRef4b() { + return c4bRefColors; + } + + void setNormalRefFloat(float[] normals) { + + if (normals != null) { + if ((vertexType & NORMAL_DEFINED) != 0 && + (vertexType & NORMAL_DEFINED) != NF) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.NORMALS) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray122")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (idx.maxNormalIndex*3 >= normals.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + } else if (normals.length < 3 * (initialNormalIndex + validVertexCount )) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + } + geomLock.getLock(); + dirtyFlag |= NORMAL_CHANGED; + floatRefNormals = normals; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (normals == null) + vertexType &= ~NF; + else + vertexType |= NF; + } + else { + setupMirrorNormalPointer(NF); + } + geomLock.unLock(); + + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + + } + + float[] getNormalRefFloat() { + return floatRefNormals; + } + + // setup the normal with nio buffer + void setNormalRefBuffer(J3DBuffer normals) { + + FloatBufferWrapper bufferImpl = null; + + if (normals != null) { + if(normals.getBufferType() != J3DBuffer.TYPE_FLOAT) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray116")); + + bufferImpl = (FloatBufferWrapper)normals.getBufferImpl(); + + if ( ! bufferImpl.isDirect()) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray120")); + + if ((vertexFormat & GeometryArray.NORMALS) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray122")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + if (idx.maxNormalIndex * 3 >= + ((FloatBufferWrapper)normals.getBufferImpl()).limit()) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + } else if (bufferImpl.limit() < 3 * (initialNormalIndex + validVertexCount )) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + } + geomLock.getLock(); + dirtyFlag |= NORMAL_CHANGED; + normalRefBuffer = normals; + + if (normals == null) { + vertexType &= ~NF; + floatBufferRefNormals = null; + } + else { + vertexType |= NF; + floatBufferRefNormals = bufferImpl; + } + geomLock.unLock(); + + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + J3DBuffer getNormalRefBuffer() { + return normalRefBuffer; + } + + void setNormalRef3f(Vector3f[] normals) { + + if (normals != null) { + if ((vertexType & NORMAL_DEFINED) != 0 && + (vertexType & NORMAL_DEFINED) != N3F) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.NORMALS) == 0) { + throw new IllegalStateException(J3dI18N.getString("GeometryArray122")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + if (idx.maxNormalIndex >= normals.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + } else if (normals.length < (initialNormalIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + } + geomLock.getLock(); + dirtyFlag |= NORMAL_CHANGED; + v3fRefNormals = normals; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (normals == null) + vertexType &= ~N3F; + else + vertexType |= N3F; + } + else { + setupMirrorNormalPointer(N3F); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + Vector3f[] getNormalRef3f() { + return v3fRefNormals; + } + + final int getColorStride() { + return ((vertexFormat & GeometryArray.WITH_ALPHA) != 0 ? 4 : 3); + } + + final int getTexStride() { + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + return 2; + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + return 3; + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + return 4; + } + + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray121")); + } + + void setTexCoordRefFloat(int texCoordSet, float[] texCoords) { + + if (texCoords != null) { + + if ((vertexType & TEXCOORD_DEFINED) != 0 && + (vertexType & TEXCOORD_DEFINED) != TF) { + throw new IllegalArgumentException( + J3dI18N.getString("GeometryArray98")); + } + + int ts = getTexStride(); + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (idx.maxTexCoordIndices[texCoordSet]*ts >= texCoords.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + } else if (texCoords.length < ts*(initialTexCoordIndex[texCoordSet]+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray113")); + } + } + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + refTexCoords[texCoordSet] = texCoords; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (texCoords == null) + vertexType &= ~TF; + else + vertexType |= TF; + } + else { + setupMirrorTexCoordPointer(texCoordSet, TF); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + + + float[] getTexCoordRefFloat(int texCoordSet) { + return ((float[])refTexCoords[texCoordSet]); + } + + // set the tex coord with nio buffer + void setTexCoordRefBuffer(int texCoordSet, J3DBuffer texCoords) { + + FloatBufferWrapper bufferImpl = null; + + if (texCoords != null) { + if(texCoords.getBufferType() != J3DBuffer.TYPE_FLOAT) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray116")); + + bufferImpl = (FloatBufferWrapper)texCoords.getBufferImpl(); + int bufferSize = bufferImpl.limit(); + + if ( ! bufferImpl.isDirect()) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray120")); + + int ts = getTexStride(); + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + if (idx.maxTexCoordIndices[texCoordSet] * ts >= bufferSize) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + } else if (bufferSize < ts*(initialTexCoordIndex[texCoordSet] + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray113")); + } + } + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + // refTexCoordsBuffer contains J3DBuffer object for tex coord + refTexCoordsBuffer[texCoordSet] = texCoords; + if (texCoords == null) { + vertexType &= ~TF; + refTexCoords[texCoordSet] = null; + } + else { + vertexType |= TF; + // refTexCoords contains NIOBuffer object for tex coord + refTexCoords[texCoordSet] = bufferImpl.getBufferAsObject(); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + J3DBuffer getTexCoordRefBuffer(int texCoordSet) { + return (J3DBuffer)(refTexCoordsBuffer[texCoordSet]); + } + + void setTexCoordRef2f(int texCoordSet, TexCoord2f[] texCoords) { + + if (texCoords != null) { + if ((vertexType & TEXCOORD_DEFINED) != 0 && + (vertexType & TEXCOORD_DEFINED) != T2F) { + throw new IllegalArgumentException( + J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) == 0) { + throw new IllegalStateException( + J3dI18N.getString("GeometryArray94")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (idx.maxTexCoordIndices[texCoordSet] >= texCoords.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + } else if (texCoords.length < (initialTexCoordIndex[texCoordSet] + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray113")); + } + + } + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + refTexCoords[texCoordSet] = texCoords; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (texCoords == null) + vertexType &= ~T2F; + else + vertexType |= T2F; + } + else { + setupMirrorTexCoordPointer(T2F); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + + TexCoord2f[] getTexCoordRef2f(int texCoordSet) { + if (refTexCoords != null && refTexCoords[texCoordSet] != null && + refTexCoords[texCoordSet] instanceof TexCoord2f[]) { + return ((TexCoord2f[])refTexCoords[texCoordSet]); + } else { + return null; + } + } + + + void setTexCoordRef3f(int texCoordSet, TexCoord3f[] texCoords) { + + if (texCoords != null) { + + if ((vertexType & TEXCOORD_DEFINED) != 0 && + (vertexType & TEXCOORD_DEFINED) != T3F) { + throw new IllegalArgumentException( + J3dI18N.getString("GeometryArray98")); + } + + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) == 0) { + throw new IllegalStateException( + J3dI18N.getString("GeometryArray95")); + } + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (idx.maxTexCoordIndices[texCoordSet] >= texCoords.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + + } else if (texCoords.length < (initialTexCoordIndex[texCoordSet] + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray113")); + } + + } + + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + refTexCoords[texCoordSet] = texCoords; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + if (texCoords == null) + vertexType &= ~T3F; + else + vertexType |= T3F; + } + else { + setupMirrorTexCoordPointer(T3F); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + + + TexCoord3f[] getTexCoordRef3f(int texCoordSet) { + if (refTexCoords != null && refTexCoords[texCoordSet] != null && + refTexCoords[texCoordSet] instanceof TexCoord3f[]) { + return ((TexCoord3f[])refTexCoords[texCoordSet]); + } else { + return null; + } + } + + + void setInterleavedVertices(float[] vertexData) { + if (vertexData != null) { + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (stride * idx.maxCoordIndex >= vertexData.length) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + if (stride * idx.maxTexCoordIndices[i] >= vertexData.length) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("IndexedGeometryArray25")); + } + } + } + + if (((this.vertexFormat & GeometryArray.COLOR) != 0) && + (stride * idx.maxColorIndex >= vertexData.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + + if (((this.vertexFormat & GeometryArray.NORMALS) != 0) && + (stride * idx.maxNormalIndex >= vertexData.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + } else { + if (vertexData.length < (stride * (initialVertexIndex+validVertexCount))) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray114")); + } + } + + // If the geometry has been rendered transparent, then make a copy + // of the color pointer with 4f + geomLock.getLock(); + dirtyFlag |= VERTEX_CHANGED; + colorChanged = 0xffff; + interLeavedVertexData = vertexData; + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + setupMirrorInterleavedColorPointer(false); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + processCoordsChanged(vertexData == null); + sendDataChangedMessage(true); + } + } + + // set the interleaved vertex with NIO buffer + void setInterleavedVertexBuffer(J3DBuffer vertexData) { + + FloatBufferWrapper bufferImpl = null; + + if (vertexData != null ){ + + if (vertexData.getBufferType() != J3DBuffer.TYPE_FLOAT) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray116")); + + bufferImpl = (FloatBufferWrapper)vertexData.getBufferImpl(); + + if (!bufferImpl.isDirect()) + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray120")); + + int bufferSize = bufferImpl.limit(); + + if (this instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained idx = (IndexedGeometryArrayRetained)this; + + if (stride * idx.maxCoordIndex >= bufferSize) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + + if ((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + if (stride * idx.maxTexCoordIndices[i] >= bufferSize) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("IndexedGeometryArray25")); + } + } + } + + if (((this.vertexFormat & GeometryArray.COLOR) != 0) && + (stride * idx.maxColorIndex >= bufferSize)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + + if (((this.vertexFormat & GeometryArray.NORMALS) != 0) && + (stride * idx.maxNormalIndex >= bufferSize)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } else { + if (bufferSize < (stride * (initialVertexIndex+validVertexCount))) + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray114")); + } + } + // If the geometry has been rendered transparent, then make a copy + // of the color pointer with 4f + geomLock.getLock(); + dirtyFlag |= VERTEX_CHANGED; + colorChanged = 0xffff; + interleavedVertexBuffer = vertexData; + + if(vertexData == null) + interleavedFloatBufferImpl = null; + else + interleavedFloatBufferImpl = bufferImpl; + + if (inUpdater || (this instanceof IndexedGeometryArrayRetained && + ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0))) { + setupMirrorInterleavedColorPointer(false); + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + processCoordsChanged(vertexData == null); + sendDataChangedMessage(true); + } + } + + float[] getInterleavedVertices() { + return interLeavedVertexData; + } + + J3DBuffer getInterleavedVertexBuffer() { + return interleavedVertexBuffer; + } + + void setValidVertexCount(int validVertexCount) { + boolean nullGeo = false; + if (validVertexCount < 0) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray110")); + } + if ((initialVertexIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray100")); + } + else if ((initialCoordIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray104")); + } + else if ((initialColorIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray101")); + } + else if ((initialNormalIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray102")); + } + else { + if ((vertexFormat & (GeometryArray.BY_REFERENCE|vertexFormat &GeometryArray.INTERLEAVED)) == GeometryArray.BY_REFERENCE) { + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + if ((initialTexCoordIndex[i] + validVertexCount) + > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString( + "GeometryArray103")); + } + } + } + } + } + if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + // use nio buffer for interleaved data + if(( vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0 && interleavedFloatBufferImpl != null){ + if(interleavedFloatBufferImpl.limit() < stride * (initialVertexIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray114")); + } + } + //use java array for interleaved data + else if( interLeavedVertexData != null) { + if(interLeavedVertexData.length < stride * (initialVertexIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray114")); + } + } + else { + nullGeo = true; + } + } else if ((vertexFormat & GeometryArray.BY_REFERENCE) != 0) { + if ((vertexType & GeometryArrayRetained.VERTEX_DEFINED) == 0) + nullGeo = true; + + if(( vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { + // by reference with nio buffer + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case PF: + if(floatBufferRefCoords.limit() < 3 * (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + case PD: + if(doubleBufferRefCoords.limit() < 3 * (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + } + + switch ((vertexType & COLOR_DEFINED)) { + case CF: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + if (floatBufferRefColors.limit() < 3 * (initialColorIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + else if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + if (floatBufferRefColors.limit() < 4 * (initialColorIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + break; + case CUB: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + if (byteBufferRefColors.limit() < 3 * (initialColorIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + else if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + if (byteBufferRefColors.limit() < 4 * (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + break; + } + switch ((vertexType & GeometryArrayRetained.TEXCOORD_DEFINED)) { + case TF: + FloatBufferWrapper texBuffer; + for (int i = 0; i < texCoordSetCount; i++) { + texBuffer = (FloatBufferWrapper)(((J3DBuffer)refTexCoordsBuffer[i]).getBufferImpl()); + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + if (texBuffer.limit() < 2 * (initialTexCoordIndex[i] + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + if (texBuffer.limit() < 3 * (initialTexCoordIndex[i] + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + if (texBuffer.limit() < 4 * (initialTexCoordIndex[i] + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } + } + break; + } + switch ((vertexType & GeometryArrayRetained.NORMAL_DEFINED)) { + case NF: + if (floatBufferRefNormals.limit() < 3 * (initialNormalIndex + validVertexCount )) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + break; + } + } + // By reference with java array + else { + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case PF: + if (floatRefCoords.length < 3 * (initialCoordIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + case PD: + if (doubleRefCoords.length < 3 * (initialCoordIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + case P3F: + if (p3fRefCoords.length < (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + case P3D: + if (p3dRefCoords.length < (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + } + switch ((vertexType & COLOR_DEFINED)) { + case CF: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + if (floatRefColors.length < 3 * (initialColorIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + else if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + if (floatRefColors.length < 4 * (initialColorIndex+ validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + break; + case CUB: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + if (byteRefColors.length < 3 * (initialColorIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + else if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + if (byteRefColors.length < 4 * (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + break; + case C3F: + if (c3fRefColors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + break; + case C4F: + if (c4fRefColors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + break; + case C3UB: + if (c3bRefColors.length < (initialColorIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + break; + case C4UB: + if (c4bRefColors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + break; + } + switch ((vertexType & GeometryArrayRetained.TEXCOORD_DEFINED)) { + case TF: + for (int i = 0; i < texCoordSetCount; i++) { + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + if (((float[])refTexCoords[i]).length < 2 * (initialTexCoordIndex[i] + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + if (((float[])refTexCoords[i]).length < 3 * (initialTexCoordIndex[i] + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + if (((float[])refTexCoords[i]).length < 4 * (initialTexCoordIndex[i] + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } + } + } + break; + case T2F: + for (int i = 0; i < texCoordSetCount; i++) { + if (((TexCoord2f[])refTexCoords[i]).length < (initialTexCoordIndex[i] + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } + break; + case T3F: + for (int i = 0; i < texCoordSetCount; i++) { + if (((TexCoord3f[])refTexCoords[i]).length < (initialTexCoordIndex[i] + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } + break; + } + switch ((vertexType & GeometryArrayRetained.NORMAL_DEFINED)) { + case NF: + if (floatRefNormals.length < 3 * (initialNormalIndex + validVertexCount )) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + break; + case N3F: + if (v3fRefNormals.length < (initialNormalIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + } + } + } + + geomLock.getLock(); + dirtyFlag |= VERTEX_CHANGED; + this.validVertexCount = validVertexCount; + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + processCoordsChanged(nullGeo); + sendDataChangedMessage(true); + } + + } + + + int getValidVertexCount() { + return validVertexCount; + } + + //Used for interleaved data (array or nio buffer) + void setInitialVertexIndex(int initialVertexIndex) { + boolean nullGeo = false; + + if ((initialVertexIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray100")); + } + + if((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0 && interleavedFloatBufferImpl != null) { + if(interleavedFloatBufferImpl.limit() < stride * (initialVertexIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray114")); + } + } + // interleaved data using java array + else if(interLeavedVertexData != null) { + if (interLeavedVertexData.length < stride * (initialVertexIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray114")); + } + } + else { + nullGeo = (vertexFormat & GeometryArray.INTERLEAVED) != 0; // Only for byRef + } + geomLock.getLock(); + dirtyFlag |= VERTEX_CHANGED; + this.initialVertexIndex = initialVertexIndex; + geomLock.unLock(); + if (!inUpdater&& source != null && source.isLive()) { + processCoordsChanged(nullGeo); + sendDataChangedMessage(true); + } + } + + int getInitialVertexIndex() { + return initialVertexIndex; + } + + void setInitialCoordIndex(int initialCoordIndex) { + if ((initialCoordIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray104")); + } + // use NIO buffer + if((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0){ + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case PF: + if(floatBufferRefCoords.limit() < (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + case PD: + if(doubleBufferRefCoords.limit() < (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + } + } else { + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case PF: + if (floatRefCoords.length < 3 * (initialCoordIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + case PD: + if (doubleRefCoords.length < 3 * (initialCoordIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + case P3F: + if (p3fRefCoords.length < (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + case P3D: + if (p3dRefCoords.length < (initialCoordIndex+validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray99")); + } + break; + } + } + geomLock.getLock(); + dirtyFlag |= COORDINATE_CHANGED; + this.initialCoordIndex = initialCoordIndex; + dirtyFlag |= COORDINATE_CHANGED; + geomLock.unLock(); + // Send a message, since bounds changed + if (!inUpdater && source != null && source.isLive()) { + processCoordsChanged((vertexType & GeometryArrayRetained.VERTEX_DEFINED) == 0); + sendDataChangedMessage(true); + } + } + + int getInitialCoordIndex() { + return initialCoordIndex; + } + + void setInitialColorIndex(int initialColorIndex) { + if ((initialColorIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray101")); + } + // NIO BUFFER CASE + if((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0){ + switch ((vertexType & COLOR_DEFINED)) { + case CF: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + if (floatBufferRefColors.limit() < 3 * (initialColorIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + else if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + if (floatBufferRefColors.limit() < 4 * (initialColorIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + break; + + case CUB: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + if (byteBufferRefColors.limit() < 3 * (initialColorIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + else if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + if (byteBufferRefColors.limit() < 4 * (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + break; + } + } + // Java ARRAY CASE + else { + switch ((vertexType & COLOR_DEFINED)) { + case CF: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + if (floatRefColors.length < 3 * (initialColorIndex+validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + else if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + if (floatRefColors.length < 4 * (initialColorIndex+ validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + break; + case CUB: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + if (byteRefColors.length < 3 * (initialColorIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + else if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + if (byteRefColors.length < 4 * (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + } + break; + case C3F: + if (c3fRefColors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + break; + case C4F: + if (c4fRefColors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + break; + case C3UB: + if (c3bRefColors.length < (initialColorIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + break; + case C4UB: + if (c4bRefColors.length < (initialColorIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray112")); + } + break; + } + } + geomLock.getLock(); + dirtyFlag |= COLOR_CHANGED; + colorChanged = 0xffff; + this.initialColorIndex = initialColorIndex; + geomLock.unLock(); + // There is no need to send message for by reference, since we + // use VA + + } + + int getInitialColorIndex() { + return initialColorIndex; + } + + void setInitialNormalIndex(int initialNormalIndex) { + if ((initialNormalIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray102")); + } + if((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0){ + if((vertexType & NORMAL_DEFINED) == NF){ + if (floatBufferRefNormals.limit() < 3 * (initialNormalIndex + validVertexCount )) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + } + } else { + switch((vertexType & NORMAL_DEFINED)){ + case NF: + if (floatRefNormals.length < 3 * (initialNormalIndex + validVertexCount )) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + break; + case N3F: + if (v3fRefNormals.length < (initialNormalIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("GeometryArray111")); + } + } + } + geomLock.getLock(); + dirtyFlag |= NORMAL_CHANGED; + this.initialNormalIndex = initialNormalIndex; + geomLock.unLock(); + // There is no need to send message for by reference, since we + // use VA + } + + int getInitialNormalIndex() { + return initialNormalIndex; + } + + void setInitialTexCoordIndex(int texCoordSet, int initialTexCoordIndex) { + if ((initialTexCoordIndex + validVertexCount) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryArray103")); + } + + if((vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0){ + if((vertexType & TEXCOORD_DEFINED) == TF) { + FloatBufferWrapper texBuffer = (FloatBufferWrapper)(((J3DBuffer) refTexCoordsBuffer[texCoordSet]).getBufferImpl()); + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + if (texBuffer.limit() < 2 * (initialTexCoordIndex+ validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + if (texBuffer.limit() < 3 * (initialTexCoordIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + if (texBuffer.limit() < 4 * (initialTexCoordIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } + } + } else { + switch ((vertexType & TEXCOORD_DEFINED)) { + case TF: + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + if (((float[])refTexCoords[texCoordSet]).length < 2 * (initialTexCoordIndex+ validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + if (((float[])refTexCoords[texCoordSet]).length < 3 * (initialTexCoordIndex + validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + if (((float[])refTexCoords[texCoordSet]).length < 4 * (initialTexCoordIndex + validVertexCount)) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + } + break; + case T2F: + if (((TexCoord2f[])refTexCoords[texCoordSet]).length < (initialTexCoordIndex+ validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + break; + case T3F: + if (((TexCoord3f[])refTexCoords[texCoordSet]).length < (initialTexCoordIndex+ validVertexCount) ) { + throw new ArrayIndexOutOfBoundsException( + J3dI18N.getString("GeometryArray113")); + } + break; + } + } + geomLock.getLock(); + dirtyFlag |= TEXTURE_CHANGED; + this.initialTexCoordIndex[texCoordSet] = initialTexCoordIndex; + geomLock.unLock(); + // There is no need to send message for by reference, since we + // use VA + } + + int getInitialTexCoordIndex(int texCoordSet) { + return initialTexCoordIndex[texCoordSet]; + } + + + int getTexCoordSetCount() { + return this.texCoordSetCount; + } + + int getTexCoordSetMapLength() { + if (this.texCoordSetMap != null) + return this.texCoordSetMap.length; + else + return 0; + } + + void getTexCoordSetMap(int [] texCoordSetMap) { + + if (this.texCoordSetMap!=null) { + for (int i = 0; i < this.texCoordSetMap.length; i++) { + texCoordSetMap[i] = this.texCoordSetMap[i]; + } + } + } + + protected void finalize() { + // For Pure immediate mode, there is no clearLive so + // surface will free when JVM do GC + if (pVertexBuffers != 0) { + // memory not yet free for immediate mode rendering + // It is thread safe since D3D only free surface in + // the next swapBuffer() call which must be in the + // same renderer threads + freeD3DArray(true); + } + } + + void freeDlistId() { + if (dlistId != -1) { + VirtualUniverse.mc.freeDisplayListId(dlistObj); + dlistId = -1; + } + } + + void assignDlistId() { + if (dlistId == -1) { + dlistObj = VirtualUniverse.mc.getDisplayListId(); + dlistId = dlistObj.intValue(); + } + } + + void incrDlistRefCount(int bit) { + int index = getIndex(bit); + int[] newList = null; + int i; + synchronized(renderMolPerDlist) { + if (index >= renderMolPerDlist.length) { + newList = new int[index * 2]; + for (i = 0; i < renderMolPerDlist.length; i++) { + newList[i] = renderMolPerDlist[i]; + } + newList[index] = 1; + + // This must be the last statment for + // sync. to work correctly + renderMolPerDlist = newList; + } + else { + renderMolPerDlist[index]++; + } + } + } + + int decrDlistRefCount(int rdrBit) { + int index = getIndex(rdrBit); + synchronized(renderMolPerDlist) { + if (index >= renderMolPerDlist.length) { + // In case of sharedDlist it is possible that + // decrDlistRefCount is invoke for cleanup + // from another canvas + return -1; + } + renderMolPerDlist[index]--; + return renderMolPerDlist[index]; + } + } + + void setDlistTimeStamp(int rdrBit, long timeStamp) { + int index = getIndex(rdrBit); + if (index >= timeStampPerDlist.length) { + long[] newList = new long[index * 2]; + for (int i = 0; i < timeStampPerDlist.length; i++) { + newList[i] = timeStampPerDlist[i]; + } + timeStampPerDlist = newList; + } + timeStampPerDlist[index] = timeStamp; + } + + long getDlistTimeStamp(int rdrBit) { + int index = getIndex(rdrBit); + // If index is greater than what currently exists, increase + // the array and return zero + if (index >= timeStampPerDlist.length) { + setDlistTimeStamp(rdrBit, 0); + } + return timeStampPerDlist[index]; + } + + int getIndex(int bit) { + int num = 0; + + while (bit > 0) { + num++; + bit >>= 1; + } + return num; + } + + + boolean isWriteStatic() { + + if (source.getCapability(GeometryArray.ALLOW_COORDINATE_WRITE ) || + source.getCapability(GeometryArray.ALLOW_COLOR_WRITE) || + source.getCapability(GeometryArray.ALLOW_NORMAL_WRITE) || + source.getCapability(GeometryArray.ALLOW_TEXCOORD_WRITE) || + source.getCapability(GeometryArray.ALLOW_COUNT_WRITE) || + source.getCapability(GeometryArray.ALLOW_REF_DATA_WRITE)) + return false; + + return true; + } + + /** + * The functions below are only used in compile mode + */ + void setCompiled(ArrayList curList) { + int i; + int num = curList.size(); + int offset = 0; + geoOffset = new int[num]; + compileVcount = new int[num]; + int vcount = 0, vformat = 0; + vcount = 0; + isCompiled = true; + + if (num > 0) + source = ((SceneGraphObjectRetained)curList.get(0)).source; + for (i = 0; i < num; i++) { + // Build the back mapping + GeometryArrayRetained geo = (GeometryArrayRetained)curList.get(i); + ((GeometryArray)geo.source).retained = this; + compileVcount[i] = geo.getValidVertexCount(); + vcount += geo.getValidVertexCount(); + geoOffset[i] = offset; + offset += geo.stride() * compileVcount[i]; + vformat = geo.getVertexFormat(); + } + createGeometryArrayData(vcount, vformat); + + // Assign the initial and valid fields + validVertexCount = vcount; + initialVertexIndex = 0; + + mergeGeometryArrays(curList); + + } + + /* + // Ununsed + int getVertexCount(int index) { + return compileVcount[index]; + } + + + int getValidVertexCount(int index) { + return compileVcount[index]; + } + + + int getInitialVertexIndex(int index) { + return 0; + } + */ + + void mergeGeometryArrays(ArrayList list) { + float[] curVertexData; + int length, srcOffset; + int curOffset = 0; + // We only merge if the texCoordSetCount is 1; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + texCoordSetCount = 1; + texCoordSetMap = new int[1]; + texCoordSetMap[0] = 1; + } + for (int i = 0; i < list.size(); i++) { + GeometryArrayRetained geo = (GeometryArrayRetained)list.get(i); + // Take into account the validVertexCount and initialVertexIndex + curVertexData = geo.vertexData; + length = geo.validVertexCount * stride; + srcOffset = geo.initialVertexIndex * stride; + System.arraycopy(curVertexData, srcOffset, this.vertexData, curOffset, + length); + curOffset += length; + + // assign geoBounds + geoBounds.combine(geo.geoBounds); + + } + this.centroid.set(geoBounds.getCenter()); + } + + boolean isMergeable() { + + // For now, turn off by ref geometry + if ((vertexFormat & GeometryArray.BY_REFERENCE) != 0) + return false; + + if (!isStatic()) + return false; + + // If there is more than one set of texture coordinate set defined + // then don't merge geometry (we avoid dealing with texCoordSetMap + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0 && + (texCoordSetCount > 1 || + texCoordSetMap != null && texCoordSetMap.length > 1)) { + return false; + } + + + // If intersect is allowed turn off merging + if (source.getCapability(Geometry.ALLOW_INTERSECT)) + return false; + + return true; + } + + boolean isTextureGeometryMergeable(GeometryArrayRetained srcGeo) { + + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + if (texCoordSetCount != srcGeo.texCoordSetCount ) + return false; + + // If they are both non-null, then check if they are equivalent + if (texCoordSetMap != null && srcGeo.texCoordSetMap != null) { + if (texCoordSetMap.length != srcGeo.texCoordSetMap.length) + return false; + + // Check the texCoordSetMap is same + for (int j = 0; j < texCoordSetMap.length; j++) { + if (texCoordSetMap[j] != srcGeo.texCoordSetMap[j]) + return false; + } + } + // Check if they are both null; + // if one is null and other is non-null return false + else if (texCoordSetMap != srcGeo.texCoordSetMap) + return false; + } + + return true; + } + + void compile(CompileState compState) { + super.compile(compState); + + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + compState.needNormalsTransform = true; + } + } + + void mergeTransform(TransformGroupRetained xform) { + if (geoBounds != null) { + geoBounds.transform(xform.transform); + } + } + + // This adds a MorphRetained to the list of users of this geometry + void addMorphUser(MorphRetained m) { + int index; + ArrayList morphList; + + if(morphUniverseList == null) { + morphUniverseList = new ArrayList(1); + morphUserLists = new ArrayList(1); + } + synchronized (morphUniverseList) { + if (morphUniverseList.contains(m.universe)) { + index = morphUniverseList.indexOf(m.universe); + morphList = (ArrayList)morphUserLists.get(index); + morphList.add(m); + } else { + morphUniverseList.add(m.universe); + morphList = new ArrayList(5); + morphList.add(m); + morphUserLists.add(morphList); + } + } + } + + // This adds a MorphRetained to the list of users of this geometry + void removeMorphUser(MorphRetained m) { + int index; + ArrayList morphList; + + if(morphUniverseList == null) + return; + + synchronized (morphUniverseList) { + index = morphUniverseList.indexOf(m.universe); + morphList = (ArrayList)morphUserLists.get(index); + morphList.remove(morphList.indexOf(m)); + if (morphList.size() == 0) { + morphUserLists.remove(index); + morphUniverseList.remove(index); + } + } + } + // Initialize mirror object when geometry is first setLived + void initMirrorGeometry() { + geomLock.getLock(); + if (this instanceof IndexedGeometryArrayRetained) { + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + mirrorGeometry = (GeometryRetained) + ((IndexedGeometryArrayRetained)this).cloneNonIndexedGeometry(); + } + else { + mirrorGeometry = null; + } + } + geomLock.unLock(); + + } + + // Update Mirror Object in response to change in geometry + void updateMirrorGeometry() { + geomLock.getLock(); + if (this instanceof IndexedGeometryArrayRetained) { + if (mirrorGeometry != null) { + mirrorGeometry = (GeometryRetained) + ((IndexedGeometryArrayRetained)this).cloneNonIndexedGeometry(); + } + } + geomLock.unLock(); + + } + + + // Used by the picking intersect routines + void getVertexData(int i, Point3d pnts) { + int offset; + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + offset = stride * i + coordinateOffset; + pnts.x = this.vertexData[offset]; + pnts.y = this.vertexData[offset+1]; + pnts.z = this.vertexData[offset+2]; + return; + } + + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0 ) { + if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + offset = stride * i + coordinateOffset; + pnts.x = this.interLeavedVertexData[offset]; + pnts.y = this.interLeavedVertexData[offset+1]; + pnts.z = this.interLeavedVertexData[offset+2]; + } + else { + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case GeometryArrayRetained.PF: + offset = i*3; + pnts.x = this.floatRefCoords[offset]; + pnts.y = this.floatRefCoords[offset+1]; + pnts.z = this.floatRefCoords[offset+2]; + break; + case GeometryArrayRetained.PD: + offset = i*3; + pnts.x = this.doubleRefCoords[offset]; + pnts.y = this.doubleRefCoords[offset+1]; + pnts.z = this.doubleRefCoords[offset+2]; + break; + case GeometryArrayRetained.P3F: + pnts.x = this.p3fRefCoords[i].x; + pnts.y = this.p3fRefCoords[i].y; + pnts.z = this.p3fRefCoords[i].z; + break; + case GeometryArrayRetained.P3D: + pnts.x = this.p3dRefCoords[i].x; + pnts.y = this.p3dRefCoords[i].y; + pnts.z = this.p3dRefCoords[i].z; + break; + } + } + }// end of non nio buffer + else { // NIO BUFFER + if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + offset = stride * i + coordinateOffset; + pnts.x = this.interleavedFloatBufferImpl.get(offset); + pnts.y = this.interleavedFloatBufferImpl.get(offset+1); + pnts.z = this.interleavedFloatBufferImpl.get(offset+2); + } + else { + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case GeometryArrayRetained.PF: + offset = i*3; + pnts.x = this.floatBufferRefCoords.get(offset); + pnts.y = this.floatBufferRefCoords.get(offset+1); + pnts.z = this.floatBufferRefCoords.get(offset+2); + break; + case GeometryArrayRetained.PD: + offset = i*3; + pnts.x = this.doubleBufferRefCoords.get(offset); + pnts.y = this.doubleBufferRefCoords.get(offset+1); + pnts.z = this.doubleBufferRefCoords.get(offset+2); + break; + } + } + } // end of nio buffer + } + + void getCrossValue(Point3d p1, Point3d p2, Vector3d value) { + value.x += p1.y*p2.z - p1.z*p2.y; + value.y += p2.x*p1.z - p2.z*p1.x; + value.z += p1.x*p2.y - p1.y*p2.x; + } + + + boolean intersect(Transform3D thisLocalToVworld, + Transform3D otherLocalToVworld, GeometryRetained geom) { + + Transform3D tg = VirtualUniverse.mc.getTransform3D(null); + boolean isIntersect = false; + + if (geom instanceof GeometryArrayRetained ) { + GeometryArrayRetained geomArray = (GeometryArrayRetained) geom; + + if (geomArray.validVertexCount >= validVertexCount) { + tg.invert(otherLocalToVworld); + tg.mul(thisLocalToVworld); + isIntersect = intersect(tg, geom); + } else { + tg.invert(thisLocalToVworld); + tg.mul(otherLocalToVworld); + isIntersect = geomArray.intersect(tg, this); + } + } else { + tg.invert(thisLocalToVworld); + tg.mul(otherLocalToVworld); + isIntersect = geom.intersect(tg, this); + } + + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, tg); + return isIntersect; + } + + int getNumCoordCount() { + int count = 0; + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0){ + count = vertexCount; + return count; + } + + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0){ + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case PF: + count = floatRefCoords.length/3; + break; + case PD: + count = doubleRefCoords.length/3; + break; + case P3F: + count = p3fRefCoords.length; + break; + case P3D: + count = p3dRefCoords.length; + break; + } + } + else { + count = interLeavedVertexData.length/stride; + } + } + else { // nio buffer + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0){ + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case PF: + count = floatBufferRefCoords.limit()/3; // TODO: limit or capacity + break; + case PD: + count = doubleBufferRefCoords.limit()/3; + break; + } + } + else { + count = interleavedFloatBufferImpl.limit()/stride; + } + } + } + return count; + } + + int getNumColorCount() { + int count = 0; + if ((vertexFormat & GeometryArray.COLOR) != 0){ + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0){ + count = vertexCount; + return count; + } + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0){ + switch ((vertexType & GeometryArrayRetained.COLOR_DEFINED)) { + case CF: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + count = floatRefColors.length/3; + } + else if ((vertexFormat & GeometryArray.COLOR_4)== GeometryArray.COLOR_4){ + count = floatRefColors.length/4; + } + break; + case CUB: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + count = byteRefColors.length/3; + } + else if ((vertexFormat & GeometryArray.COLOR_4)== GeometryArray.COLOR_4){ + count = byteRefColors.length/4; + } + break; + case C3F: + count = c3fRefColors.length; + break; + case C4F: + count = c4fRefColors.length; + break; + case C3UB: + count = c3bRefColors.length; + break; + case C4UB: + count = c4bRefColors.length; + break; + } + } + else { + count = interLeavedVertexData.length/stride; + } + } // end of non nio buffer + else { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0){ + switch ((vertexType & GeometryArrayRetained.COLOR_DEFINED)) { + case CF: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + count = floatBufferRefColors.limit()/3; + } + else if ((vertexFormat & GeometryArray.COLOR_4)== GeometryArray.COLOR_4){ + count = floatBufferRefColors.limit()/4; + } + break; + case CUB: + if ((vertexFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + count = byteBufferRefColors.limit()/3; + } + else if ((vertexFormat & GeometryArray.COLOR_4)== GeometryArray.COLOR_4){ + count = byteBufferRefColors.limit()/4; + } + break; + } + } + else { + count = interleavedFloatBufferImpl.limit()/stride; + } + } // end of nio buffer + } + return count; + } + + int getNumNormalCount() { + int count = 0; + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0){ + count = vertexCount; + return count; + } + + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0){ + switch ((vertexType & NORMAL_DEFINED)) { + case NF: + count = floatRefNormals.length/3; + break; + case N3F: + count = v3fRefNormals.length; + break; + } + } + else { + count = interLeavedVertexData.length/stride; + } + } // end of non nio buffer + else { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0){ + if ((vertexType & NORMAL_DEFINED) == NF ) { + count = floatBufferRefNormals.limit()/3; + } + } + else { + count = interleavedFloatBufferImpl.limit()/stride; + } + } + } + return count; + } + + int getNumTexCoordCount(int i) { + int count = 0; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0){ + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0){ + count = vertexCount; + return count; + } + + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0){ + switch ((vertexType & TEXCOORD_DEFINED)) { + case TF: + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + count = ((float[])refTexCoords[i]).length/2; + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + count = ((float[])refTexCoords[i]).length/3; + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + count = ((float[])refTexCoords[i]).length/4; + } + + break; + case T2F: + count = ((TexCoord2f[])refTexCoords[i]).length; + break; + case T3F: + count = ((TexCoord3f[])refTexCoords[i]).length; + } + } + else { + count = interLeavedVertexData.length/stride; + } + } + else { // nio buffer + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0){ + if ((vertexType & TEXCOORD_DEFINED) == TF) { + FloatBufferWrapper texBuffer = (FloatBufferWrapper)(((J3DBuffer) refTexCoordsBuffer[i]).getBufferImpl()); + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + count = texBuffer.limit()/2; + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + count = texBuffer.limit()/3; + } else if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + count = texBuffer.limit()/4; + } + } + } + else { + count = interleavedFloatBufferImpl.limit()/stride; + } + } + } + return count; + } + + // Found the min distance from center to the point/line/tri/quad + // form by dist[] + void computeMinDistance(Point3d coordinates[], Point3d center, + Vector3d normal, + double dist[], Point3d iPnt) { + double x, y, z; + int i, j; + + if (coordinates.length == 1) { + // a point + iPnt.x = coordinates[0].x; + iPnt.y = coordinates[0].y; + iPnt.z = coordinates[0].z; + x = iPnt.x - center.x; + y = iPnt.y - center.y; + z = iPnt.z - center.z; + dist[0] = Math.sqrt(x*x + y*y + z*z); + return; + } + + + if (coordinates.length == 2) { + // a line + dist[0] = Math.sqrt(Distance.pointToSegment(center, + coordinates[0], + coordinates[1], + iPnt, null)); + return; + } + + double normalLen = 0; + + if (normal == null) { + Vector3d vec0 = new Vector3d(); + Vector3d vec1 = new Vector3d(); + normal = new Vector3d(); + // compute plane normal for coordinates. + for (i=0; i<coordinates.length-1;) { + vec0.x = coordinates[i+1].x - coordinates[i].x; + vec0.y = coordinates[i+1].y - coordinates[i].y; + vec0.z = coordinates[i+1].z - coordinates[i++].z; + if(vec0.length() > 0.0) + break; + } + + for (j=i; j<coordinates.length-1; j++) { + vec1.x = coordinates[j+1].x - coordinates[j].x; + vec1.y = coordinates[j+1].y - coordinates[j].y; + vec1.z = coordinates[j+1].z - coordinates[j].z; + if(vec1.length() > 0.0) + break; + } + + if (j == (coordinates.length-1)) { + // Degenerate polygon, check with edge only + normal = null; + } else { + normal.cross(vec0,vec1); + } + } + + if (normal != null) { + normalLen = normal.length(); + if ( normalLen == 0.0) { + // Degenerate polygon, check with edge only + normal = null; + } + } + + + if (coordinates.length == 3) { + // a triangle + if (normal != null) { + double d = -(normal.x*coordinates[0].x + + normal.y*coordinates[0].y + + normal.z*coordinates[0].z); + dist[0] = (normal.x*center.x + normal.y*center.y + + normal.z*center.z + + d)/normalLen; + iPnt.x = center.x - dist[0]*normal.x/normalLen; + iPnt.y = center.y - dist[0]*normal.y/normalLen; + iPnt.z = center.z - dist[0]*normal.z/normalLen; + + if (pointInTri(iPnt, coordinates[0], coordinates[1], + coordinates[2], normal)) { + return; + } + } + + // checking point to line distance + double minDist; + Point3d minPnt = new Point3d(); + + dist[0] = Distance.pointToSegment(center, coordinates[0], + coordinates[1], iPnt, null); + minDist = Distance.pointToSegment(center, coordinates[1], + coordinates[2], minPnt, null); + if (minDist < dist[0]) { + dist[0] = minDist; + iPnt.x = minPnt.x; + iPnt.y = minPnt.y; + iPnt.z = minPnt.z; + } + minDist = Distance.pointToSegment(center, coordinates[2], + coordinates[0], minPnt, null); + if (minDist < dist[0]) { + dist[0] = minDist; + iPnt.x = minPnt.x; + iPnt.y = minPnt.y; + iPnt.z = minPnt.z; + } + dist[0] = Math.sqrt(dist[0]); + return; + } + + // a quad + if (normal != null) { + double d = -(normal.x*coordinates[0].x + + normal.y*coordinates[0].y + + normal.z*coordinates[0].z); + dist[0] = (normal.x*center.x + normal.y*center.y + + normal.z*center.z + + d)/normalLen; + iPnt.x = center.x - dist[0]*normal.x/normalLen; + iPnt.y = center.y - dist[0]*normal.y/normalLen; + iPnt.z = center.z - dist[0]*normal.z/normalLen; + + if (pointInTri(iPnt, coordinates[0], coordinates[1], + coordinates[2], normal) || + pointInTri(iPnt, coordinates[1], coordinates[2], + coordinates[3], normal)) { + return; + } + } + + // checking point to line distance + double minDist; + Point3d minPnt = new Point3d(); + + dist[0] = Distance.pointToSegment(center, coordinates[0], + coordinates[1], iPnt, null); + minDist = Distance.pointToSegment(center, coordinates[1], + coordinates[2], minPnt, null); + if (minDist < dist[0]) { + dist[0] = minDist; + iPnt.x = minPnt.x; + iPnt.y = minPnt.y; + iPnt.z = minPnt.z; + } + minDist = Distance.pointToSegment(center, coordinates[2], + coordinates[3], minPnt, null); + if (minDist < dist[0]) { + dist[0] = minDist; + iPnt.x = minPnt.x; + iPnt.y = minPnt.y; + iPnt.z = minPnt.z; + } + + minDist = Distance.pointToSegment(center, coordinates[3], + coordinates[0], minPnt, null); + if (minDist < dist[0]) { + dist[0] = minDist; + iPnt.x = minPnt.x; + iPnt.y = minPnt.y; + iPnt.z = minPnt.z; + } + + dist[0] = Math.sqrt(dist[0]); + } + + Vector3d getVector3d() { + return (Vector3d)FreeListManager.getObject(FreeListManager.VECTOR3D); + } + + void freeVector3d(Vector3d v) { + FreeListManager.freeObject(FreeListManager.VECTOR3D, v); + } + + Point3d getPoint3d() { + return (Point3d)FreeListManager.getObject(FreeListManager.POINT3D); + } + + void freePoint3d(Point3d p) { + FreeListManager.freeObject(FreeListManager.POINT3D, p); + } + + + void handleFrequencyChange(int bit) { + int mask = 0; + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + if ((bit == GeometryArray.ALLOW_COORDINATE_WRITE) || + (((vertexFormat & GeometryArray.COLOR) != 0) && + bit == GeometryArray.ALLOW_COLOR_WRITE)|| + (((vertexFormat & GeometryArray.NORMALS) != 0) && + bit ==GeometryArray.ALLOW_NORMAL_WRITE) || + (((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0)&& + bit == GeometryArray.ALLOW_TEXCOORD_WRITE) || + (bit == GeometryArray.ALLOW_COUNT_WRITE)) { + mask = 1; + } + } + else { + if (bit == GeometryArray.ALLOW_REF_DATA_WRITE) + mask = 1; + } + if (mask != 0) { + setFrequencyChangeMask(bit, mask); + } + } + + +} diff --git a/src/classes/share/javax/media/j3d/GeometryAtom.java b/src/classes/share/javax/media/j3d/GeometryAtom.java new file mode 100644 index 0000000..b8dc3f5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryAtom.java @@ -0,0 +1,263 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; +import javax.vecmath.*; + +/** + * A GeometryAtom is the smallest object representing Geometry. + */ + +class GeometryAtom extends Object implements BHLeafInterface, NnuId { + + /** + * Array of geometry components of this geometry atom + */ + // The first index of geometryArr should always be 0, unless geometryArr contains + // multiple Text3Ds. + GeometryRetained[] geometryArray = null; + + /** + * Array of transforms used only for Text3d. + */ + Transform3D[] lastLocalTransformArray = null; + + + /** + * The locale that this geometry atom is attatched to. This is only non-null + * if this instance is directly linked into a locale. + */ + Locale locale = null; + + /** + * The mirror Shape3DRetained for this GeometryAtom. + */ + Shape3DRetained source = null; + + /** + * The BHLeafNode for this GeometryAtom. + */ + BHLeafNode bhLeafNode = null; + + // true if alpha channel is editable + boolean alphaEditable; + + // true if this ga is visible. Default is true. + boolean visible = true; + + /** + * This is the original geometry type from which this atom came + */ + int geoType = -1; + + /** + * The list of RenderAtoms for this GeometryAtom + */ + RenderAtom[] renderAtoms = new RenderAtom[0]; + + // Id use for quick search. + int nnuId; + + Point3d[] centroid = null; + boolean centroidIsDirty = true; + Object lockObj = new Object(); + + + GeometryAtom() { + // Get a not necessary unique Id. + nnuId = NnuIdManager.getId(); + } + + public int getId() { + return nnuId; + } + + public int equal(NnuId obj) { + int keyId = obj.getId(); + if(nnuId < keyId) { + return -1; + } + else if(nnuId > keyId) { + return 1; + } + else { // Found it! + return 0; + } + } + + public BoundingBox computeBoundingHull() { + /* + System.out.println("Bounds is " + source.vwcBounds); + for(int i=0; i<geometryArray.length; i++) { + System.out.println( i + " geoBounds " + + geometryArray[i].geoBounds); + } + */ + + return source.vwcBounds; + } + + // This method is use by picking and collision queries. + public boolean isEnable() { + return ((source.vwcBounds != null) && + (source.vwcBounds.isEmpty() == false) && + (source.switchState.currentSwitchOn)); + } + + // This method is use by visibility query. + public boolean isEnable(int vis) { + if((source.vwcBounds != null) && (source.vwcBounds.isEmpty() == false) && + (source.switchState.currentSwitchOn)) { + switch(vis) { + case View.VISIBILITY_DRAW_VISIBLE: + return visible; + case View.VISIBILITY_DRAW_INVISIBLE: + return (!visible); + case View.VISIBILITY_DRAW_ALL: + return true; + } + } + return false; + } + + public Locale getLocale2() { + return locale; + } + + + /** + * Gets a RenderAtom for the given viewIndex. + * If it doesn't exist, it creates one. + */ + RenderAtom getRenderAtom(View view) { + RenderAtom ra; + int index; + // If renderAtom is not scoped to this view, don't even + // bother creating the renderAtom + + synchronized (renderAtoms) { + index = view.viewIndex; + if (index >= renderAtoms.length) { + + // If creating a new RenderAtom, but this ga is not scoped + // to this view, then just return .. + if (source.viewList != null && + !source.viewList.contains(view)) + return null; + RenderAtom[] newList = new RenderAtom[index+1]; + for (int i = 0; i < renderAtoms.length; i++) { + newList[i] = renderAtoms[i]; + } + ra = new RenderAtom(); + newList[index] = ra; + newList[index].geometryAtom = this; + + // Allocate space based on number of geometry in the list + ra.rListInfo = new RenderAtomListInfo[geometryArray.length]; + if (geoType != GeometryRetained.GEO_TYPE_TEXT3D) { + for (int j = 0; j < ra.rListInfo.length; j++) { + ra.rListInfo[j] = new RenderAtomListInfo(); + ra.rListInfo[j].renderAtom = ra; + ra.rListInfo[j].index = j; + } + } + else { + for (int j = 0; j < ra.rListInfo.length; j++) { + ra.rListInfo[j] = new RenderAtomListInfo(); + ra.rListInfo[j].renderAtom = ra; + ra.rListInfo[j].index = j; + ra.rListInfo[j].localToVworld = VirtualUniverse.mc.getTransform3D(null); + } + } + + // Note this must be the last line in synchronized. + // Otherwise the lock is changed to newList and + // another thread can come in modified. This cause + // NullPointerException in + // renderAtoms[index].geometryAtom = this; + // which I encounter. + renderAtoms = newList; + } else { + if (renderAtoms[index] == null) { + // If creating a new RenderAtom, but this ga is not scoped + // to this view, then just return .. + if (source.viewList != null && + !source.viewList.contains(view)) + return null; + + ra = new RenderAtom(); + renderAtoms[index] = ra; + renderAtoms[index].geometryAtom = this; + // Allocate space based on number of geometry in the list + ra.rListInfo = new RenderAtomListInfo[geometryArray.length]; + if (geoType != GeometryRetained.GEO_TYPE_TEXT3D) { + for (int j = 0; j < ra.rListInfo.length; j++) { + ra.rListInfo[j] = new RenderAtomListInfo(); + ra.rListInfo[j].renderAtom = ra; + ra.rListInfo[j].index = j; + } + } + else { + for (int j = 0; j < ra.rListInfo.length; j++) { + ra.rListInfo[j] = new RenderAtomListInfo(); + ra.rListInfo[j].renderAtom = ra; + ra.rListInfo[j].index = j; + ra.rListInfo[j].localToVworld = VirtualUniverse.mc.getTransform3D(null); + } + } + } + } + } + + return (renderAtoms[index]); + } + // If the renderAtom is transparent, then make sure that the + // value is up-to-date + + void updateCentroid() { + synchronized(lockObj) { + for (int j = 0; j < geometryArray.length; j++) { + if (geometryArray[j] == null) + continue; + synchronized(geometryArray[j].centroid) { + if (geometryArray[j].recompCentroid) { + geometryArray[j].computeCentroid(); + geometryArray[j].recompCentroid = false; + } + } + } + if (centroidIsDirty) { + if (centroid == null) { + centroid = new Point3d[geometryArray.length]; + for (int j = 0; j < centroid.length; j++) { + if (geometryArray[j] == null) + continue; + centroid[j] = new Point3d(geometryArray[j].centroid); + source.getCurrentLocalToVworld(0).transform(centroid[j]); + } + } + else { + for (int j = 0; j < centroid.length; j++) { + if (geometryArray[j] == null) + continue; + centroid[j].set(geometryArray[j].centroid); + source.getCurrentLocalToVworld(0).transform(centroid[j]); + } + } + centroidIsDirty = false; + } + } + } + +} diff --git a/src/classes/share/javax/media/j3d/GeometryDecompressor.java b/src/classes/share/javax/media/j3d/GeometryDecompressor.java new file mode 100644 index 0000000..dce4596 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryDecompressor.java @@ -0,0 +1,1200 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.* ; + +/** + * This abstract class provides the base methods needed to create a geometry + * decompressor. Subclasses must implement a backend to handle the output, + * consisting of a generalized triangle strip, line strip, or point array, + * along with possible global color and normal changes. + */ +abstract class GeometryDecompressor { + private static final boolean debug = false ; + private static final boolean benchmark = false ; + + /** + * Compressed geometry format version supported. + */ + static final int majorVersionNumber = 1 ; + static final int minorVersionNumber = 0 ; + static final int minorMinorVersionNumber = 2 ; + + /** + * This method is called when a SetState command is encountered in the + * decompression stream. + * + * @param bundlingNorm true indicates normals are bundled with vertices + * @param bundlingColor true indicates colors are bundled with vertices + * @param doingAlpha true indicates alpha values are bundled with vertices + */ + abstract void outputVertexFormat(boolean bundlingNorm, + boolean bundlingColor, + boolean doingAlpha) ; + + /** + * This method captures the vertex output of the decompressor. The normal + * or color references may be null if the corresponding data is not + * bundled with the vertices in the compressed geometry buffer. Alpha + * values may be included in the color. + * + * @param position The coordinates of the vertex. + * @param normal The normal bundled with the vertex. May be null. + * @param color The color bundled with the vertex. May be null. + * Alpha may be present. + * @param vertexReplaceCode Specifies the generalized strip flag + * that is bundled with each vertex. + * @see GeneralizedStripFlags + * @see CompressedGeometryHeader + */ + abstract void outputVertex(Point3f position, Vector3f normal, + Color4f color, int vertexReplaceCode) ; + + /** + * This method captures the global color output of the decompressor. It + * is only invoked if colors are not bundled with the vertex data. The + * global color applies to all succeeding vertices until the next time the + * method is invoked. + * + * @param color The current global color. + */ + abstract void outputColor(Color4f color) ; + + /** + * This method captures the global normal output of the decompressor. It + * is only invoked if normals are not bundled with the vertex data. The + * global normal applies to all succeeding vertices until the next time the + * method is invoked. + * + * @param normal The current global normal. + */ + abstract void outputNormal(Vector3f normal) ; + + // Geometry compression opcodes. + private static final int GC_VERTEX = 0x40 ; + private static final int GC_SET_NORM = 0xC0 ; + private static final int GC_SET_COLOR = 0x80 ; + private static final int GC_MESH_B_R = 0x20 ; + private static final int GC_SET_STATE = 0x18 ; + private static final int GC_SET_TABLE = 0x10 ; + private static final int GC_PASS_THROUGH = 0x08 ; + private static final int GC_EOS = 0x00 ; + private static final int GC_V_NO_OP = 0x01 ; + private static final int GC_SKIP_8 = 0x07 ; + + // Three 64-entry decompression tables are used: gctables[0] for + // positions, gctables[1] for colors, and gctables[2] for normals. + private HuffmanTableEntry gctables[][] ; + + /** + * Decompression table entry. + */ + static class HuffmanTableEntry { + int tagLength, dataLength ; + int rightShift, absolute ; + + public String toString() { + return + " tag length: " + tagLength + + " data length: " + dataLength + + " shift: " + rightShift + + " abs/rel: " + absolute ; + } + } + + // A 16-entry mesh buffer is used. + private MeshBufferEntry meshBuffer[] ; + private int meshIndex = 15 ; + private int meshState ; + + // meshState values. These are needed to determine if colors and/or + // normals should come from meshBuffer or from SetColor or SetNormal. + private static final int USE_MESH_NORMAL = 0x1 ; + private static final int USE_MESH_COLOR = 0x2 ; + + /** + * Mesh buffer entry containing position, normal, and color. + */ + static class MeshBufferEntry { + short x, y, z ; + short octant, sextant, u, v ; + short r, g, b, a ; + } + + // Geometry compression state variables. + private short curX, curY, curZ ; + private short curR, curG, curB, curA ; + private int curSex, curOct, curU, curV ; + + // Current vertex data. + private Point3f curPos ; + private Vector3f curNorm ; + private Color4f curColor ; + private int repCode ; + + // Flags indicating what data is bundled with the vertex. + private boolean bundlingNorm ; + private boolean bundlingColor ; + private boolean doingAlpha ; + + // Internal decompression buffering variables. + private int currentHeader = 0 ; + private int nextHeader = 0 ; + private int bitBuffer = 0 ; + private int bitBufferCount = 32 ; + + // Used for benchmarking if so configured. + private long startTime ; + private int vertexCount ; + + // Bit-field masks: BMASK[i] = (1<<i)-1 + private static final int BMASK[] = { + 0x0, 0x1, 0x3, 0x7, + 0xF, 0x1F, 0x3F, 0x7F, + 0xFF, 0x1FF, 0x3FF, 0x7FF, + 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF, + 0xFFFF, 0x1FFFF, 0x3FFFF, 0x7FFFF, + 0xFFFFF, 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, + 0xFFFFFF, 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, + 0xFFFFFFF, 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, + 0xFFFFFFFF, + } ; + + // A reference to the compressed data and the current offset. + private byte gcData[] ; + private int gcIndex ; + + // The normals table for decoding 6-bit [u,v] spherical sextant coordinates. + private static final double gcNormals[][][] ; + private static final double NORMAL_MAX_Y_ANG = 0.615479709 ; + private static final boolean printNormalTable = false ; + + /** + * Initialize the normals table. + */ + static { + int i, j, inx, iny, inz ; + double th, psi, qnx, qny, qnz ; + + gcNormals = new double[65][65][3] ; + + for (i = 0 ; i < 65 ; i++) { + for (j = 0 ; j < 65 ; j++) { + if (i+j > 64) continue ; + + psi = NORMAL_MAX_Y_ANG * (i / 64.0) ; + th = Math.asin(Math.tan(NORMAL_MAX_Y_ANG * ((64-j)/64.0))) ; + + qnx = Math.cos(th) * Math.cos(psi) ; + qny = Math.sin(psi) ; + qnz = Math.sin(th) * Math.cos(psi) ; + + // Convert the floating point normal to s1.14 bit notation, + // then back again. + qnx = qnx*16384.0 ; inx = (int)qnx ; + qnx = (double)inx ; qnx = qnx/16384.0 ; + + qny = qny*16384.0 ; iny = (int)qny ; + qny = (double)iny ; qny = qny/16384.0 ; + + qnz = qnz*16384.0 ; inz = (int)qnz ; + qnz = (double)inz ; qnz = qnz/16384.0 ; + + gcNormals[i][j][0] = qnx ; + gcNormals[i][j][1] = qny ; + gcNormals[i][j][2] = qnz ; + } + } + + if (printNormalTable) { + System.out.println("struct {") ; + System.out.println(" double nx, ny, nz ;") ; + System.out.println("} gcNormals[65][65] = {"); + for (i = 0 ; i <= 64 ; i++) { + System.out.println("{") ; + for (j = 0 ; j <= 64 ; j++) { + if (j+i > 64) continue ; + System.out.println("{ " + gcNormals[i][j][0] + + ", " + gcNormals[i][j][1] + + ", " + gcNormals[i][j][2] + " }") ; + } + System.out.println("},") ; + } + System.out.println("}") ; + } + } + + // + // The constructor. + // + GeometryDecompressor() { + curPos = new Point3f() ; + curNorm = new Vector3f() ; + curColor = new Color4f() ; + gctables = new HuffmanTableEntry[3][64] ; + + for (int i = 0 ; i < 64 ; i++) { + gctables[0][i] = new HuffmanTableEntry() ; + gctables[1][i] = new HuffmanTableEntry() ; + gctables[2][i] = new HuffmanTableEntry() ; + } + + meshBuffer = new MeshBufferEntry[16] ; + for (int i = 0 ; i < 16 ; i++) + meshBuffer[i] = new MeshBufferEntry() ; + } + + /** + * Check version numbers and return true if compatible. + */ + boolean checkVersion(int majorVersionNumber, int minorVersionNumber) { + return ((majorVersionNumber < this.majorVersionNumber) || + ((majorVersionNumber == this.majorVersionNumber) && + (minorVersionNumber <= this.minorVersionNumber))) ; + } + + /** + * Decompress data and invoke abstract output methods. + * + * @param start byte offset to start of compressed geometry in data array + * @param length size of compressed geometry in bytes + * @param data array containing compressed geometry buffer of the + * specified length at the given offset from the start of the array + * @exception ArrayIndexOutOfBoundsException if start+length > data size + */ + void decompress(int start, int length, byte data[]) { + if (debug) + System.out.println("GeometryDecompressor.decompress\n" + + " start: " + start + + " length: " + length + + " data array size: " + data.length) ; + if (benchmark) + benchmarkStart(length) ; + + if (start+length > data.length) + throw new ArrayIndexOutOfBoundsException + (J3dI18N.getString("GeometryDecompressor0")) ; + + // Set reference to compressed data and skip to start of data. + gcData = data ; + gcIndex = start ; + + // Initialize state. + bitBufferCount = 0 ; + meshState = 0 ; + bundlingNorm = false ; + bundlingColor = false ; + doingAlpha = false ; + repCode = 0 ; + + // Headers are interleaved for hardware implementations, so the + // first is always a nullop. + nextHeader = GC_V_NO_OP ; + + // Enter decompression loop. + while (gcIndex < start+length) + processDecompression() ; + + // Finish out any bits left in bitBuffer. + while (bitBufferCount > 0) + processDecompression() ; + + if (benchmark) + benchmarkPrint(length) ; + } + + // + // Return the next bitCount bits of compressed data. + // + private int getBits(int bitCount, String d) { + int bits ; + + if (debug) + System.out.print(" getBits(" + bitCount + ") " + d + ", " + + bitBufferCount + " available at gcIndex " + + gcIndex) ; + + if (bitCount == 0) { + if (debug) System.out.println(": got 0x0") ; + return 0 ; + } + + if (bitBufferCount == 0) { + bitBuffer = (((gcData[gcIndex++] & 0xff) << 24) | + ((gcData[gcIndex++] & 0xff) << 16) | + ((gcData[gcIndex++] & 0xff) << 8) | + ((gcData[gcIndex++] & 0xff))) ; + + bitBufferCount = 32 ; + } + + if (bitBufferCount >= bitCount) { + bits = (bitBuffer >>> (32 - bitCount)) & BMASK[bitCount] ; + bitBuffer = bitBuffer << bitCount ; + bitBufferCount -= bitCount ; + } else { + bits = (bitBuffer >>> (32 - bitCount)) & BMASK[bitCount] ; + bits = bits >>> (bitCount - bitBufferCount) ; + bits = bits << (bitCount - bitBufferCount) ; + + bitBuffer = (((gcData[gcIndex++] & 0xff) << 24) | + ((gcData[gcIndex++] & 0xff) << 16) | + ((gcData[gcIndex++] & 0xff) << 8) | + ((gcData[gcIndex++] & 0xff))) ; + + bits = bits | + ((bitBuffer >>> (32 - (bitCount - bitBufferCount))) & + BMASK[bitCount - bitBufferCount]) ; + + bitBuffer = bitBuffer << (bitCount - bitBufferCount) ; + bitBufferCount = 32 - (bitCount - bitBufferCount) ; + } + + if (debug) + System.out.println(": got 0x" + Integer.toHexString(bits)) ; + + return bits ; + } + + // + // Shuffle interleaved headers and opcodes. + // + private void processDecompression() { + int mbp ; + currentHeader = nextHeader ; + + if ((currentHeader & 0xC0) == GC_VERTEX) { + // Process a vertex. + if (!bundlingNorm && !bundlingColor) { + // get next opcode, process current position opcode + nextHeader = getBits(8, "header") ; + mbp = processDecompressionOpcode(0) ; + + } else if (bundlingNorm && !bundlingColor) { + // get normal header, process current position opcode + nextHeader = getBits(6, "normal") ; + mbp = processDecompressionOpcode(0) ; + currentHeader = nextHeader | GC_SET_NORM ; + + // get next opcode, process current normal opcode + nextHeader = getBits(8, "header") ; + processDecompressionOpcode(mbp) ; + + } else if (!bundlingNorm && bundlingColor) { + // get color header, process current position opcode + nextHeader = getBits(6, "color") ; + mbp = processDecompressionOpcode(0) ; + currentHeader = nextHeader | GC_SET_COLOR ; + + // get next opcode, process current color opcode + nextHeader = getBits(8, "header") ; + processDecompressionOpcode(mbp) ; + + } else { + // get normal header, process current position opcode + nextHeader = getBits(6, "normal") ; + mbp = processDecompressionOpcode(0) ; + currentHeader = nextHeader | GC_SET_NORM ; + + // get color header, process current normal opcode + nextHeader = getBits(6, "color") ; + processDecompressionOpcode(mbp) ; + currentHeader = nextHeader | GC_SET_COLOR ; + + // get next opcode, process current color opcode + nextHeader = getBits(8, "header") ; + processDecompressionOpcode(mbp) ; + } + + // Send out the complete vertex. + outputVertex(curPos, curNorm, curColor, repCode) ; + if (benchmark) vertexCount++ ; + + // meshState bits get turned off in the setColor and setNormal + // routines in order to keep track of what data a mesh buffer + // reference should use. + meshState |= USE_MESH_NORMAL ; + meshState |= USE_MESH_COLOR ; + + } else { + // Non-vertex case: get next opcode, then process current opcode. + nextHeader = getBits(8, "header") ; + processDecompressionOpcode(0) ; + } + } + + // + // Decode the opcode in currentHeader, and dispatch to the appropriate + // processing method. + // + private int processDecompressionOpcode(int mbp) { + if ((currentHeader & 0xC0) == GC_SET_NORM) + processSetNormal(mbp) ; + else if ((currentHeader & 0xC0) == GC_SET_COLOR) + processSetColor(mbp) ; + else if ((currentHeader & 0xC0) == GC_VERTEX) + // Return the state of the mesh buffer push bit + // when processing a vertex. + return processVertex() ; + else if ((currentHeader & 0xE0) == GC_MESH_B_R) { + processMeshBR() ; + + // Send out the complete vertex. + outputVertex(curPos, curNorm, curColor, repCode) ; + if (benchmark) vertexCount++ ; + + // meshState bits get turned off in the setColor and setNormal + // routines in order to keep track of what data a mesh buffer + // reference should use. + meshState |= USE_MESH_NORMAL ; + meshState |= USE_MESH_COLOR ; + } + else if ((currentHeader & 0xF8) == GC_SET_STATE) + processSetState() ; + else if ((currentHeader & 0xF8) == GC_SET_TABLE) + processSetTable() ; + else if ((currentHeader & 0xFF) == GC_EOS) + processEos() ; + else if ((currentHeader & 0xFF) == GC_V_NO_OP) + processVNoop() ; + else if ((currentHeader & 0xFF) == GC_PASS_THROUGH) + processPassThrough() ; + else if ((currentHeader & 0xFF) == GC_SKIP_8) + processSkip8() ; + + return 0 ; + } + + // + // Process a set state opcode. + // + private void processSetState() { + int ii ; + if (debug) + System.out.println("GeometryDecompressor.processSetState") ; + + ii = getBits(3, "bundling") ; + + bundlingNorm = ((currentHeader & 0x1) != 0) ; + bundlingColor = (((ii >>> 2) & 0x1) != 0) ; + doingAlpha = (((ii >>> 1) & 0x1) != 0) ; + + if (debug) + System.out.println(" bundling normal: " + bundlingNorm + + " bundling color: " + bundlingColor + + " alpha present: " + doingAlpha) ; + + // Call the abstract output implementation. + outputVertexFormat(bundlingNorm, bundlingColor, doingAlpha) ; + } + + // + // Process a set decompression table opcode. + // + // Extract the parameters of the table set command, + // and set the approprate table entries. + // + private void processSetTable() { + HuffmanTableEntry gct[] ; + int i, adr, tagLength, dataLength, rightShift, absolute ; + int ii, index ; + + if (debug) + System.out.println("GeometryDecompressor.processSetTable") ; + + // Get reference to approprate 64 entry table. + index = (currentHeader & 0x6) >>> 1 ; + gct = gctables[index] ; + + // Get the remaining bits of the set table command. + ii = getBits(15, "set table") ; + + // Extract the individual fields from the two bit strings. + adr = ((currentHeader & 0x1) << 6) | ((ii >>> 9) & 0x3F) ; + + // Get data length. For positions and colors, 0 really means 16, as 0 + // lengths are meaningless for them. Normal components are allowed to + // have lengths of 0. + dataLength = (ii >>> 5) & 0x0F ; + if (dataLength == 0 && index != 2) + dataLength = 16 ; + + rightShift = ii & 0x0F ; + absolute = (ii >>> 4) & 0x1 ; + + // + // Decode the tag length from the address field by finding the + // first set 1 from the left in the bitfield. + // + for (tagLength = 6 ; tagLength > 0 ; tagLength--) { + if ((adr >> tagLength) != 0) break ; + } + + // Shift the address bits up into place, and off the leading 1. + adr = (adr << (6 - tagLength)) & 0x3F ; + + if (debug) + System.out.println(" table " + ((currentHeader & 0x6) >>> 1) + + " address " + adr + + " tag length " + tagLength + + " data length " + dataLength + + " shift " + rightShift + + " absolute " + absolute) ; + + // Fill in the table fields with the specified values. + for (i = 0 ; i < (1 << (6 - tagLength)) ; i++) { + gct[adr+i].tagLength = tagLength ; + gct[adr+i].dataLength = dataLength ; + gct[adr+i].rightShift = rightShift ; + gct[adr+i].absolute = absolute ; + } + } + + + // + // Process a vertex opcode. Any bundled normal and/or color will be + // processed by separate methods. Return the mesh buffer push indicator. + // + private int processVertex() { + HuffmanTableEntry gct ; + float fX, fY, fZ ; + short dx, dy, dz ; + int mbp, x, y, z, dataLen ; + int ii ; + + // If the next command is a mesh buffer reference + // then use colors and normals from the mesh buffer. + meshState = 0 ; + + // Get a reference to the approprate tag table entry. + gct = gctables[0][currentHeader & 0x3F] ; + + if (debug) System.out.println("GeometryDecompressor.processVertex\n" + + gct.toString()) ; + + // Get the true length of the data. + dataLen = gct.dataLength - gct.rightShift ; + + // Read in the replace code and mesh buffer push bits, + // if they're not in the current header. + if (6 - (3 * dataLen) - gct.tagLength > 0) { + int numBits = 6 - (3 * dataLen) - gct.tagLength ; + int jj ; + + jj = currentHeader & BMASK[numBits] ; + ii = getBits(3 - numBits, "repcode/mbp") ; + ii |= (jj << (3 - numBits)) ; + } + else + ii = getBits(3, "repcode/mbp") ; + + repCode = ii >>> 1 ; + mbp = ii & 0x1 ; + + // Read in x, y, and z components. + x = currentHeader & BMASK[6-gct.tagLength] ; + + if (gct.tagLength + dataLen == 6) { + y = getBits(dataLen, "y") ; + z = getBits(dataLen, "z") ; + } else if (gct.tagLength + dataLen < 6) { + x = x >> (6 - gct.tagLength - dataLen) ; + + y = currentHeader & BMASK[6 - gct.tagLength - dataLen] ; + if (gct.tagLength + 2*dataLen == 6) { + z = getBits(dataLen, "z") ; + } else if (gct.tagLength + 2*dataLen < 6) { + y = y >> (6 - gct.tagLength - 2*dataLen) ; + + z = currentHeader & BMASK[6 - gct.tagLength - 2*dataLen] ; + if (gct.tagLength + 3*dataLen < 6) { + z = z >> (6 - gct.tagLength - 3*dataLen) ; + } else if (gct.tagLength + 3*dataLen > 6) { + ii = getBits(dataLen - (6 - gct.tagLength - 2*dataLen), + "z") ; + z = (z << (dataLen - (6 - gct.tagLength - 2*dataLen))) + | ii ; + } + } else { + ii = getBits(dataLen - (6 - gct.tagLength - dataLen), "y") ; + y = (y << (dataLen - (6 - gct.tagLength - dataLen))) | ii ; + z = getBits(dataLen, "z") ; + } + } else { + ii = getBits(dataLen - (6 - gct.tagLength), "x") ; + x = (x << (dataLen - (6 - gct.tagLength))) | ii ; + y = getBits(dataLen, "y") ; + z = getBits(dataLen, "z") ; + } + + // Sign extend delta x y z components. + x = x << (32 - dataLen) ; x = x >> (32 - dataLen) ; + y = y << (32 - dataLen) ; y = y >> (32 - dataLen) ; + z = z << (32 - dataLen) ; z = z >> (32 - dataLen) ; + + // Normalize values. + dx = (short)(x << gct.rightShift) ; + dy = (short)(y << gct.rightShift) ; + dz = (short)(z << gct.rightShift) ; + + // Update current position, first adding deltas if in relative mode. + if (gct.absolute != 0) { + curX = dx ; curY = dy ; curZ = dz ; + if (debug) System.out.println(" absolute position: " + + curX + " " + curY + " " + curZ) ; + } else { + curX += dx ; curY += dy ; curZ += dz ; + if (debug) System.out.println(" delta position: " + + dx + " " + dy + " " + dz) ; + } + + // Do optional mesh buffer push. + if (mbp != 0) { + // Increment to next position (meshIndex is initialized to 15). + meshIndex = (meshIndex + 1) & 0xF ; + meshBuffer[meshIndex].x = curX ; + meshBuffer[meshIndex].y = curY ; + meshBuffer[meshIndex].z = curZ ; + if (debug) + System.out.println(" pushed position into mesh buffer at " + + meshIndex) ; + } + + // Convert point back to [-1..1] floating point. + fX = curX ; fX /= 32768.0 ; + fY = curY ; fY /= 32768.0 ; + fZ = curZ ; fZ /= 32768.0 ; + if (debug) + System.out.println(" result position " + fX + " " + fY + " " + fZ) ; + + curPos.set(fX, fY, fZ) ; + return mbp ; + } + + + // + // Process a set current normal opcode. + // + private void processSetNormal(int mbp) { + HuffmanTableEntry gct ; + int index, du, dv, n, dataLength ; + int ii ; + + // if next command is a mesh buffer reference, use this normal + meshState &= ~USE_MESH_NORMAL ; + + // use table 2 for normals + gct = gctables[2][currentHeader & 0x3F] ; + + if (debug) + System.out.println("GeometryDecompressor.processSetNormal\n" + + gct.toString()) ; + + // subtract up-shift amount to get true data (u, v) length + dataLength = gct.dataLength - gct.rightShift ; + + if (gct.absolute != 0) { + // + // Absolute normal case. Extract index from 6-bit tag. + // + index = currentHeader & BMASK[6-gct.tagLength] ; + + if (gct.tagLength != 0) { + // read in the rest of the 6-bit sex/oct pair (index) + ii = getBits(6 - (6 - gct.tagLength), "sex/oct") ; + index = (index << (6 - (6 - gct.tagLength))) | ii ; + } + + // read in u and v data + curU = getBits(dataLength, "u") ; + curV = getBits(dataLength, "v") ; + + // normalize u, v, sextant, and octant + curU = curU << gct.rightShift ; + curV = curV << gct.rightShift ; + + curSex = (index >> 3) & 0x7 ; + curOct = index & 0x7 ; + + if (debug) { + if (curSex < 6) + System.out.println(" absolute normal: sex " + curSex + + " oct " + curOct + + " u " + curU + " v " + curV) ; + else + System.out.println(" special normal: sex " + curSex + + " oct " + curOct) ; + } + } else { + // + // Relative normal case. Extract du from 6-bit tag. + // + du = currentHeader & BMASK[6-gct.tagLength] ; + + if (gct.tagLength + dataLength < 6) { + // normalize du, get dv + du = du >> (6 - gct.tagLength - dataLength) ; + dv = currentHeader & BMASK[6 - gct.tagLength - dataLength] ; + + if (gct.tagLength + 2*dataLength < 6) { + // normalize dv + dv = dv >> (6 - gct.tagLength - 2*dataLength) ; + } else if (gct.tagLength + 2*dataLength > 6) { + // read in rest of dv and normalize it + ii = getBits(dataLength - + (6 - gct.tagLength - dataLength), "dv") ; + dv = (dv << (dataLength - + (6 - gct.tagLength - dataLength))) | ii ; + } + } else if (gct.tagLength + dataLength > 6) { + // read in rest of du and normalize it + ii = getBits(dataLength - (6 - gct.tagLength), "du") ; + du = (du << (dataLength - (6 - gct.tagLength))) | ii ; + // read in dv + dv = getBits(dataLength, "dv") ; + } else { + // read in dv + dv = getBits(dataLength, "dv") ; + } + + // Sign extend delta uv components. + du = du << (32 - dataLength) ; du = du >> (32 - dataLength) ; + dv = dv << (32 - dataLength) ; dv = dv >> (32 - dataLength) ; + + // normalize values + du = du << gct.rightShift ; + dv = dv << gct.rightShift ; + + // un-delta + curU += du ; + curV += dv ; + + if (debug) + System.out.println(" delta normal: du " + du + " dv " + dv) ; + + // + // Check for normal wrap. + // + if (! ((curU >= 0) && (curV >= 0) && (curU + curV <= 64))) + if ((curU < 0) && (curV >= 0)) { + // wrap on u, same octant, different sextant + curU = -curU ; + switch (curSex) { + case 0: curSex = 4 ; break ; + case 1: curSex = 5 ; break ; + case 2: curSex = 3 ; break ; + case 3: curSex = 2 ; break ; + case 4: curSex = 0 ; break ; + case 5: curSex = 1 ; break ; + } + } else if ((curU >= 0) && (curV < 0)) { + // wrap on v, same sextant, different octant + curV = -curV ; + switch (curSex) { + case 1: case 5: + curOct = curOct ^ 4 ; // invert x axis + break ; + case 0: case 4: + curOct = curOct ^ 2 ; // invert y axis + break ; + case 2: case 3: + curOct = curOct ^ 1 ; // invert z axis + break ; + } + } else if (curU + curV > 64) { + // wrap on uv, same octant, different sextant + curU = 64 - curU ; + curV = 64 - curV ; + switch (curSex) { + case 0: curSex = 2 ; break ; + case 1: curSex = 3 ; break ; + case 2: curSex = 0 ; break ; + case 3: curSex = 1 ; break ; + case 4: curSex = 5 ; break ; + case 5: curSex = 4 ; break ; + } + } else { + throw new IllegalArgumentException + (J3dI18N.getString("GeometryDecompressor1")) ; + } + } + + // do optional mesh buffer push + if (mbp != 0) { + if (debug) + System.out.println(" pushing normal into mesh buffer at " + + meshIndex) ; + + meshBuffer[meshIndex].sextant = (short)curSex ; + meshBuffer[meshIndex].octant = (short)curOct ; + meshBuffer[meshIndex].u = (short)curU ; + meshBuffer[meshIndex].v = (short)curV ; + } + + // convert normal back to [-1..1] floating point + indexNormal(curSex, curOct, curU, curV, curNorm) ; + + // a set normal opcode when normals aren't bundled with the vertices + // is a global normal change. + if (! bundlingNorm) outputNormal(curNorm) ; + } + + + // + // Get the floating point normal from its sextant, octant, u, and v. + // + private void indexNormal(int sex, int oct, int u, int v, Vector3f n) { + float nx, ny, nz, t ; + + if (debug) System.out.println(" sextant " + sex + " octant " + oct + + " u " + u + " v " + v) ; + if (sex > 5) { + // special normals + switch (oct & 0x1) { + case 0: // six coordinate axes + switch (((sex & 0x1) << 1) | ((oct & 0x4) >> 2)) { + case 0: nx = 1.0f ; ny = nz = 0.0f ; break ; + case 1: ny = 1.0f ; nx = nz = 0.0f ; break ; + default: + case 2: nz = 1.0f ; nx = ny = 0.0f ; break ; + } + sex = 0 ; oct = (oct & 0x2) >> 1 ; + oct = (oct << 2) | (oct << 1) | oct ; + break ; + case 1: // eight mid + default: + oct = ((sex & 0x1) << 2) | (oct >> 1) ; + sex = 0 ; + nx = ny = nz = (float)(1.0/Math.sqrt(3.0)) ; + break ; + } + if ((oct & 0x1) != 0) nz = -nz ; + if ((oct & 0x2) != 0) ny = -ny ; + if ((oct & 0x4) != 0) nx = -nx ; + + } else { + // regular normals + nx = (float)gcNormals[v][u][0] ; + ny = (float)gcNormals[v][u][1] ; + nz = (float)gcNormals[v][u][2] ; + + // reverse the swap + if ((sex & 0x4) != 0) { t = nx ; nx = nz ; nz = t ; } + if ((sex & 0x2) != 0) { t = ny ; ny = nz ; nz = t ; } + if ((sex & 0x1) != 0) { t = nx ; nx = ny ; ny = t ; } + + // reverse the sign flip + if ((oct & 0x1) != 0) nz = -nz ; + if ((oct & 0x2) != 0) ny = -ny ; + if ((oct & 0x4) != 0) nx = -nx ; + } + + // return resulting normal + n.set(nx, ny, nz) ; + if (debug) + System.out.println(" result normal: " + nx + " " + ny + " " + nz) ; + } + + + // + // Process a set current color command. + // + private void processSetColor(int mbp) { + HuffmanTableEntry gct ; + short dr, dg, db, da ; + float fR, fG, fB, fA ; + int r, g, b, a, index, dataLength ; + int ii ; + + // If the next command is a mesh buffer reference, use this color. + meshState &= ~USE_MESH_COLOR ; + + // Get the huffman table entry. + gct = gctables[1][currentHeader & 0x3F] ; + + if (debug) + System.out.println("GeometryDecompressor.processSetColor\n" + + gct.toString()) ; + + // Get the true length of the data. + dataLength = gct.dataLength - gct.rightShift ; + + // Read in red, green, blue, and possibly alpha. + r = currentHeader & BMASK[6 - gct.tagLength] ; + a = 0 ; + + if (gct.tagLength + dataLength == 6) { + g = getBits(dataLength, "g") ; + b = getBits(dataLength, "b") ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + else if (gct.tagLength + dataLength < 6) { + r = r >> (6 - gct.tagLength - dataLength) ; + + g = currentHeader & BMASK[6-gct.tagLength-dataLength] ; + if (gct.tagLength + 2*dataLength == 6) { + b = getBits(dataLength, "b") ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + else if (gct.tagLength + 2*dataLength < 6) { + g = g >> (6 - gct.tagLength - 2*dataLength) ; + + b = currentHeader & BMASK[6-gct.tagLength-2*dataLength] ; + if (gct.tagLength + 3*dataLength == 6) { + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + else if (gct.tagLength + 3*dataLength < 6) { + b = b >> (6 - gct.tagLength - 3*dataLength) ; + + if (doingAlpha) { + a = currentHeader & + BMASK[6 - gct.tagLength - 4*dataLength] ; + if (gct.tagLength + 4 * dataLength < 6) { + a = a >> (6 - gct.tagLength - 3*dataLength) ; + } + else if (gct.tagLength + 4 * dataLength > 6) { + ii = getBits(dataLength - + (6-gct.tagLength - 3*dataLength), "a") ; + a = (a << (dataLength - + (6-gct.tagLength - 3*dataLength))) | ii ; + } + } + } else { + ii = getBits(dataLength - + (6 - gct.tagLength - 2*dataLength), "b") ; + b = (b << (dataLength - + (6 - gct.tagLength - 2*dataLength))) | ii ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + } else { + ii = getBits(dataLength - (6 - gct.tagLength - dataLength), + "g") ; + g = (g << (dataLength - + (6 - gct.tagLength - dataLength))) | ii ; + b = getBits(dataLength, "b") ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + } else { + ii = getBits(dataLength - (6 - gct.tagLength), "r") ; + r = (r << (dataLength - (6 - gct.tagLength))) | ii ; + g = getBits(dataLength, "g") ; + b = getBits(dataLength, "b") ; + if (doingAlpha) + a = getBits(dataLength, "a") ; + } + + // Sign extend delta x y z components. + r <<= (32 - dataLength) ; r >>= (32 - dataLength) ; + g <<= (32 - dataLength) ; g >>= (32 - dataLength) ; + b <<= (32 - dataLength) ; b >>= (32 - dataLength) ; + a <<= (32 - dataLength) ; a >>= (32 - dataLength) ; + + // Normalize values. + dr = (short)(r << gct.rightShift) ; + dg = (short)(g << gct.rightShift) ; + db = (short)(b << gct.rightShift) ; + da = (short)(a << gct.rightShift) ; + + // Update current position, first adding deltas if in relative mode. + if (gct.absolute != 0) { + curR = dr ; curG = dg ; curB = db ; + if (doingAlpha) curA = da ; + if (debug) System.out.println(" absolute color: r " + curR + + " g " + curG + " b " + curB + + " a " + curA) ; + } else { + curR += dr ; curG += dg ; curB += db ; + if (doingAlpha) curA += da ; + if (debug) System.out.println(" delta color: dr " + dr + + " dg " + dg + " db " + db + + " da " + da) ; + } + + // Do optional mesh buffer push. + if (mbp != 0) { + if (debug) + System.out.println(" pushing color into mesh buffer at " + + meshIndex) ; + + meshBuffer[meshIndex].r = curR ; + meshBuffer[meshIndex].g = curG ; + meshBuffer[meshIndex].b = curB ; + meshBuffer[meshIndex].a = curA ; + } + + // Convert point back to [-1..1] floating point. + fR = curR ; fR /= 32768.0 ; + fG = curG ; fG /= 32768.0 ; + fB = curB ; fB /= 32768.0 ; + fA = curA ; fA /= 32768.0 ; + + curColor.set(fR, fG, fB, fA) ; + if (debug) System.out.println(" result color: " + fR + + " " + fG + " " + fB + " " + fA) ; + + // A set color opcode when colors aren't bundled with the vertices + // is a global color change. + if (! bundlingColor) outputColor(curColor) ; + } + + + // + // Process a mesh buffer reference command. + // + private void processMeshBR() { + MeshBufferEntry entry ; + int index, normal ; + int ii ; + + if (debug) + System.out.println("GeometryDecompressor.processMeshBR") ; + + ii = getBits(1, "mbr") ; + + index = (currentHeader >>> 1) & 0xF ; + repCode = ((currentHeader & 0x1) << 1) | ii ; + + // Adjust index to proper place in fifo. + index = (meshIndex - index) & 0xf ; + if (debug) + System.out.println(" using index " + index) ; + + // Get reference to mesh buffer entry. + entry = meshBuffer[index] ; + curX = entry.x ; + curY = entry.y ; + curZ = entry.z ; + + // Convert point back to [-1..1] floating point. + curPos.set(((float)curX)/32768.0f, + ((float)curY)/32768.0f, + ((float)curZ)/32768.0f) ; + + if (debug) System.out.println(" retrieved position " + curPos.x + + " " + curPos.y + " " + curPos.z + + " replace code " + repCode) ; + + // Get mesh buffer normal if previous opcode was not a setNormal. + if (bundlingNorm && ((meshState & USE_MESH_NORMAL) != 0)) { + curSex = entry.sextant ; + curOct = entry.octant ; + curU = entry.u ; + curV = entry.v ; + + // Convert normal back to -1.0 - 1.0 floating point from index. + normal = (curSex<<15) | (curOct<<12) | (curU<<6) | curV ; + + if (debug) System.out.println(" retrieving normal") ; + indexNormal(curSex, curOct, curU, curV, curNorm) ; + } + + // Get mesh buffer color if previous opcode was not a setColor. + if (bundlingColor && ((meshState & USE_MESH_COLOR) != 0)) { + curR = entry.r ; + curG = entry.g ; + curB = entry.b ; + + // Convert point back to -1.0 - 1.0 floating point. + curColor.x = curR ; curColor.x /= 32768.0 ; + curColor.y = curG ; curColor.y /= 32768.0 ; + curColor.z = curB ; curColor.z /= 32768.0 ; + + if (doingAlpha) { + curA = entry.a ; + curColor.w = curA ; curColor.w /= 32768.0 ; + } + if (debug) + System.out.println(" retrieved color " + curColor.x + + " " + curColor.y + " " + curColor.z + + " " + curColor.w) ; + } + + // Reset meshState. + meshState = 0 ; + } + + + // Process a end-of-stream opcode. + private void processEos() { + if (debug) System.out.println("GeometryDecompressor.processEos") ; + } + + // Process a variable length no-op opcode. + private void processVNoop() { + int ii, ct ; + if (debug) System.out.println("GeometryDecompressor.processVNoop") ; + + ct = getBits(5, "noop count") ; + ii = getBits(ct, "noop bits") ; + } + + // Process a pass-through opcode. + private void processPassThrough() { + int ignore ; + if (debug) + System.out.println("GeometryDecompressor.processPassThrough") ; + + ignore = getBits(24, "passthrough") ; + ignore = getBits(32, "passthrough") ; + } + + // Process a skip-8 opcode. + private void processSkip8() { + int skip ; + if (debug) System.out.println("GeometryDecompressor.processSkip8") ; + + skip = getBits(8, "skip8") ; + } + + private void benchmarkStart(int length) { + vertexCount = 0 ; + System.out.println(" GeometryDecompressor: decompressing " + + length + " bytes...") ; + startTime = System.currentTimeMillis() ; + } + + private void benchmarkPrint(int length) { + float t = (System.currentTimeMillis() - startTime) / 1000.0f ; + System.out.println + (" done in " + t + " sec." + "\n" + + " decompressed " + vertexCount + " vertices at " + + (vertexCount/t) + " vertices/sec\n") ; + + System.out.print(" vertex data present: coords") ; + int floatVertexSize = 12 ; + if (bundlingNorm) { + System.out.print(" normals") ; + floatVertexSize += 12 ; + } + if (bundlingColor) { + System.out.println(" colors") ; + floatVertexSize += 12 ; + } + if (doingAlpha) { + System.out.println(" alpha") ; + floatVertexSize += 4 ; + } + System.out.println() ; + + System.out.println + (" bytes of data in generalized strip output: " + + (vertexCount * floatVertexSize) + "\n" + + " compression ratio: " + + (length / (float)(vertexCount * floatVertexSize)) + "\n") ; + } +} diff --git a/src/classes/share/javax/media/j3d/GeometryDecompressorRetained.java b/src/classes/share/javax/media/j3d/GeometryDecompressorRetained.java new file mode 100644 index 0000000..03673e8 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryDecompressorRetained.java @@ -0,0 +1,384 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.* ; +import java.util.* ; + +/** + * This class implements a retained geometry backend for the abstract + * GeometryDecompressor. + */ +class GeometryDecompressorRetained extends GeometryDecompressor { + private static final boolean debug = false ; + private static final boolean benchmark = false ; + private static final boolean statistics = false ; + private static final boolean printInfo = debug || benchmark || statistics ; + + // Type of connections in the compressed data: + // TYPE_POINT (1), TYPE_LINE (2), or TYPE_TRIANGLE (4). + private int bufferDataType ; + + // Data bundled with each vertex: bitwise combination of + // NORMAL_IN_BUFFER (1), COLOR_IN_BUFFER (2), ALPHA_IN_BUFFER (4). + private int dataPresent ; + + // Size of the compressed geometry in bytes. + private int size ; + + // Decompressor output state variables. + private Color4f curColor ; + private Vector3f curNormal ; + + // List for accumulating the output of the decompressor and converting to + // GeometryArray representations. + private GeneralizedVertexList vlist ; + + // Geometric bounds. + private Point3d lbounds = new Point3d() ; + private Point3d ubounds = new Point3d() ; + + // Decompression output constraints. The decompressor will still process + // all data contained in the compressed buffer, but will only retain data + // for output subject to these booleans. + private boolean boundsOnly = false ; + private boolean positionsOnly = false ; + + // A very rough gauge used to initialize the size of vlist, based on + // normal-per-vertex data collected from the HelloUniverse.cg file + // (seagull, '57 Chevy, dinosaur). + // + // TODO: get fudge values for other vertex combinations + private static final float bytesPerVertexFudge = 5.3f ; + + // Used for benchmarking if so configured. + private long startTime ; + private long endTime ; + + // Private convenience copies of various constants. + private static final int TYPE_POINT = + CompressedGeometryRetained.TYPE_POINT ; + private static final int TYPE_LINE = + CompressedGeometryRetained.TYPE_LINE ; + private static final int TYPE_TRIANGLE = + CompressedGeometryRetained.TYPE_TRIANGLE ; + private static final int FRONTFACE_CCW = + GeneralizedStripFlags.FRONTFACE_CCW ; + + /** + * If the given argument is true, sets the decompressor to output only the + * bounding box of the decompressed geometry. + * @param boundsOnly set to true if the decompressor should output only the + * geometric bounding box. + */ + void setDecompressBoundsOnly(boolean boundsOnly) { + this.boundsOnly = boundsOnly ; + if (boundsOnly) this.positionsOnly = false ; + } + + /** + * If the given argument is true, sets the decompressor to output only the + * decompressed positions, their connections, and the bounding box. + * @param positionsOnly set to true if the decompressor should output only + * position, connection, and bounding box data. + */ + void setDecompressPositionsOnly(boolean positionsOnly) { + this.positionsOnly = positionsOnly ; + if (positionsOnly) this.boundsOnly = false ; + } + + /** + * Decompress the geometry data in a CompressedGeometryRetained. The + * GeometryArray output is intended to be cached as retained data for + * efficient rendering. Global color and normal changes output by the + * decompressor are stored as redundant per-vertex data so that a single + * GeometryArray can be used. + * + * Since only one GeometryArray is created, if the bundling attributes + * change while building the vertex list then the decompression is + * aborted. There should be only one SetState bundling attribute command + * per CompressedGeometry. + * + * @param cgr CompressedGeometryRetained containing compressed geometry + * @return GeometryArrayRetained containing the results of the + * decompression, or null if only the bounds are computed. + */ + GeometryRetained decompress(CompressedGeometryRetained cgr) { + + if (! checkVersion(cgr.majorVersionNumber, cgr.minorVersionNumber)) { + return null ; + } + + vlist = null ; + curColor = null ; + curNormal = null ; + lbounds.set( 1.0, 1.0, 1.0) ; + ubounds.set(-1.0,-1.0,-1.0) ; + + // Get the descriptors for the compressed data. + bufferDataType = cgr.bufferType ; + dataPresent = cgr.bufferContents ; + if (printInfo) beginPrint() ; + + // Call the superclass decompress() method which calls the output + // methods of this subclass. The results are stored in vlist. + size = cgr.size ; + super.decompress(cgr.offset, size, cgr.compressedGeometry) ; + + if (boundsOnly) { + if (printInfo) endPrint() ; + return null ; + } + + // Convert the output to a GeometryRetained. + GeometryArray ga ; + switch(bufferDataType) { + case TYPE_TRIANGLE: + ga = vlist.toTriangleStripArray() ; + break ; + case TYPE_LINE: + ga = vlist.toLineStripArray() ; + break ; + case TYPE_POINT: + ga = vlist.toPointArray() ; + break ; + default: + throw new IllegalArgumentException + (J3dI18N.getString("GeometryDecompressorRetained0")) ; + } + + // Release the reference to the non-retained data. + ga.retained.setSource(null) ; + + if (printInfo) endPrint() ; + return (GeometryRetained)ga.retained ; + } + + /** + * Get the bounds of the decompressed geometry. + * @param bb BoundingBox to receive bounds + */ + void getBoundingBox(BoundingBox bb) { + bb.setLower(lbounds) ; + bb.setUpper(ubounds) ; + } + + /** + * Initialize the vertex output list based on the vertex format provided + * by the SetState decompression command. + */ + void outputVertexFormat(boolean bundlingNorm, boolean bundlingColor, + boolean doingAlpha) { + + if (boundsOnly) return ; + + if (vlist != null) + throw new IllegalStateException + (J3dI18N.getString("GeometryDecompressorRetained1")) ; + + int vertexFormat = GeometryArray.COORDINATES ; + + if (! positionsOnly) { + if (bundlingNorm) vertexFormat |= GeometryArray.NORMALS ; + if (bundlingColor) vertexFormat |= GeometryArray.COLOR ; + if (doingAlpha) vertexFormat |= GeometryArray.WITH_ALPHA ; + } + + vlist = new GeneralizedVertexList(vertexFormat, FRONTFACE_CCW, + (int)(size/bytesPerVertexFudge)) ; + } + + /** + * Process a decompressed vertex. + */ + void outputVertex(Point3f position, Vector3f normal, + Color4f color, int vertexReplaceCode) { + + if (position.x < lbounds.x) lbounds.x = position.x ; + if (position.y < lbounds.y) lbounds.y = position.y ; + if (position.z < lbounds.z) lbounds.z = position.z ; + + if (position.x > ubounds.x) ubounds.x = position.x ; + if (position.y > ubounds.y) ubounds.y = position.y ; + if (position.z > ubounds.z) ubounds.z = position.z ; + + if (boundsOnly) return ; + if (curColor != null) color = curColor ; + if (curNormal != null) normal = curNormal ; + + vlist.addVertex(position, normal, color, vertexReplaceCode) ; + + if (debug) { + System.out.println("outputVertex: flag " + vertexReplaceCode) ; + System.out.println(" position " + position.toString()) ; + if (normal != null) + System.out.println(" normal " + normal.toString()) ; + if (color != null) + System.out.println(" color " + color.toString()) ; + } + } + + /** + * Any global colors output by the decompressor are stored as per-vertex + * color in the retained data used internally by the renderer. This is + * done for performance and simplicity reasons, at the expense of + * replicating colors. + * + * The next method sets the current color that will be copied to each + * succeeding vertex. The outputColor() method is never called if + * colors are bundled with each vertex in the compressed buffer. + */ + void outputColor(Color4f color) { + if (boundsOnly || positionsOnly) return ; + if (debug) System.out.println("outputColor: " + color.toString()) ; + + if ((vlist.vertexFormat & GeometryArray.COLOR) == 0) { + if (vlist.size() > 0) + throw new IllegalStateException + (J3dI18N.getString("GeometryDecompressorRetained2")) ; + + vlist.setVertexFormat(vlist.vertexFormat | GeometryArray.COLOR) ; + } + + if (curColor == null) curColor = new Color4f() ; + curColor.set(color) ; + } + + /** + * Set the current normal that will be copied to each succeeding vertex + * output by the decompressor. The per-vertex copy is always needed since + * in Java 3D a normal is always associated with a vertex. This is never + * called if normals are bundled with each vertex in the compressed + * buffer. + */ + void outputNormal(Vector3f normal) { + if (boundsOnly || positionsOnly) return ; + if (debug) System.out.println("outputNormal: " + normal.toString()) ; + + if ((vlist.vertexFormat & GeometryArray.NORMALS) == 0) { + if (vlist.size() > 0) + throw new IllegalStateException + (J3dI18N.getString("GeometryDecompressorRetained3")) ; + + vlist.setVertexFormat(vlist.vertexFormat | GeometryArray.NORMALS) ; + } + + if (curNormal == null) curNormal = new Vector3f() ; + curNormal.set(normal) ; + } + + private void beginPrint() { + System.out.println("\nGeometryDecompressorRetained") ; + + switch(bufferDataType) { + case TYPE_TRIANGLE: + System.out.println(" buffer TYPE_TRIANGLE") ; + break ; + case TYPE_LINE: + System.out.println(" buffer TYPE_LINE") ; + break ; + case TYPE_POINT: + System.out.println(" buffer TYPE_POINT") ; + break ; + default: + throw new IllegalArgumentException + (J3dI18N.getString("GeometryDecompressorRetained4")) ; + } + + System.out.print(" buffer data present: coords") ; + + if ((dataPresent & CompressedGeometryHeader.NORMAL_IN_BUFFER) != 0) + System.out.print(" normals") ; + if ((dataPresent & CompressedGeometryHeader.COLOR_IN_BUFFER) != 0) + System.out.print(" colors") ; + if ((dataPresent & CompressedGeometryHeader.ALPHA_IN_BUFFER) != 0) + System.out.print(" alpha") ; + + System.out.println() ; + if (boundsOnly) System.out.println(" computing bounds only") ; + if (positionsOnly) System.out.println(" computing positions only") ; + + startTime = System.currentTimeMillis() ; + } + + private void endPrint() { + endTime = System.currentTimeMillis() ; + + if (benchmark || statistics) + printBench() ; + + if (statistics) + printStats() ; + } + + private void printBench() { + float t = (endTime - startTime) / 1000.0f ; + + if (boundsOnly) { + System.out.println(" decompression took " + t + " sec.\n") ; + return ; + } + + System.out.println + (" decompression + strip conversion took " + t + " sec.") ; + + switch(bufferDataType) { + case TYPE_POINT: + System.out.println + (" decompressed " + (vlist.size()) + + " points at " + (vlist.size()/t) + + " points/sec.\n") ; + break ; + case TYPE_LINE: + System.out.println + (" decompressed " + (vlist.vertexCount - vlist.stripCount) + + " lines at " + ((vlist.vertexCount - vlist.stripCount)/t) + + " lines/sec.\n") ; + break ; + case TYPE_TRIANGLE: + System.out.println + (" decompressed " + + (vlist.vertexCount - 2*vlist.stripCount) + + " triangles at " + + ((vlist.vertexCount - 2*vlist.stripCount)/t) + + " triangles/sec.\n") ; + break ; + } + } + + private void printStats() { + System.out.println(" bounding box:\n lower " + lbounds.toString() + + "\n upper " + ubounds.toString()) ; + + if (boundsOnly) return ; + + System.out.print + (" number of vertices in GeometryArray output: " + + vlist.vertexCount + "\n" + + " GeometryArray vertex data present: coords") ; + + if ((vlist.vertexFormat & GeometryArray.NORMALS) != 0) + System.out.print(" normals") ; + + if ((vlist.vertexFormat & GeometryArray.COLOR) != 0) + System.out.print(" colors") ; + + if ((vlist.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + System.out.print(" alpha") ; + + System.out.println("\n number of strips: " + vlist.stripCount) ; + if (vlist.stripCount > 0) + System.out.println + (" vertices/strip: " + + ((float)vlist.vertexCount / (float)vlist.stripCount)) ; + } +} diff --git a/src/classes/share/javax/media/j3d/GeometryDecompressorShape3D.java b/src/classes/share/javax/media/j3d/GeometryDecompressorShape3D.java new file mode 100644 index 0000000..366a2bc --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryDecompressorShape3D.java @@ -0,0 +1,466 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.* ; +import java.util.* ; + +/** + * This class implements a Shape3D backend for the abstract + * GeometryDecompressor. + */ +class GeometryDecompressorShape3D extends GeometryDecompressor { + private static final boolean debug = false ; + private static final boolean benchmark = false ; + private static final boolean statistics = false ; + private static final boolean printInfo = debug || benchmark || statistics ; + + // Type of connections in the compressed data: + // TYPE_POINT (1), TYPE_LINE (2), or TYPE_TRIANGLE (4). + private int bufferDataType ; + + // Data bundled with each vertex: bitwise combination of + // NORMAL_IN_BUFFER (1), COLOR_IN_BUFFER (2), ALPHA_IN_BUFFER (4). + private int dataPresent ; + + // List for accumulating the output of the decompressor and converting to + // GeometryArray representations. + private GeneralizedVertexList vlist ; + + // Accumulates Shape3D objects constructed from decompressor output. + private ArrayList shapes ; + + // Decompressor output state variables. + private Color4f curColor ; + private Vector3f curNormal ; + + // Variables for gathering statistics. + private int origVertexCount ; + private int stripCount ; + private int vertexCount ; + private int triangleCount ; + private long startTime ; + private long endTime ; + + // Triangle array type to construct. + private int triOutputType ; + + // Types of triangle output available. + private static final int TRI_SET = 0 ; + private static final int TRI_STRIP_SET = 1 ; + private static final int TRI_STRIP_AND_FAN_SET = 2 ; + private static final int TRI_STRIP_AND_TRI_SET = 3 ; + + // Private convenience copies of various constants. + private static final int TYPE_POINT = + CompressedGeometryRetained.TYPE_POINT ; + private static final int TYPE_LINE = + CompressedGeometryRetained.TYPE_LINE ; + private static final int TYPE_TRIANGLE = + CompressedGeometryRetained.TYPE_TRIANGLE ; + private static final int FRONTFACE_CCW = + GeneralizedStripFlags.FRONTFACE_CCW ; + + /** + * Decompress the given compressed geometry. + * @param cgr CompressedGeometryRetained object with compressed geometry + * @return an array of Shape3D with TriangleArray geometry if compressed + * data contains triangles; otherwise, Shape3D array containing PointArray + * or LineStripArray geometry + * @see CompressedGeometry + * @see GeometryDecompressor + */ + Shape3D[] toTriangleArrays(CompressedGeometryRetained cgr) { + return decompress(cgr, TRI_SET) ; + } + + + /** + * Decompress the given compressed geometry. + * @param cgr CompressedGeometryRetained object with compressed geometry + * @return an array of Shape3D with TriangleStripArray geometry if + * compressed data contains triangles; otherwise, Shape3D array containing + * PointArray or LineStripArray geometry + * @see CompressedGeometry + * @see GeometryDecompressor + */ + Shape3D[] toTriangleStripArrays(CompressedGeometryRetained cgr) { + return decompress(cgr, TRI_STRIP_SET) ; + } + + + /** + * Decompress the given compressed geometry. + * @param cgr CompressedGeometryRetained object with compressed geometry + * @return an array of Shape3D with TriangleStripArray and + * TriangleFanArray geometry if compressed data contains triangles; + * otherwise, Shape3D array containing PointArray or LineStripArray + * geometry + * @see CompressedGeometry + * @see GeometryDecompressor + */ + Shape3D[] toStripAndFanArrays(CompressedGeometryRetained cgr) { + return decompress(cgr, TRI_STRIP_AND_FAN_SET) ; + } + + + /** + * Decompress the given compressed geometry. + * @param cgr CompressedGeometryRetained object with compressed geometry + * @return an array of Shape3D with TriangleStripArray and + * TriangleArray geometry if compressed data contains triangles; + * otherwise, Shape3D array containing PointArray or LineStripArray + * geometry + * @see CompressedGeometry + * @see GeometryDecompressor + */ + Shape3D[] toStripAndTriangleArrays(CompressedGeometryRetained cgr) { + return decompress(cgr, TRI_STRIP_AND_TRI_SET) ; + } + + /** + * Decompress the data contained in a CompressedGeometryRetained and + * return an array of Shape3D objects using the specified triangle output + * type. The triangle output type is ignored if the compressed data + * contains points or lines. + */ + private Shape3D[] decompress(CompressedGeometryRetained cgr, + int triOutputType) { + + if (! checkVersion(cgr.majorVersionNumber, cgr.minorVersionNumber)) { + return null ; + } + + vlist = null ; + curColor = null ; + curNormal = null ; + + // Get descriptors for compressed data. + bufferDataType = cgr.bufferType ; + dataPresent = cgr.bufferContents ; + if (printInfo) beginPrint() ; + + // Initialize the decompressor backend. + this.triOutputType = triOutputType ; + shapes = new ArrayList() ; + + // Call the superclass decompress() method which calls the output + // methods of this subclass. The results are stored in vlist. + super.decompress(cgr.offset, cgr.size, cgr.compressedGeometry) ; + + // Convert the decompressor output to Shape3D objects. + addShape3D() ; + if (printInfo) endPrint() ; + + // Return the fixed-length output array. + Shape3D shapeArray[] = new Shape3D[shapes.size()] ; + return (Shape3D[])shapes.toArray(shapeArray) ; + } + + /** + * Initialize the vertex output list based on the vertex format provided + * by the SetState decompression command. + */ + void outputVertexFormat(boolean bundlingNorm, boolean bundlingColor, + boolean doingAlpha) { + + if (vlist != null) + // Construct shapes using the current vertex format. + addShape3D() ; + + int vertexFormat = GeometryArray.COORDINATES ; + + if (bundlingNorm) vertexFormat |= GeometryArray.NORMALS ; + if (bundlingColor) vertexFormat |= GeometryArray.COLOR ; + if (doingAlpha) vertexFormat |= GeometryArray.WITH_ALPHA ; + + vlist = new GeneralizedVertexList(vertexFormat, FRONTFACE_CCW) ; + } + + /** + * Add a new decompressed vertex to the current list. + */ + void outputVertex(Point3f position, Vector3f normal, + Color4f color, int vertexReplaceCode) { + + if (curNormal != null) normal = curNormal ; + vlist.addVertex(position, normal, color, vertexReplaceCode) ; + + if (debug) { + System.out.println(" outputVertex: flag " + vertexReplaceCode) ; + System.out.println(" position " + position.toString()) ; + if (normal != null) + System.out.println(" normal " + normal.toString()) ; + if (color != null) + System.out.println(" color " + color.toString()) ; + } + } + + /** + * Create a Shape3D using the current color for both the ambient and + * diffuse material colors, then start a new vertex list for the new + * color. The outputColor() method is never called if colors are bundled + * with each vertex in the compressed buffer. + */ + void outputColor(Color4f color) { + if (debug) System.out.println(" outputColor: " + color.toString()) ; + + if (vlist.size() > 0) { + // Construct Shape3D using the current color. + addShape3D() ; + + // Start a new vertex list for the new color. + vlist = new GeneralizedVertexList(vlist.vertexFormat, + FRONTFACE_CCW) ; + } + if (curColor == null) curColor = new Color4f() ; + curColor.set(color) ; + } + + /** + * Set the current normal that will be copied to each succeeding vertex + * output by the decompressor. The per-vertex copy is needed since in + * Java 3D a normal is always associated with a vertex. This method is + * never called if normals are bundled with each vertex in the compressed + * buffer. + */ + void outputNormal(Vector3f normal) { + if (debug) System.out.println(" outputNormal: " + normal.toString()) ; + + if ((vlist.vertexFormat & GeometryArray.NORMALS) == 0) { + if (vlist.size() > 0) + // Construct Shape3D using the current vertex format. + addShape3D() ; + + // Start a new vertex list with the new format. + vlist = new GeneralizedVertexList + (vlist.vertexFormat|GeometryArray.NORMALS, FRONTFACE_CCW) ; + } + if (curNormal == null) curNormal = new Vector3f() ; + curNormal.set(normal) ; + } + + /** + * Create a Shape3D object of the desired type from the current vertex + * list. Apply the current color, if non-null, as a Material attribute. + */ + private void addShape3D() { + Material m = new Material() ; + + if (curColor != null) { + if ((vlist.vertexFormat & GeometryArray.WITH_ALPHA) == 0) { + m.setAmbientColor(curColor.x, curColor.y, curColor.z) ; + m.setDiffuseColor(curColor.x, curColor.y, curColor.z) ; + } + else { + m.setAmbientColor(curColor.x, curColor.y, curColor.z) ; + m.setDiffuseColor(curColor.x, curColor.y, curColor.z, + curColor.w) ; + } + } + + if ((vlist.vertexFormat & GeometryArray.NORMALS) == 0) + m.setLightingEnable(false) ; + else + m.setLightingEnable(true) ; + + Appearance a = new Appearance() ; + a.setMaterial(m) ; + + switch(bufferDataType) { + case TYPE_TRIANGLE: + switch(triOutputType) { + case TRI_SET: + TriangleArray ta = vlist.toTriangleArray() ; + if (ta != null) + shapes.add(new Shape3D(ta, a)) ; + break ; + case TRI_STRIP_SET: + TriangleStripArray tsa = vlist.toTriangleStripArray() ; + if (tsa != null) + shapes.add(new Shape3D(tsa, a)) ; + break ; + case TRI_STRIP_AND_FAN_SET: + GeometryStripArray gsa[] = vlist.toStripAndFanArrays() ; + if (gsa[0] != null) + shapes.add(new Shape3D(gsa[0], a)) ; + if (gsa[1] != null) + shapes.add(new Shape3D(gsa[1], a)) ; + break ; + case TRI_STRIP_AND_TRI_SET: + GeometryArray ga[] = vlist.toStripAndTriangleArrays() ; + if (ga[0] != null) + shapes.add(new Shape3D(ga[0], a)) ; + if (ga[1] != null) + shapes.add(new Shape3D(ga[1], a)) ; + break ; + default: + throw new IllegalArgumentException + (J3dI18N.getString("GeometryDecompressorShape3D0")) ; + } + break ; + + case TYPE_LINE: + LineStripArray lsa = vlist.toLineStripArray() ; + if (lsa != null) + shapes.add(new Shape3D(lsa, a)) ; + break ; + + case TYPE_POINT: + PointArray pa = vlist.toPointArray() ; + if (pa != null) + shapes.add(new Shape3D(pa, a)) ; + break ; + + default: + throw new IllegalArgumentException + (J3dI18N.getString("GeometryDecompressorShape3D1")) ; + } + + if (benchmark || statistics) { + origVertexCount += vlist.size() ; + vertexCount += vlist.vertexCount ; + stripCount += vlist.stripCount ; + triangleCount += vlist.triangleCount ; + } + } + + private void beginPrint() { + System.out.println("\nGeometryDecompressorShape3D") ; + + switch(bufferDataType) { + case TYPE_TRIANGLE: + System.out.println(" buffer TYPE_TRIANGLE") ; + break ; + case TYPE_LINE: + System.out.println(" buffer TYPE_LINE") ; + break ; + case TYPE_POINT: + System.out.println(" buffer TYPE_POINT") ; + break ; + default: + throw new IllegalArgumentException + (J3dI18N.getString("GeometryDecompressorShape3D1")) ; + } + + System.out.print(" buffer data present: coords") ; + + if ((dataPresent & CompressedGeometryHeader.NORMAL_IN_BUFFER) != 0) + System.out.print(" normals") ; + if ((dataPresent & CompressedGeometryHeader.COLOR_IN_BUFFER) != 0) + System.out.print(" colors") ; + if ((dataPresent & CompressedGeometryHeader.ALPHA_IN_BUFFER) != 0) + System.out.print(" alpha") ; + + System.out.println() ; + + stripCount = 0 ; + vertexCount = 0 ; + triangleCount = 0 ; + origVertexCount = 0 ; + + startTime = System.currentTimeMillis() ; + } + + private void endPrint() { + endTime = System.currentTimeMillis() ; + + if (benchmark || statistics) + printBench() ; + + if (statistics) + printStats() ; + } + + private void printBench() { + float t = (endTime - startTime) / 1000.0f ; + System.out.println + (" decompression + strip conversion took " + t + " sec.") ; + + switch(bufferDataType) { + case TYPE_POINT: + System.out.println + (" points decompressed: " + vertexCount + "\n" + + " net decompression rate: " + (vertexCount/t) + + " points/sec.\n") ; + break ; + case TYPE_LINE: + System.out.println + (" lines decompressed: " + (vertexCount - stripCount) + "\n" + + " net decompression rate: " + ((vertexCount - stripCount)/t) + + " lines/sec.\n") ; + break ; + case TYPE_TRIANGLE: + System.out.println + (" triangles decompressed: " + + (vertexCount - 2*stripCount) + "\n" + + " net decompression rate: " + + ((vertexCount - 2*stripCount)/t) + " triangles/sec.\n") ; + break ; + } + } + + private void printStats() { + switch(triOutputType) { + case TRI_SET: + System.out.println(" using individual triangle output") ; + break ; + case TRI_STRIP_SET: + System.out.println(" using strip output") ; + break ; + case TRI_STRIP_AND_FAN_SET: + System.out.println(" using strips and fans for output") ; + break ; + case TRI_STRIP_AND_TRI_SET: + System.out.println(" using strips and triangles for output") ; + break ; + } + + System.out.print + (" number of Shape3D objects: " + shapes.size() + + "\n number of Shape3D decompressed vertices: ") ; + + if (triOutputType == TRI_SET || bufferDataType == TYPE_POINT) { + System.out.println(vertexCount) ; + } + else if (triOutputType == TRI_STRIP_AND_TRI_SET) { + System.out.println((vertexCount + triangleCount*3) + + "\n number of strips: " + stripCount + + "\n number of individual triangles: " + + triangleCount) ; + if (stripCount > 0) + System.out.println + (" vertices/strip: " + (float)vertexCount/stripCount + + "\n triangles represented in strips: " + + (vertexCount - 2*stripCount)) ; + } + else { + System.out.println(vertexCount + + "\n number of strips: " + stripCount) ; + if (stripCount > 0) + System.out.println + (" vertices/strip: " + (float)vertexCount/stripCount) ; + } + + System.out.print(" vertex data present in last Shape3D: coords") ; + if ((vlist.vertexFormat & GeometryArray.NORMALS) != 0) + System.out.print(" normals") ; + + if ((vlist.vertexFormat & GeometryArray.COLOR) != 0) { + System.out.print(" colors") ; + if ((vlist.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + System.out.print(" alpha") ; + } + System.out.println() ; + } +} + diff --git a/src/classes/share/javax/media/j3d/GeometryLock.java b/src/classes/share/javax/media/j3d/GeometryLock.java new file mode 100644 index 0000000..34b8378 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryLock.java @@ -0,0 +1,72 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; + +class GeometryLock { + + // Current thread holding the lock + Thread threadId = null; + + // Whether the lock is currently owned + boolean lockOwned = false; + + // Count > 1 , if there is nested lock by the same thread + int count = 0; + + // Number of outstanding threads waiting for the lock + int waiting = 0; + + + synchronized void getLock() { + Thread curThread = Thread.currentThread(); + // If the thread already has the lock, incr + // a count and return + if (threadId == curThread) { + count++; + return; + } + // Otherwise, wait until the lock is released + while (lockOwned) { + try { + waiting++; + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + waiting--; + } + count++; + // Acquire the lock + lockOwned = true; + threadId = curThread; + } + + synchronized void unLock() { + Thread curThread = Thread.currentThread(); + if (threadId == curThread) { + // If the lock count > 0, then return + if (--count > 0) { + return; + } + lockOwned = false; + threadId = null; + if (waiting > 0) { + notify(); + } + } + + } + +} diff --git a/src/classes/share/javax/media/j3d/GeometryRetained.java b/src/classes/share/javax/media/j3d/GeometryRetained.java new file mode 100644 index 0000000..34b665c --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryRetained.java @@ -0,0 +1,276 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; + +abstract class GeometryRetained extends NodeComponentRetained { + + static final int GEO_TYPE_NONE = -1; + + static final int GEO_TYPE_QUAD_SET = 1; + static final int GEO_TYPE_TRI_SET = 2; + static final int GEO_TYPE_POINT_SET = 3; + static final int GEO_TYPE_LINE_SET = 4; + static final int GEO_TYPE_TRI_STRIP_SET = 5; + static final int GEO_TYPE_TRI_FAN_SET = 6; + static final int GEO_TYPE_LINE_STRIP_SET = 7; + + static final int GEO_TYPE_INDEXED_QUAD_SET = 8; + static final int GEO_TYPE_INDEXED_TRI_SET = 9; + static final int GEO_TYPE_INDEXED_POINT_SET = 10; + static final int GEO_TYPE_INDEXED_LINE_SET = 11; + static final int GEO_TYPE_INDEXED_TRI_STRIP_SET = 12; + static final int GEO_TYPE_INDEXED_TRI_FAN_SET = 13; + static final int GEO_TYPE_INDEXED_LINE_STRIP_SET = 14; + + static final int GEO_TYPE_RASTER = 15; + static final int GEO_TYPE_TEXT3D = 16; + static final int GEO_TYPE_COMPRESSED = 17; + + static final int GEO_TYPE_TOTAL = 17; + static final int GEO_TYPE_GEOMETRYARRAY = 14; + + BoundingBox geoBounds = new BoundingBox(); + + // Indicates whether bounds need to be computed. + // Checked when a user does addUser/removeUser and count goes from 0 to one + // but geometry has not changed and there is no need to recompute + boolean boundsDirty = true; // Changed while holding the geoBounds lock + + int computeGeoBounds = 0; // Changed while holding the geoBounds lock + + // The "type" of this object + int geoType = GEO_TYPE_NONE; + + // The id used by the native code when building this object + int nativeId = -1; + + // A mask that indicates that something has changed in this object + int isDirty = 0xffff; + + + // Geometry Lock (used only by GeometryArrayRetained and RasterRetained) + GeometryLock geomLock = new GeometryLock(); + + // Lock used for synchronization of live state + Object liveStateLock = new Object(); + + abstract void update(); + + // A reference to the mirror copy of the geometry + GeometryRetained mirrorGeometry = null; + + // indicates whether the geometry in editable + boolean isEditable = true; + + // A list of Universes that this Geometry is referenced from + ArrayList universeList = new ArrayList(); + + // A list of ArrayLists which contain all the Shape3DRetained objects + // refering to this geometry. Each list corresponds to the universe + // above. + ArrayList userLists = new ArrayList(); + + // true if color not specified with alpha channel + boolean noAlpha = false; + static final double EPSILON = 1.0e-6; + + Point3d centroid = new Point3d(); + boolean recompCentroid = true; + // The cached value is evaluated by renderBin and used in determining + // whether to put it in display list or not + int cachedChangedFrequent = 0; + + static final int POINT_TYPE = 1; + static final int LINE_TYPE = 2; + static final int TRIANGLE_TYPE = 3; + static final int QUAD_TYPE = 4; + static final int RASTER_TYPE = 5; + static final int TEXT3D_TYPE = 6; + static final int COMPRESS_TYPE = 7; + + + boolean isEquivalenceClass( GeometryRetained geometry ) { + int t1 = getClassType(); + int t2 = geometry.getClassType(); + + if (t1 == QUAD_TYPE) { + t1 = TRIANGLE_TYPE; + } + if (t2 == QUAD_TYPE) { + t2 = TRIANGLE_TYPE; + } + return (t1 == t2); + } + + void incrComputeGeoBounds() { + synchronized(geoBounds) { + computeGeoBounds++; + // When it goes from zero to one, compute it .. + if (computeGeoBounds == 1 && source.isLive()) { + computeBoundingBox(); + } + } + } + + void decrComputeGeoBounds() { + synchronized(geoBounds) { + computeGeoBounds--; + } + } + + + // This adds a Shape3DRetained to the list of users of this geometry + void addUser(Shape3DRetained s) { + int index; + ArrayList shapeList; + + if (s.sourceNode.boundsAutoCompute) { + incrComputeGeoBounds(); + } + + // If static, no need to maintain a userlist + if (this instanceof GeometryArrayRetained) { + if (((GeometryArrayRetained)this).isWriteStatic()) { + return; + } + } + synchronized (universeList) { + if (universeList.contains(s.universe)) { + index = universeList.indexOf(s.universe); + shapeList = (ArrayList)userLists.get(index); + shapeList.add(s); + } else { + universeList.add(s.universe); + shapeList = new ArrayList(); + shapeList.add(s); + userLists.add(shapeList); + } + } + + } + + // This adds a Shape3DRetained to the list of users of this geometry + void removeUser(Shape3DRetained s) { + int index; + ArrayList shapeList; + + if (s.sourceNode.boundsAutoCompute) { + decrComputeGeoBounds(); + } + + if (this instanceof GeometryArrayRetained) { + if (((GeometryArrayRetained)this).isWriteStatic()) { + return; + } + } + + synchronized (universeList) { + index = universeList.indexOf(s.universe); + shapeList = (ArrayList)userLists.get(index); + shapeList.remove(shapeList.indexOf(s)); + if (shapeList.size() == 0) { + userLists.remove(index); + universeList.remove(index); + } + } + + } + + public void updateObject() { + this.update(); + } + + + abstract void computeBoundingBox(); + + void setLive(boolean inBackgroundGroup, int refCount) { + doSetLive(inBackgroundGroup,refCount); + super.markAsLive(); + } + + /** + * This setLive routine calls the superclass's method when reference + * count is 1 + */ + void doSetLive(boolean inBackgroundGroup, int refCount) { + super.doSetLive(inBackgroundGroup, refCount); + this.update(); + this.computeBoundingBox(); + } + + abstract void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha, + boolean multiScreen, int screen, + boolean ignoreVertexColors, + int pass); + + /** + * This method should return an int indicating the format of the vertices, + * if any, stored in the geometry. Instances that can return a valid value + * should override this method, otherwise it will be assumed that no + * valid vertex components exist. + * @return format of vertices in the GeometryRetained as specified by + * GeometryArray, if appropriate to this instance. + */ + int getVertexFormat() { + return 0 ; + } + + abstract boolean intersect(PickShape pickShape, double dist[], Point3d iPnt); + abstract boolean intersect(Bounds targetBound); + abstract boolean intersect(Point3d[] pnts); + abstract boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom); + + + boolean intersect(Transform3D thisLocalToVworld, + Transform3D otherLocalToVworld, GeometryRetained geom) { + Transform3D tg = VirtualUniverse.mc.getTransform3D(null); + tg.invert(otherLocalToVworld); + tg.mul(thisLocalToVworld); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, tg); + return intersect(tg, geom); + } + + + boolean intersect(Transform3D thisLocalToVworld, Bounds targetBound) { + Bounds transBound = (Bounds) targetBound.clone(); + + Transform3D tg = VirtualUniverse.mc.getTransform3D(null); + tg.invert(thisLocalToVworld); + transBound.transform(tg); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, tg); + return intersect(transBound); + } + + + boolean canBeInDisplayList(boolean alphaEditable) { + + return (VirtualUniverse.mc.isDisplayList) && + !(this.isEditable || + (!(this instanceof GeometryArrayRetained) && alphaEditable)|| + (alphaEditable && ((((GeometryArrayRetained)this).vertexFormat& + GeometryArray.COLOR) != 0)) || + (((((GeometryArrayRetained)this).vertexFormat & + GeometryArray.BY_REFERENCE) != 0) && !VirtualUniverse.mc.buildDisplayListIfPossible)); + } + + void computeCentroid() { + this.centroid.set(geoBounds.getCenter()); + } + + abstract int getClassType(); + +} diff --git a/src/classes/share/javax/media/j3d/GeometryStripArray.java b/src/classes/share/javax/media/j3d/GeometryStripArray.java new file mode 100644 index 0000000..feee968 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryStripArray.java @@ -0,0 +1,223 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The GeometryStripArray object is an abstract class that is extended for + * a set of GeometryArray strip primitives. These include LINE_STRIP, + * TRIANGLE_STRIP, and TRIANGLE_FAN. In addition to specifying the array + * of vertex elements, which is inherited from GeometryArray, the + * GeometryStripArray class specifies the number of strips and an + * array of per-strip vertex counts that specify where the separate strips + * appear in the vertex array. + */ + +public abstract class GeometryStripArray extends GeometryArray { + + // non-public, no parameter constructor + GeometryStripArray() {} + + /** + * Constructs an empty GeometryStripArray object with the specified + * number of vertices, vertex format, and + * array of per-strip vertex counts. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2 or TEXTURE_COORDINATE_3, to signal the + * inclusion of per-vertex texture coordinates 2D or 3D. + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * The length of this array is the number of separate strips. + * The sum of the elements in this array defines the total number + * of valid vertices that are rendered (validVertexCount). + * + * @exception IllegalArgumentException if + * <code>validVertexCount > vertexCount</code> + */ + public GeometryStripArray(int vertexCount, + int vertexFormat, + int[] stripVertexCounts) { + + super(vertexCount, vertexFormat); + ((GeometryStripArrayRetained)this.retained).setStripVertexCounts(stripVertexCounts); + } + + /** + * Constructs an empty GeometryStripArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and + * array of per-strip vertex counts. + * + * @param vertexCount the number of vertex elements in this array<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2 or TEXTURE_COORDINATE_3, to signal the + * inclusion of per-vertex texture coordinates 2D or 3D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code> or + * <code>TEXTURE_COORDINATE_3</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code> or + * <code>TEXTURE_COORDINATE_3</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * The length of this array is the number of separate strips. + * The sum of the elements in this array defines the total number + * of valid vertices that are rendered (validVertexCount). + * + * @exception IllegalArgumentException if + * <code>validVertexCount > vertexCount</code> + * + * @since Java 3D 1.2 + */ + public GeometryStripArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int[] stripVertexCounts) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap); + ((GeometryStripArrayRetained)this.retained).setStripVertexCounts(stripVertexCounts); + } + + /** + * Get number of strips in the GeometryStripArray. + * @return numStrips number of strips + */ + public int getNumStrips() { + if (isLiveOrCompiled()) + if(!this.getCapability(GeometryArray.ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryStripArray0")); + + return ((GeometryStripArrayRetained)this.retained).getNumStrips(); + } + + /** + * Sets the array of strip vertex counts. The length of this + * array is the number of separate strips. The elements in this + * array specify the number of vertices for each separate strip. + * The sum of the elements in this array defines the total number + * of valid vertices that are rendered (validVertexCount). + * + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * + * @exception IllegalArgumentException if any of the following are + * true: + * <ul> + * <code>initialVertexIndex + validVertexCount > vertexCount</code>,<br> + * <code>initialCoordIndex + validVertexCount > vertexCount</code>,<br> + * <code>initialColorIndex + validVertexCount > vertexCount</code>,<br> + * <code>initialNormalIndex + validVertexCount > vertexCount</code>,<br> + * <code>initialTexCoordIndex + validVertexCount > vertexCount</code> + * </ul> + * <p> + * + * @exception ArrayIndexOutOfBoundsException if the geometry data format + * is <code>BY_REFERENCE</code> and any the following + * are true for non-null array references: + * <ul> + * <code>CoordRef.length</code> < <i>num_words</i> * + * (<code>initialCoordIndex + validVertexCount</code>)<br> + * <code>ColorRef.length</code> < <i>num_words</i> * + * (<code>initialColorIndex + validVertexCount</code>)<br> + * <code>NormalRef.length</code> < <i>num_words</i> * + * (<code>initialNormalIndex + validVertexCount</code>)<br> + * <code>TexCoordRef.length</code> < <i>num_words</i> * + * (<code>initialTexCoordIndex + validVertexCount</code>)<br> + * </ul> + * where <i>num_words</i> depends on which variant of + * <code>set</code><i>Array</i><code>Ref</code> is used. + * + * @since Java 3D 1.3 + */ + public void setStripVertexCounts(int[] stripVertexCounts) { + if (isLiveOrCompiled()) + if(!this.getCapability(GeometryArray.ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryStripArray2")); + + ((GeometryStripArrayRetained)this.retained).setStripVertexCounts(stripVertexCounts); + + } + + /** + * Get a list of vertexCounts for each strip. The list is copied + * into the specified array. The array must be large enough to hold + * all of the ints. + * @param stripVertexCounts an array that will receive vertexCounts + */ + public void getStripVertexCounts(int[] stripVertexCounts) { + if (isLiveOrCompiled()) + if(!this.getCapability(GeometryArray.ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("GeometryStripArray1")); + + ((GeometryStripArrayRetained)this.retained).getStripVertexCounts(stripVertexCounts); + } + + /** + * This method is not supported for geometry strip arrays. + * The sum of the elements in the strip vertex counts array + * defines the valid vertex count. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setValidVertexCount(int validVertexCount) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/classes/share/javax/media/j3d/GeometryStripArrayRetained.java b/src/classes/share/javax/media/j3d/GeometryStripArrayRetained.java new file mode 100644 index 0000000..3f4dffd --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryStripArrayRetained.java @@ -0,0 +1,739 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; +import java.util.Vector; +import com.sun.j3d.internal.ByteBufferWrapper; +import com.sun.j3d.internal.BufferWrapper; +import com.sun.j3d.internal.FloatBufferWrapper; +import com.sun.j3d.internal.DoubleBufferWrapper; + + +/** + * The GeometryStripArray object is an abstract class that is extended for + * a set of GeometryArray strip primitives. These include LINE_STRIP, + * TRIANGLE_STRIP, and TRIANGLE_FAN. + */ + +abstract class GeometryStripArrayRetained extends GeometryArrayRetained { + + // Array of per-strip vertex counts + int stripVertexCounts[]; + + // Array of per-strip starting index + int stripStartVertexIndices[]; // start of vertices for both by-copy + // and by-ref + int stripStartOffsetIndices[]; // Used in byRef non_interleaved + + // Following variables are only used in the compile mode + // isCompiled = true + int[] compileNumStrips; + int[] compileStripCountOffset; + + + /** + * Set stripVertexCount data into local array + */ + void setStripVertexCounts(int stripVertexCounts[]){ + boolean nullGeo = false; + + int i, num = stripVertexCounts.length, total = 0; + for (i=0; i < num; i++) { + total += stripVertexCounts[i]; + if (this instanceof LineStripArrayRetained) { + if (stripVertexCounts[i] < 2) { + throw new IllegalArgumentException(J3dI18N.getString("LineStripArrayRetained1")); + } + } + else if (this instanceof TriangleStripArrayRetained) { + if (stripVertexCounts[i] < 3) { + throw new IllegalArgumentException(J3dI18N.getString("TriangleStripArrayRetained1")); + } + } + else if (this instanceof TriangleFanArrayRetained) { + if (stripVertexCounts[i] < 3) { + throw new IllegalArgumentException(J3dI18N.getString("TriangleFanArrayRetained1")); + } + } + } + + if ((initialVertexIndex + total) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryStripArray3")); + } + else if ((initialCoordIndex + total) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryStripArray7")); + } + else if ((initialColorIndex + total) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryStripArray4")); + } + else if ((initialNormalIndex + total) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString("GeometryStripArray5")); + } + else { + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + if ((vertexFormat & (GeometryArray.BY_REFERENCE|vertexFormat &GeometryArray.INTERLEAVED)) == GeometryArray.BY_REFERENCE) { + for (i = 0; i < texCoordSetCount; i++) { + if ((initialTexCoordIndex[i] + total) > vertexCount) { + throw new IllegalArgumentException(J3dI18N.getString( + "GeometryStripArray6")); + } + } + } + } + } + geomLock.getLock(); + dirtyFlag |= STRIPCOUNT_CHANGED; + validVertexCount = total; + this.stripVertexCounts = new int[num]; + stripStartVertexIndices = new int[num]; + stripStartOffsetIndices = new int[num]; + stripStartOffsetIndices[0] = 0; + if ((vertexFormat & (GeometryArray.BY_REFERENCE|vertexFormat &GeometryArray.INTERLEAVED)) == GeometryArray.BY_REFERENCE) { + stripStartVertexIndices[0] = initialCoordIndex; + nullGeo = ((vertexType & GeometryArrayRetained.VERTEX_DEFINED) == 0); + } + else { + stripStartVertexIndices[0] = initialVertexIndex; + if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + if (( vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { + nullGeo = (interLeavedVertexData == null); + } + else { + nullGeo = (interleavedFloatBufferImpl == null); + } + } + } + + for (i=0; i<num-1; i++) { + this.stripVertexCounts[i] = stripVertexCounts[i]; + stripStartVertexIndices[i+1] = stripStartVertexIndices[i] + + stripVertexCounts[i]; + stripStartOffsetIndices[i+1] = stripStartOffsetIndices[i]+stripVertexCounts[i]; + } + this.stripVertexCounts[num-1] = stripVertexCounts[num-1]; + + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + processCoordsChanged(nullGeo); + sendDataChangedMessage(true); + } + + } + void unIndexify(IndexedGeometryStripArrayRetained src) { + if ((src.vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + unIndexifyJavaArray(src); + } + else { + unIndexifyNIOBuffer(src); + } + } + void unIndexifyJavaArray(IndexedGeometryStripArrayRetained src) { + int vOffset = 0, srcOffset, tOffset = 0; + int base = src.initialIndexIndex; + int i,j, k, index, colorStride = 0; + float[] vdata = null; + + if (((src.vertexFormat & GeometryArray.BY_REFERENCE) == 0) || + ((src.vertexFormat & GeometryArray.INTERLEAVED) != 0)) { + + if ((src.vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + vdata = src.vertexData; + if ((src.vertexFormat & GeometryArray.COLOR) != 0) + colorStride = 4; + } + else if ((src.vertexFormat & GeometryArray.INTERLEAVED) != 0) { + vdata = src.interLeavedVertexData; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + colorStride = 4; + else if ((src.vertexFormat & GeometryArray.COLOR) != 0) + colorStride = 3; + } + + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j + base; + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + System.arraycopy(vdata, + src.indexNormal[index]*src.stride + src.normalOffset, + vertexData, vOffset + normalOffset, 3); + } + if (colorStride == 4) { + /* + System.out.println("vdata.length = "+vdata.length); + System.out.println("vertexData.length = "+vertexData.length); + System.out.println("src.stride = "+src.stride); + System.out.println("src.colorOffset = "+src.colorOffset); + System.out.println("index = "+index+" src.indexColor.length = "+src.indexColor.length); + System.out.println("src.indexColor[index] = "+src.indexColor[index]); + System.out.println("base = "+base); + */ + System.arraycopy(vdata, + src.indexColor[index]*src.stride + src.colorOffset, + vertexData, vOffset + colorOffset, colorStride); + } else if (colorStride == 3) { + System.arraycopy(vdata, + src.indexColor[index]*src.stride + src.colorOffset, + vertexData, vOffset + colorOffset, colorStride); + vertexData[vOffset + colorOffset + 3] = 1.0f; + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (k = 0; k < texCoordSetCount; k++) { + System.arraycopy(vdata, + (((int[])src.indexTexCoord[k])[index]) + *src.stride + src.textureOffset + + src.texCoordSetMapOffset[k], + vertexData, + vOffset + textureOffset + + texCoordSetMapOffset[k], + texCoordStride); + } + } + + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + System.arraycopy(vdata, + src.indexCoord[index]*src.stride + src.coordinateOffset, + vertexData, vOffset + coordinateOffset, 3); + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + } + else { + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + base = src.initialIndexIndex; + vOffset = normalOffset; + switch ((src.vertexType & NORMAL_DEFINED)) { + case NF: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + System.arraycopy(src.floatRefNormals, + src.indexNormal[index]*3, + vertexData, + vOffset, 3); + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case N3F: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexNormal[j+base]; + vertexData[vOffset] = src.v3fRefNormals[index].x; + vertexData[vOffset+1] = src.v3fRefNormals[index].y; + vertexData[vOffset+2] = src.v3fRefNormals[index].z; + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + default: + break; + } + } + if ((vertexFormat & GeometryArray.COLOR) != 0){ + base = src.initialIndexIndex; + vOffset = colorOffset; + int multiplier = 3; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + multiplier = 4; + + switch ((src.vertexType & COLOR_DEFINED)) { + case CF: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + System.arraycopy(src.floatRefColors, + src.indexColor[index]*multiplier, + vertexData, + vOffset, 4); + } + else { + System.arraycopy(src.floatRefColors, + src.indexColor[index]*multiplier, + vertexData, + vOffset, 3); + vertexData[vOffset+3] = 1.0f; + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case CUB: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexColor[j+base] * multiplier; + vertexData[vOffset] = (src.byteRefColors[index] & 0xff) * ByteToFloatScale; + vertexData[vOffset+1] = (src.byteRefColors[index+1] & 0xff) * ByteToFloatScale;; + vertexData[vOffset+2] = (src.byteRefColors[index+2] & 0xff) * ByteToFloatScale;; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + vertexData[vOffset+3] = (src.byteRefColors[index+3] & 0xff) * ByteToFloatScale; + } + else { + vertexData[vOffset+3] = 1.0f; + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case C3F: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexColor[j+base]; + vertexData[vOffset] = src.c3fRefColors[index].x; + vertexData[vOffset+1] = src.c3fRefColors[index].y; + vertexData[vOffset+2] = src.c3fRefColors[index].z; + vertexData[vOffset+3] = 1.0f; + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case C4F: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexColor[j+base]; + vertexData[vOffset] = src.c4fRefColors[index].x; + vertexData[vOffset+1] = src.c4fRefColors[index].y; + vertexData[vOffset+2] = src.c4fRefColors[index].z; + vertexData[vOffset+3] = src.c4fRefColors[index].w; + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case C3UB: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexColor[j+base]; + vertexData[vOffset] = (src.c3bRefColors[index].x & 0xff) * ByteToFloatScale; + vertexData[vOffset+1] = (src.c3bRefColors[index].y & 0xff) * ByteToFloatScale; + vertexData[vOffset+2] = (src.c3bRefColors[index].z & 0xff) * ByteToFloatScale; + vertexData[vOffset+3] = 1.0f; + + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case C4UB: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexColor[j+base]; + vertexData[vOffset] = (src.c4bRefColors[index].x & 0xff) * ByteToFloatScale; + vertexData[vOffset+1] = (src.c4bRefColors[index].y & 0xff) * ByteToFloatScale; + vertexData[vOffset+2] = (src.c4bRefColors[index].z & 0xff) * ByteToFloatScale; + vertexData[vOffset+3] = (src.c4bRefColors[index].w & 0xff) * ByteToFloatScale; + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + default: + break; + } + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + base = src.initialIndexIndex; + vOffset = textureOffset; + switch ((src.vertexType & TEXCOORD_DEFINED)) { + case TF: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + + for (k = 0, tOffset = vOffset; + k < texCoordSetCount; k++) { + System.arraycopy(src.refTexCoords[k], + ((int[])src.indexTexCoord[k])[index] + *texCoordStride, + vertexData, tOffset, texCoordStride); + tOffset += texCoordStride; + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case T2F: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + for (k = 0, tOffset = vOffset; + k < texCoordSetCount; k++) { + srcOffset = + ((int[])src.indexTexCoord[k])[index]; + vertexData[tOffset] = ((TexCoord2f[]) + src.refTexCoords[k])[srcOffset].x; + vertexData[tOffset+1] = ((TexCoord2f[]) + src.refTexCoords[k])[srcOffset].y; + tOffset += texCoordStride; + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case T3F: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + for (k = 0, tOffset = vOffset; + k < texCoordSetCount; k++) { + srcOffset = + ((int[])src.indexTexCoord[k])[index]; + vertexData[tOffset] = ((TexCoord3f[]) + src.refTexCoords[k])[srcOffset].x; + vertexData[tOffset+1] = ((TexCoord3f[]) + src.refTexCoords[k])[srcOffset].y; + vertexData[tOffset+2] = ((TexCoord3f[]) + src.refTexCoords[k])[srcOffset].z; + tOffset += texCoordStride; + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + + default: + break; + } + } + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + vOffset = coordinateOffset; + base = src.initialIndexIndex; + switch ((src.vertexType & VERTEX_DEFINED)) { + case PF: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + System.arraycopy(src.floatRefCoords, + src.indexCoord[index]*3, + vertexData, + vOffset, 3); + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case PD: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexCoord[j+base] * 3; + vertexData[vOffset] = (float)src.doubleRefCoords[index]; + vertexData[vOffset+1] = (float)src.doubleRefCoords[index+1]; + vertexData[vOffset+2] = (float)src.doubleRefCoords[index+2]; + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case P3F: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexCoord[j+base]; + vertexData[vOffset] = src.p3fRefCoords[index].x; + vertexData[vOffset+1] = src.p3fRefCoords[index].y; + vertexData[vOffset+2] = src.p3fRefCoords[index].z; + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case P3D: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexCoord[j+base]; + vertexData[vOffset] = (float)src.p3dRefCoords[index].x; + vertexData[vOffset+1] = (float)src.p3dRefCoords[index].y; + vertexData[vOffset+2] = (float)src.p3dRefCoords[index].z; + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + default: + break; + } + } + } + } + + void unIndexifyNIOBuffer(IndexedGeometryStripArrayRetained src) { + int vOffset = 0, srcOffset, tOffset = 0; + int base = src.initialIndexIndex; + int i,j, k, index, colorStride = 0; + + + // interleaved case + if ((src.vertexFormat & GeometryArray.INTERLEAVED) != 0) { + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + colorStride = 4; + else if ((src.vertexFormat & GeometryArray.COLOR) != 0) + colorStride = 3; + + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j + base; + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + src.interleavedFloatBufferImpl.position(src.indexNormal[index]*src.stride + src.normalOffset); + src.interleavedFloatBufferImpl.get(vertexData, vOffset + normalOffset, 3); + } + if (colorStride == 4) { + src.interleavedFloatBufferImpl.position(src.indexColor[index]*src.stride + src.colorOffset); + src.interleavedFloatBufferImpl.get(vertexData, vOffset + colorOffset, colorStride); + } else if (colorStride == 3) { + src.interleavedFloatBufferImpl.position(src.indexColor[index]*src.stride + src.colorOffset); + src.interleavedFloatBufferImpl.get(vertexData, vOffset + colorOffset, colorStride); + vertexData[vOffset + colorOffset + 3] = 1.0f; + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (k = 0; k < texCoordSetCount; k++) { + src.interleavedFloatBufferImpl.position((((int[])src.indexTexCoord[k])[index]) + *src.stride + src.textureOffset + + src.texCoordSetMapOffset[k]); + + src.interleavedFloatBufferImpl.get(vertexData, + vOffset + textureOffset + texCoordSetMapOffset[k], texCoordStride); + } + } + + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + src.interleavedFloatBufferImpl.position(src.indexCoord[index]*src.stride + src.coordinateOffset); + src.interleavedFloatBufferImpl.get( vertexData, vOffset + coordinateOffset, 3); + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + } + else { + if ((vertexFormat & GeometryArray.NORMALS) != 0){ + base = src.initialIndexIndex; + vOffset = normalOffset; + if((src.vertexType & NORMAL_DEFINED) != 0) { + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + src.floatBufferRefNormals.position(src.indexNormal[index]*3); + src.floatBufferRefNormals.get(vertexData, vOffset, 3); + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + } + } + if ((vertexFormat & GeometryArray.COLOR) != 0){ + base = src.initialIndexIndex; + vOffset = colorOffset; + int multiplier = 3; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) + multiplier = 4; + + switch ((src.vertexType & COLOR_DEFINED)) { + case CF: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + src.floatBufferRefColors.position(src.indexColor[index]*multiplier); + src.floatBufferRefColors.get(vertexData, vOffset, 4); + + } + else { + src.floatBufferRefColors.position(src.indexColor[index]*multiplier); + src.floatBufferRefColors.get(vertexData, vOffset, 3); + + vertexData[vOffset+3] = 1.0f; + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case CUB: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexColor[j+base] * multiplier; + vertexData[vOffset] = (src.byteBufferRefColors.get(index) & 0xff) * ByteToFloatScale; + vertexData[vOffset+1] = (src.byteBufferRefColors.get(index+1) & 0xff) * ByteToFloatScale;; + vertexData[vOffset+2] = (src.byteBufferRefColors.get(index+2) & 0xff) * ByteToFloatScale;; + if ((src.vertexFormat & GeometryArray.WITH_ALPHA) != 0) { + vertexData[vOffset+3] = (src.byteBufferRefColors.get(index+3) & 0xff) * ByteToFloatScale; + } + else { + vertexData[vOffset+3] = 1.0f; + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + default: + break; + } + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + base = src.initialIndexIndex; + vOffset = textureOffset; + FloatBufferWrapper texBuffer; + if((src.vertexType & TEXCOORD_DEFINED) != 0) { + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + + for (k = 0, tOffset = vOffset; + k < texCoordSetCount; k++) { + texBuffer = (FloatBufferWrapper)(((J3DBuffer) (src.refTexCoordsBuffer[k])).getBufferImpl()); + texBuffer.position(((int[])src.indexTexCoord[k])[index]*texCoordStride); + texBuffer.get(vertexData, tOffset, texCoordStride); + tOffset += texCoordStride; + } + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + } + } + if ((vertexFormat & GeometryArray.COORDINATES) != 0){ + vOffset = coordinateOffset; + base = src.initialIndexIndex; + switch ((src.vertexType & VERTEX_DEFINED)) { + case PF: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = j+base; + src.floatBufferRefCoords.position(src.indexCoord[index]*3); + src.floatBufferRefCoords.get(vertexData, vOffset, 3); + + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + case PD: + for (i=0; i < src.stripIndexCounts.length; i++) { + for (j=0; j < src.stripIndexCounts[i]; j++) { + index = src.indexCoord[j+base] * 3; + vertexData[vOffset] = (float)src.doubleBufferRefCoords.get(index); + vertexData[vOffset+1] = (float)src.doubleBufferRefCoords.get(index+1); + vertexData[vOffset+2] = (float)src.doubleBufferRefCoords.get(index+2); + vOffset += stride; + } + base += src.stripIndexCounts[i]; + } + break; + + default: + break; + } + } + } + } + + + /** + * Get number of strips in the GeometryStripArray + * @return numStrips number of strips + */ + int getNumStrips(){ + return stripVertexCounts.length; + } + + /** + * Get a list of vertexCounts for each strip + * @param stripVertexCounts an array that will receive vertexCounts + */ + void getStripVertexCounts(int stripVertexCounts[]){ + + int i, num = this.stripVertexCounts.length; + + for (i=0;i < num;i++) + { + stripVertexCounts[i] = this.stripVertexCounts[i]; + } + + } + + void getStripVertexCounts(int id, int counts[]) { + int stripOffset = compileStripCountOffset[id]; + int stripLength = compileNumStrips[id]; + System.arraycopy(stripVertexCounts, stripOffset, counts, 0,stripLength); + } + + + int getNumStrips(int id) { + return compileNumStrips[id]; + } + + // Called only for "by-copy" geometry + void mergeGeometryArrays(ArrayList list) { + int numMerge = list.size(); + int numStrips = 0; + + + for (int i = 0; i < numMerge; i++) { + numStrips += + ((GeometryStripArrayRetained)list.get(i)).stripVertexCounts.length; + } + stripVertexCounts = new int[numStrips]; + stripStartVertexIndices = new int[numStrips]; + stripStartOffsetIndices = new int[numStrips]; + int curStripOffset = 0; + int curStripIndexOffset = 0,stripLength; + int[] curStripVertexCounts; + int[] curStripStartIndices ; + int[] curStripOffsetIndices ; + + + compileNumStrips = new int[numMerge]; + compileStripCountOffset = new int[numMerge]; + for (int i = 0; i < numMerge; i++) { + GeometryStripArrayRetained strip = + (GeometryStripArrayRetained)list.get(i); + curStripVertexCounts = strip.stripVertexCounts; + curStripStartIndices = strip.stripStartVertexIndices; + curStripOffsetIndices = strip.stripStartOffsetIndices; + stripLength = curStripVertexCounts.length; + compileNumStrips[i] = stripLength; + compileStripCountOffset[i] = curStripOffset; + System.arraycopy(curStripVertexCounts, 0, stripVertexCounts, + curStripOffset, stripLength); + // Can't just copy StartIndices, have to update to reflect + // updated vertex position on the merged vertexData + for (int j = 0; j < stripLength; j++) { + stripStartVertexIndices[j+curStripOffset] = curStripStartIndices[j] + + curStripIndexOffset; + stripStartOffsetIndices[j+curStripOffset] = curStripOffsetIndices[j] + + curStripIndexOffset; + } + curStripOffset += stripLength; + curStripIndexOffset += strip.validVertexCount; + } + // Assign the merged validVertexCount + validVertexCount = curStripIndexOffset; + + // call the super to merge the vertex data + super.mergeGeometryArrays(list); + } +} diff --git a/src/classes/share/javax/media/j3d/GeometryStructure.java b/src/classes/share/javax/media/j3d/GeometryStructure.java new file mode 100644 index 0000000..2bdef2a --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryStructure.java @@ -0,0 +1,1157 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; +import java.util.Arrays; + + +/** + * A geometry structure is a object that organizes geometries + * and bounds. + */ + +class GeometryStructure extends J3dStructure { + /** + * used during Transform Processing + */ + UpdateTargets targets = null; + + /** + * A multiple read single write Lock to sychronize access into this + * GeometryStructure. + * To prevent deadlock a call to read/write lock must end with a read/write + * unlock respectively. + */ + private MRSWLock lock = null; + + /** + * A lock object to prevent concurrent getVisibleBHTree query. + */ + private Object visLock = new Object(); + + /** + * A lock object to prevent concurrent collideEntryList, + * collideExitList using toArray() in BehaviorStructure buildTree() + * while clearMirror() is invoked in GeometryStructure removeNode() + */ + private Object collideListLock = new Object(); + + /** + * Binary Hull Tree structure for handling geometry atoms. + * Do not change the following private variables to public, their access + * need to synchronize via lock. + */ + + private BHTree[] bhTreeArr = null; + private int bhTreeCount; + private int bhTreeMax; + private int bhTreeBlockSize = 5; + + /** + * The array of BHNode, a data pool, for passing data between GS and BHTrees. + * Do not change the following private variables to public, their access + * need to synchronize via lock. + */ + private BHNode[] bhNodeArr = null; + private int bhNodeCount, bhNodeMax; + private int bhNodeBlockSize = 50; + + // Support for multi-locale. + private Vector3d localeTrans = new Vector3d(); + + + //The lists of wakeupCriterion object currently in collision. + WakeupIndexedList collideEntryList; + WakeupIndexedList collideExitList; + WakeupIndexedList collideMovementList; + + // The lists of wakeupCriterion objects that GeometryStructure keeps + WakeupIndexedList wakeupOnCollisionEntry; + WakeupIndexedList wakeupOnCollisionExit; + WakeupIndexedList wakeupOnCollisionMovement; + + // When Shape insert/remove for WakeupOnCollisionxxx() using + // Group node and USE_GEOMETRY, we need to reevaluate the + // cache geometryAtoms list. + boolean reEvaluateWakeupCollisionGAs; + + private boolean transformMsg = false; + + /** + * Constructor. + */ + GeometryStructure(VirtualUniverse u) { + super(u, J3dThread.UPDATE_GEOMETRY); + bhNodeCount = 0; + bhNodeMax = bhNodeBlockSize; + bhNodeArr = new BHNode[bhNodeMax]; + bhTreeMax = 1; + bhTreeArr = new BHTree[bhTreeMax]; + bhTreeCount=0; + lock = new MRSWLock(); + collideEntryList = new WakeupIndexedList(WakeupOnCollisionEntry.class, + WakeupOnCollisionEntry.COLLIDEENTRY_IN_BS_LIST, u); + collideExitList = new WakeupIndexedList(WakeupOnCollisionExit.class, + WakeupOnCollisionExit.COLLIDEEXIT_IN_BS_LIST, u); + collideMovementList = new WakeupIndexedList(WakeupOnCollisionMovement.class, + WakeupOnCollisionMovement.COLLIDEMOVE_IN_BS_LIST, u); + wakeupOnCollisionEntry = new WakeupIndexedList(WakeupOnCollisionEntry.class, + WakeupOnCollisionEntry.COND_IN_GS_LIST, u); + wakeupOnCollisionExit = new WakeupIndexedList(WakeupOnCollisionExit.class, + WakeupOnCollisionExit.COND_IN_GS_LIST, u); + wakeupOnCollisionMovement = new WakeupIndexedList(WakeupOnCollisionMovement.class, + WakeupOnCollisionMovement.COND_IN_GS_LIST, u); + } + + void processMessages(long referenceTime) { + J3dMessage m; + J3dMessage[] messages = getMessages(referenceTime); + int nMsg = getNumMessage(); + + if (nMsg > 0) { + reEvaluateWakeupCollisionGAs = false; + for (int i=0; i < nMsg; i++) { + lock.writeLock(); + m = messages[i]; + switch (m.type) { + case J3dMessage.TRANSFORM_CHANGED: + transformMsg = true; + break; + case J3dMessage.SWITCH_CHANGED: + processSwitchChanged(m); + // may need to process dirty switched-on transform + if (universe.transformStructure.getLazyUpdate()) { + transformMsg = true; + } + break; + case J3dMessage.INSERT_NODES: + insertNodes((Object[])m.args[0]); + reEvaluateWakeupCollisionGAs = true; + break; + case J3dMessage.REMOVE_NODES: + removeNodes(m); + reEvaluateWakeupCollisionGAs = true; + break; + case J3dMessage.SHAPE3D_CHANGED: { + int comp = ((Integer)m.args[1]).intValue(); + if (comp == Shape3DRetained.GEOMETRY_CHANGED) { + m.args[0] = m.args[2]; + removeNodes(m); + insertNodes((Object[])m.args[3]); + reEvaluateWakeupCollisionGAs = true; + } + else if (comp == Shape3DRetained.APPEARANCE_CHANGED) { + processVisibleChanged(m.args[2], + ((GeometryAtom[]) m.args[3])); + } + break; + } + case J3dMessage.TEXT3D_DATA_CHANGED: + removeNodes(m); + insertNodes((Object[])m.args[1]); + break; + case J3dMessage.TEXT3D_TRANSFORM_CHANGED: + processBoundsChanged((Object []) m.args[0], false); + break; + case J3dMessage.MORPH_CHANGED: { + int comp = ((Integer)m.args[1]).intValue(); + if (comp == MorphRetained.GEOMETRY_CHANGED) { + // TODO: Optimize this case. + processBoundsChanged((Object []) m.args[3], false); + } + else if (comp == MorphRetained.APPEARANCE_CHANGED) { + processVisibleChanged(m.args[2], + ((GeometryAtom[]) m.args[3])); + } + break; + } + case J3dMessage.REGION_BOUND_CHANGED: + case J3dMessage.BOUNDS_AUTO_COMPUTE_CHANGED: + // Only set this flag, when bounds might be empty. + processBoundsChanged((Object [])m.args[0], false); + break; + case J3dMessage.GEOMETRY_CHANGED: + // System.out.println("J3dMessage.GEOMETRY_CHANGED"); + processBoundsChanged((Object []) m.args[0], false); + break; + case J3dMessage.RENDERINGATTRIBUTES_CHANGED: + processVisibleChanged(m.args[2], + ((GeometryAtom[]) m.args[3])); + break; + } + + lock.writeUnlock(); + m.decRefcount(); + } + + if (transformMsg) { + targets = universe.transformStructure.getTargetList(); + lock.writeLock(); + + processTransformChanged(targets); + + lock.writeUnlock(); + + transformMsg = false; + targets = null; + } + + Arrays.fill(messages, 0, nMsg, null); + } + + processCollisionDetection(); + } + + + private int getBHTreeIndex(Locale locale) { + int i; + + for (i=0; i< bhTreeCount; i++) { + if (bhTreeArr[i].locale == locale) + return i; + } + // Can't find will return -1 so that other + // program know this + return -1; + } + + private int getOrAddBHTreeIndex(Locale locale) { + int i; + + for (i=0; i< bhTreeCount; i++) { + if (bhTreeArr[i].locale == locale) + return i; + } + + if (bhTreeCount >= bhTreeMax) { + // allocate a bigger array here.... + if (J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.geometryStructure, J3dDebug.LEVEL_2, + "Expanding bhTreeArr array ...\n"); + bhTreeMax += bhTreeBlockSize; + BHTree[] oldBhTreeArr = bhTreeArr; + + bhTreeArr = new BHTree[bhTreeMax]; + System.arraycopy(oldBhTreeArr, 0, bhTreeArr, 0, oldBhTreeArr.length); + } + + bhTreeArr[bhTreeCount] = new BHTree(locale); + bhTreeCount++; + return i; + } + + private void clearBhNodeArr() { + bhNodeCount = 0; + } + + private void addToBhNodeArr(BHNode bhNode) { + + // Add to bhNodeArr. + if (bhNodeCount >= bhNodeMax) { + bhNodeMax += bhNodeBlockSize; + BHNode[] oldbhNodeArr = bhNodeArr; + + bhNodeArr = new BHNode[bhNodeMax]; + System.arraycopy(oldbhNodeArr, 0, bhNodeArr, 0, oldbhNodeArr.length); + } + + bhNodeArr[bhNodeCount] = bhNode; + bhNodeCount++; + } + + private void processVisibleChanged(Object valueObj, GeometryAtom[] gaArr) { + boolean visible = true; // Default is true. + int i, treeIndex; + + if ((gaArr == null) || (gaArr.length < 1)) + return; + + treeIndex = getBHTreeIndex(gaArr[0].locale); + + visible = ((Boolean)valueObj).booleanValue(); + + for ( i=gaArr.length-1; i>=0; i--) { + gaArr[i].visible = visible; + } + + } + + private void insertNodes(Object[] nodes) { + Object node; + GeometryAtom geomAtom; + BHTree currTree = null; + + clearBhNodeArr(); + + // System.out.println("GS : nodes.length is " + nodes.length); + + for (int i=0; i<nodes.length; i++) { + node = nodes[i]; + if (node instanceof GeometryAtom) { + synchronized (node) { + geomAtom = (GeometryAtom) node; + if (geomAtom.source.inBackgroundGroup) { + geomAtom.source.geometryBackground. + addBgGeometryAtomList(geomAtom); + continue; + } + BHLeafNode bhLeafNode = (BHLeafNode) + VirtualUniverse.mc.getBHNode(BHNode.BH_TYPE_LEAF); + bhLeafNode.leafIF = geomAtom; + geomAtom.bhLeafNode = bhLeafNode; + bhLeafNode.computeBoundingHull(); + // System.out.println("bhLeafNode.bHull is " + bhLeafNode.bHull); + addToBhNodeArr(bhLeafNode); + } + } else if (node instanceof GroupRetained) { + synchronized (node) { + GroupRetained group = (GroupRetained) node; + BHLeafNode bhLeafNode = (BHLeafNode) + VirtualUniverse.mc.getBHNode(BHNode.BH_TYPE_LEAF); + bhLeafNode.leafIF = group; + group.bhLeafNode = bhLeafNode; + bhLeafNode.computeBoundingHull(); + addToBhNodeArr(bhLeafNode); + } + } + } + + if (bhNodeCount < 1) { + return; + } + + // Look for the right BHTree to insert to. + if (currTree == null) { + // We must separate the following two calls + // since the first Call will allocate storage bhTreeArr + // for the second index operation. (see bug 4361998) + int idx = getOrAddBHTreeIndex(((BHLeafNode)bhNodeArr[0]).getLocale()); + currTree = bhTreeArr[idx]; + + } + + currTree.insert(bhNodeArr, bhNodeCount); + + // currTree.gatherTreeStatistics(); + } + + void removeNodes(J3dMessage m) { + Object[] nodes = (Object[]) m.args[0]; + BHTree currTree = null; + Object node; + int index; + + clearBhNodeArr(); + for (int i=0; i<nodes.length; i++) { + node = nodes[i]; + if (node instanceof GeometryAtom) { + synchronized (node) { + GeometryAtom geomAtom = (GeometryAtom) node; + if ((geomAtom.source != null) && + (geomAtom.source.inBackgroundGroup)) { + geomAtom.source.geometryBackground. + removeBgGeometryAtomList(geomAtom); + continue; + } + if (geomAtom.bhLeafNode != null) { + addToBhNodeArr(geomAtom.bhLeafNode); + // Dereference BHLeafNode in GeometryAtom. + geomAtom.bhLeafNode = null; + } + + } + } else if (node instanceof GroupRetained) { + if (((NodeRetained)node).nodeType != NodeRetained.ORDEREDGROUP) { + synchronized (node) { + GroupRetained group = (GroupRetained) node; + if (group.bhLeafNode != null) { + addToBhNodeArr(group.bhLeafNode); + // Dereference BHLeafNode in GroupRetained + group.bhLeafNode = null; + } + } + } + } else if (node instanceof BehaviorRetained) { + synchronized (node) { + BehaviorRetained behav = (BehaviorRetained) node; + // cleanup collideEntryList & collideExitList + // since we didn't remove + // it on remove in removeWakeupOnCollision() + + // Note that GeometryStructure may run in + // parallel with BehaviorStructure when + // BS invoke activateBehaviors() to buildTree() + // which in turn call addWakeupOnCollision() + // to modify collideEntryList at the same time. + + WakeupOnCollisionEntry wentry; + WakeupOnCollisionEntry wentryArr[] = + (WakeupOnCollisionEntry []) collideEntryList.toArray(); + for (int j=collideEntryList.arraySize()-1; j>=0; j--) { + wentry = wentryArr[j]; + if (wentry.behav == behav) { + collideEntryList.remove(wentry); + } + } + WakeupOnCollisionExit wexit; + WakeupOnCollisionExit wexitArr[] = + (WakeupOnCollisionExit []) collideExitList.toArray(); + for (int j=collideExitList.arraySize()-1; j>=0; j--) { + wexit = wexitArr[j]; + if (wexit.behav == behav) { + collideExitList.remove(wexit); + } + } + } + } + } + + if (bhNodeCount < 1) { + return; + } + + if (currTree == null) { + index = getBHTreeIndex(((BHLeafNode)bhNodeArr[0]).getLocale()); + if (index<0) + return; + currTree = bhTreeArr[index]; + } + currTree.delete(bhNodeArr, bhNodeCount); + + // It is safe to do it here since only GeometryStructure + // thread invoke wakeupOnCollisionEntry/Exit .toArray() + + + wakeupOnCollisionEntry.clearMirror(); + wakeupOnCollisionMovement.clearMirror(); + wakeupOnCollisionExit.clearMirror(); + + synchronized (collideListLock) { + collideEntryList.clearMirror(); + collideExitList.clearMirror(); + } + } + + + private void processBoundsChanged(Object[] nodes, boolean transformChanged) { + + int index; + Object node; + + clearBhNodeArr(); + for (int i = 0; i < nodes.length; i++) { + node = nodes[i]; + if (node instanceof GeometryAtom) { + synchronized (node) { + + GeometryAtom geomAtom = (GeometryAtom) node; + if (geomAtom.bhLeafNode != null) { + addToBhNodeArr(geomAtom.bhLeafNode); + } + } + } else if (node instanceof GroupRetained) { + + GroupRetained group = (GroupRetained) node; + if (group.nodeType != NodeRetained.SWITCH) { + synchronized (node) { + if (group.bhLeafNode != null) { + addToBhNodeArr(group.bhLeafNode); + } + } + } + } + } + + if (bhNodeCount < 1) { + return; + } + + index = getBHTreeIndex(((BHLeafNode)bhNodeArr[0]).getLocale()); + + if (index >= 0) { + bhTreeArr[index].boundsChanged(bhNodeArr, bhNodeCount); + + } + } + + private void processTransformChanged(UpdateTargets targets) { + + int i, j, index; + Object[] nodes, nodesArr; + UnorderList arrList; + int size; + + clearBhNodeArr(); + + arrList = targets.targetList[Targets.GEO_TARGETS]; + + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (j = 0; j < size; j++) { + nodes = (Object[])nodesArr[j]; + for (i = 0; i < nodes.length; i++) { + GeometryAtom geomAtom = (GeometryAtom) nodes[i]; + synchronized (geomAtom) { + if (geomAtom.bhLeafNode != null) { + addToBhNodeArr(geomAtom.bhLeafNode); + } + } + } + } + } + + + arrList = targets.targetList[Targets.GRP_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + for ( j = 0; j < size; j++) { + nodes = (Object[])nodesArr[j]; + for ( i = 0; i < nodes.length; i++) { + GroupRetained group = (GroupRetained) nodes[i]; + if (group.nodeType != NodeRetained.SWITCH) { + synchronized (group) { + if (group.bhLeafNode != null) { + addToBhNodeArr(group.bhLeafNode); + } + } + } + } + } + } + + if (bhNodeCount < 1) { + return; + } + + index = getBHTreeIndex(((BHLeafNode)bhNodeArr[0]).getLocale()); + + if (index >= 0) { + bhTreeArr[index].boundsChanged(bhNodeArr, bhNodeCount); + + } + } + + // This method is called by RenderBin to get a array of possibly visible + // sub-trees. + // bhTrees mustn't be null. + // Return true if bhTree's root in encompass by frustumBBox. + + boolean getVisibleBHTrees(RenderBin rBin, ArrayList bhTrees, + BoundingBox frustumBBox, + Locale locale, long referenceTime, + boolean stateChanged, + int visibilityPolicy) { + + int i, j; + boolean unviInFB = true; + + // System.out.println("GeometryStructure : view's locale is " + locale); + lock.readLock(); + + bhTrees.clear(); + if (bhTreeCount == 1) { + // For debugging only. + if (J3dDebug.devPhase) { + if (J3dDebug.doDebug(J3dDebug.geometryStructure, J3dDebug.LEVEL_2)) { + System.out.println("GeometryStructure : In simple case"); + System.out.println("GeometryStructure : view's locale is " + + locale); + System.out.println("GeometryStructure : bhTreeArr[0].locale is " + + bhTreeArr[0].locale); + } + } + // One locale case - Lets make the simple case fast. + synchronized(visLock) { + unviInFB = bhTreeArr[0].getVisibleBHTrees(rBin, bhTrees, frustumBBox, + referenceTime, + stateChanged, + visibilityPolicy, true); + } + } + else { + // Multiple locale case. + + // For debugging only. + if (J3dDebug.devPhase) + J3dDebug.doDebug(J3dDebug.geometryStructure, J3dDebug.LEVEL_2, + "GeometryStructure : bhTreeCount is " + + universe.geometryStructure.bhTreeCount + + " view's locale is " + locale + "\n"); + + BoundingBox localeFrustumBBox = new BoundingBox(); + + synchronized(visLock) { + + for (j=0; j<bhTreeCount; j++) { + if (J3dDebug.devPhase) { + J3dDebug.doDebug(J3dDebug.geometryStructure, J3dDebug.LEVEL_2, + "GeometryStructure : bhTreeArr[" + j + + "] is " + + bhTreeArr[j].locale + "\n"); + } + if (!locale.hiRes.equals(bhTreeArr[j].locale.hiRes)) { + bhTreeArr[j].locale.hiRes.difference(locale.hiRes, localeTrans); + + if (J3dDebug.devPhase) { + J3dDebug.doDebug(J3dDebug.geometryStructure, + J3dDebug.LEVEL_2, + "localeTrans is " + localeTrans + + "GeometryStructure : localeFrustumBBox " + + localeFrustumBBox + "\n" ); + } + + // Need to translate view frustumBBox here. + localeFrustumBBox.lower.x = frustumBBox.lower.x + localeTrans.x; + localeFrustumBBox.lower.y = frustumBBox.lower.y + localeTrans.y; + localeFrustumBBox.lower.z = frustumBBox.lower.z + localeTrans.z; + localeFrustumBBox.upper.x = frustumBBox.upper.x + localeTrans.x; + localeFrustumBBox.upper.y = frustumBBox.upper.y + localeTrans.y; + localeFrustumBBox.upper.z = frustumBBox.upper.z + localeTrans.z; + } + else { + frustumBBox.copy(localeFrustumBBox); + } + + if(!(bhTreeArr[j].getVisibleBHTrees(rBin, bhTrees, + localeFrustumBBox, + referenceTime, + stateChanged, + visibilityPolicy, + false))) { + unviInFB = false; + } + } + } + } + + lock.readUnlock(); + return unviInFB; + } + + GeometryAtom[] pickAll(Locale locale, PickShape shape) { + + int i; + UnorderList hitList = new UnorderList(BHNode.class); + hitList.clear(); + + lock.readLock(); + + i = getBHTreeIndex(locale); + if (i < 0) { + lock.readUnlock(); + return null; + } + + bhTreeArr[i].select(shape, hitList); + lock.readUnlock(); + + int size = hitList.size(); + + if (size < 1) + return null; + + BHNode[] hitArr = (BHNode []) hitList.toArray(false); + + GeometryAtom[] geometryAtoms = new GeometryAtom[size]; + for (i=0; i<size; i++) { + geometryAtoms[i] = (GeometryAtom)(((BHLeafNode)hitArr[i]).leafIF); + } + + return geometryAtoms; + } + + GeometryAtom pickAny(Locale locale, PickShape shape) { + + int i; + + BHNode hitNode = null; + + lock.readLock(); + + i = getBHTreeIndex(locale); + if (i < 0) { + lock.readUnlock(); + return null; + } + + hitNode = bhTreeArr[i].selectAny(shape); + + lock.readUnlock(); + + if (hitNode == null) + return null; + + return (GeometryAtom)(((BHLeafNode)hitNode).leafIF); + + } + + + void addWakeupOnCollision(WakeupOnCollisionEntry w) { + + boolean needTrigger = true; + + // Cleanup, since collideEntryList did not remove + // its condition in removeWakeupOnCollision + synchronized (collideListLock) { + WakeupOnCollisionEntry collideEntryArr[] = + (WakeupOnCollisionEntry []) collideEntryList.toArray(); + WakeupOnCollisionEntry wentry; + for (int i=collideEntryList.arraySize()-1; i>=0; i--) { + wentry = collideEntryArr[i]; + if ((wentry.behav == w.behav) && + (wentry.geometryAtoms == w.geometryAtoms)) { + collideEntryList.remove(i); + needTrigger = false; + break; + } + } + } + + // add to wakeup list + wakeupOnCollisionEntry.add(w); + w.updateCollisionBounds(false); + // check for collision and triggered event + BHLeafInterface target = collide(w.behav.locale, + w.accuracyMode, + w.geometryAtoms, + w.vwcBounds, + w.boundingLeaf, + w.armingNode, + null); + + if (target != null) { + collideEntryList.add(w); + w.setTarget(target); + } + + if ((target != null) && (needTrigger)) { + w.setTriggered(); + } + } + + + void addWakeupOnCollision(WakeupOnCollisionExit w) { + + // Cleanup, since collideExitList did not remove + // its condition in removeWakeupOnCollision + boolean needTrigger = true; + + synchronized (collideListLock) { + WakeupOnCollisionExit collideExitArr[] = + (WakeupOnCollisionExit []) collideExitList.toArray(); + WakeupOnCollisionExit wexit; + for (int i=collideExitList.arraySize()-1; i>=0; i--) { + wexit = collideExitArr[i]; + if ((wexit.behav == w.behav) && + (wexit.geometryAtoms == w.geometryAtoms)) { + collideExitList.remove(i); + needTrigger = false; + break; + } + } + } + + // add condition + wakeupOnCollisionExit.add(w); + w.updateCollisionBounds(false); + BHLeafInterface target = collide(w.behav.locale, + w.accuracyMode, + w.geometryAtoms, + w.vwcBounds, + w.boundingLeaf, + w.armingNode, + null); + + if (target != null) { + // store the target that cause this condition to collide + // this is used when this condition is triggered. + w.setTarget(target); + collideExitList.add(w); + } + + if (!needTrigger) { + return; + } + // see if the matching wakeupOnCollisionEntry + // condition exists + + synchronized (collideListLock) { + WakeupOnCollisionEntry collideEntryArr[] = + (WakeupOnCollisionEntry []) collideEntryList.toArray(); + WakeupOnCollisionEntry wentry; + + for (int i=collideEntryList.arraySize()-1; i>=0; i--) { + wentry = collideEntryArr[i]; + if ((wentry.behav == w.behav) && + (wentry.geometryAtoms == w.geometryAtoms)) { + // Should not call collideEntryList.remove(i); + // Otherwise wakeupOn for Entry case may call several + // time at when initialize if collide + if (target == null) { + w.setTriggered(); + } + break; + } + } + } + } + + void addWakeupOnCollision(WakeupOnCollisionMovement w) { + wakeupOnCollisionMovement.add(w); + w.updateCollisionBounds(false); + BHLeafInterface target = collide(w.behav.locale, + w.accuracyMode, + w.geometryAtoms, + w.vwcBounds, + w.boundingLeaf, + w.armingNode, + w); + if (target != null) { + w.setTarget(target); + collideMovementList.add(w); + } + } + + void removeWakeupOnCollision(WakeupOnCollisionEntry wentry) { + wakeupOnCollisionEntry.remove(wentry); + // No need to remove collideEntry, it is used next time + // when WakeupOnExitCollision is added to determine + // whether to trigger it. + } + + void removeWakeupOnCollision(WakeupOnCollisionExit wexit) { + wakeupOnCollisionExit.remove(wexit); + // No need to remove collideExit, it is used next time + // when WakeupOnExitCollision is added to determine + // whether to trigger it. + } + + + void removeWakeupOnCollision(WakeupOnCollisionMovement wmovement) { + wakeupOnCollisionMovement.remove(wmovement); + collideMovementList.remove(wmovement); // remove if exists + } + + /** + * This method test all wakeupOnCollision list and trigger the + * condition if collision occurs. + */ + void processCollisionDetection() { + int i, idx; + BHLeafInterface target; + + // handle WakeupOnCollisionEntry + WakeupOnCollisionEntry wentry; + WakeupOnCollisionEntry wentryArr[] = (WakeupOnCollisionEntry []) + wakeupOnCollisionEntry.toArray(); + + for (i = wakeupOnCollisionEntry.arraySize()-1; i >=0; i--) { + wentry = wentryArr[i]; + wentry.updateCollisionBounds(reEvaluateWakeupCollisionGAs); + target = collide(wentry.behav.locale, + wentry.accuracyMode, + wentry.geometryAtoms, + wentry.vwcBounds, + wentry.boundingLeaf, + wentry.armingNode, + null); + idx = collideEntryList.indexOf(wentry); + + if (target != null) { + if (idx < 0) { + collideEntryList.add(wentry); + wentry.setTarget(target); + wentry.setTriggered(); + } + } else { + if (idx >= 0) { + collideEntryList.remove(idx); + } + } + } + + // handle WakeupOnCollisionMovement + + WakeupOnCollisionMovement wmove; + WakeupOnCollisionMovement wmoveArr[] = (WakeupOnCollisionMovement []) + wakeupOnCollisionMovement.toArray(); + + for (i = wakeupOnCollisionMovement.arraySize()-1; i >=0; i--) { + wmove = wmoveArr[i]; + wmove.updateCollisionBounds(reEvaluateWakeupCollisionGAs); + target = collide(wmove.behav.locale, + wmove.accuracyMode, + wmove.geometryAtoms, + wmove.vwcBounds, + wmove.boundingLeaf, + wmove.armingNode, + wmove); + idx = collideMovementList.indexOf(wmove); + if (target != null) { + if (idx < 0) { + collideMovementList.add(wmove); + wmove.setTarget(target); + } else { + if (!wmove.duplicateEvent) { + wmove.setTriggered(); + } + } + } else { + if (idx >= 0) { + collideMovementList.remove(idx); + wmove.lastSrcBounds = null; + wmove.lastDstBounds = null; + } + } + } + + + // Finally, handle WakeupOnCollisionExit + + WakeupOnCollisionExit wexit; + WakeupOnCollisionExit wexitArr[] = (WakeupOnCollisionExit []) + wakeupOnCollisionExit.toArray(); + + for (i = wakeupOnCollisionExit.arraySize()-1; i >=0; i--) { + wexit = wexitArr[i]; + wexit.updateCollisionBounds(reEvaluateWakeupCollisionGAs); + target = collide(wexit.behav.locale, + wexit.accuracyMode, + wexit.geometryAtoms, + wexit.vwcBounds, + wexit.boundingLeaf, + wexit.armingNode, + null); + idx = collideExitList.indexOf(wexit); + if (target != null) { + if (idx < 0) { + collideExitList.add(wexit); + wexit.setTarget(target); + } + } else { + if (idx >= 0) { + collideExitList.remove(idx); + wexit.setTriggered(); + } + } + } + + } + + + /** + * Check for duplicate WakeupOnCollisionMovement event. + * We don't want to continue deliver event even though the + * two colliding object did not move but this Geometry update + * thread continue to run due to transform change in others + * shape not in collision. + */ + void checkDuplicateEvent(WakeupOnCollisionMovement wmove, + Bounds bound, + BHLeafInterface hitNode) { + Bounds hitBound; + + if ((wmove.lastSrcBounds != null) && + wmove.lastSrcBounds.equals(bound)) { + if (hitNode instanceof GeometryAtom) { + hitBound = ((GeometryAtom) hitNode).source.vwcBounds; + } else { + hitBound = ((GroupRetained) hitNode).collisionVwcBounds; + } + if ((wmove.lastDstBounds != null) && + wmove.lastDstBounds.equals(hitBound)) { + wmove.duplicateEvent = true; + } else { + wmove.duplicateEvent = false; + wmove.lastDstBounds = (Bounds) hitBound.clone(); + } + } else { + wmove.duplicateEvent = false; + wmove.lastSrcBounds = (Bounds) bound.clone(); + } + } + + + /** + * check if either the geomAtoms[] or + * bound or boundingLeaf collide with BHTree. + * Only one of geomAtoms, bound, boundingLeaf is non-null. + * If accurancyMode is USE_GEOMETRY, object geometry is used, + * otherwise object bounding box is used for collision + * detection. + * In case of GROUP & BOUND, the armingNode is used + * to tell whether the colliding Group is itself or not. + * Also in case GROUP, geomAtoms is non-null if USE_GEOMETRY. + * If cond != null, it must be instanceof WakeupOnCollisionMovement + */ + BHLeafInterface collide(Locale locale, + int accurancyMode, + UnorderList geomAtoms, + Bounds bound, + BoundingLeafRetained boundingLeaf, + NodeRetained armingNode, + WakeupCriterion cond) { + + lock.readLock(); + int idx = getBHTreeIndex(locale); + + if (idx < 0) { + lock.readUnlock(); + return null; + } + BHLeafInterface hitNode; + + if (geomAtoms != null) { + synchronized (bhTreeArr[idx]) { + if ((bound != null) && + (armingNode instanceof GroupRetained)) { + // Check Bound intersect first before process + // to individual Shape3D geometryAtoms + hitNode = bhTreeArr[idx].selectAny(bound, + accurancyMode, + (GroupRetained) + armingNode); + if (hitNode == null) { + lock.readUnlock(); + return null; + } + GeometryAtom galist[] = (GeometryAtom []) + geomAtoms.toArray(false); + + hitNode = bhTreeArr[idx].selectAny(galist, + geomAtoms.arraySize(), + accurancyMode); + + if (hitNode != null) { + lock.readUnlock(); + if (cond != null) { + checkDuplicateEvent((WakeupOnCollisionMovement) cond, + bound, hitNode); + } + return hitNode; + } + } else { + GeometryAtom ga = (GeometryAtom) geomAtoms.get(0); + hitNode = bhTreeArr[idx].selectAny(ga, accurancyMode); + + if (hitNode != null) { + lock.readUnlock(); + if (cond != null) { + checkDuplicateEvent((WakeupOnCollisionMovement) cond, + ga.source.vwcBounds, + hitNode); + } + return hitNode; + } + } + } + } else { + if (bound == null) { + if (boundingLeaf == null) { + lock.readUnlock(); + return null; + } + bound = boundingLeaf.transformedRegion; + } + if (bound == null) { + lock.readUnlock(); + return null; + } + if (armingNode instanceof GroupRetained) { + synchronized (bhTreeArr[idx]) { + hitNode = bhTreeArr[idx].selectAny(bound, + accurancyMode, + (GroupRetained) + armingNode); + lock.readUnlock(); + if ((hitNode != null) && (cond != null)) { + checkDuplicateEvent((WakeupOnCollisionMovement) cond, + bound, hitNode); + } + return hitNode; + } + } else { + synchronized (bhTreeArr[idx]) { + hitNode = bhTreeArr[idx].selectAny(bound, accurancyMode, + armingNode); + lock.readUnlock(); + if ((hitNode != null) && (cond != null)) { + checkDuplicateEvent((WakeupOnCollisionMovement) cond, + bound, hitNode); + } + return hitNode; + } + } + } + lock.readUnlock(); + return null; + } + + + /** + * This prevents wakeupCondition sent out message and set + * conditionMet to true but the + * BehaviorStructure/BehaviorScheduler is not fast enough to + * process the message and reset conditionMet to false + * when view deactivate/unregister. + */ + void resetConditionMet() { + BehaviorStructure.resetConditionMet(wakeupOnCollisionEntry); + BehaviorStructure.resetConditionMet(wakeupOnCollisionExit); + BehaviorStructure.resetConditionMet(wakeupOnCollisionMovement); + } + + /** + * This processes a switch change. + */ + private void processSwitchChanged(J3dMessage m) { + + int i; + UnorderList arrList; + int size, treeIndex; + Object[] nodes; + LeafRetained leaf; + +/* is now a NOOP + + UpdateTargets targets = (UpdateTargets)m.args[0]; + + arrList = targets.targetList[Targets.GEO_TARGETS]; + + if (arrList != null) { + size = arrList.size(); + nodes = arrList.toArray(false); + + treeIndex = getBHTreeIndex(((LeafRetained)nodes[0]).locale); + + for (i=0; i<size; i++) { + leaf = (LeafRetained)nodes[i]; + } + } +*/ + } + + void cleanup() { + collideEntryList.clear(); + collideExitList.clear(); + collideMovementList.clear(); + wakeupOnCollisionEntry.clear(); + wakeupOnCollisionExit.clear(); + wakeupOnCollisionMovement.clear(); + } +} diff --git a/src/classes/share/javax/media/j3d/GeometryUpdater.java b/src/classes/share/javax/media/j3d/GeometryUpdater.java new file mode 100644 index 0000000..8056463 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GeometryUpdater.java @@ -0,0 +1,43 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The GeometryUpdater interface is used in updating geometry data + * that is accessed by reference from a live or compiled GeometryArray + * object. Applications that wish to modify such data must define a + * class that implements this interface. An instance of that class is + * then passed to the <code>updateData</code> method of the + * GeometryArray object to be modified. + * + * @since Java 3D 1.2 + */ + +public interface GeometryUpdater { + /** + * Updates geometry data that is accessed by reference. + * This method is called by the updateData method of a + * GeometryArray object to effect + * safe updates to vertex data that + * is referenced by that object. Applications that wish to modify + * such data must implement this method and perform all updates + * within it. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * + * @param geometry the Geometry object being updated. + * @see GeometryArray#updateData + */ + public void updateData(Geometry geometry); +} diff --git a/src/classes/share/javax/media/j3d/GraphicsConfigTemplate3D.java b/src/classes/share/javax/media/j3d/GraphicsConfigTemplate3D.java new file mode 100644 index 0000000..8799c1a --- /dev/null +++ b/src/classes/share/javax/media/j3d/GraphicsConfigTemplate3D.java @@ -0,0 +1,372 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.GraphicsDevice; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsConfigTemplate; + +/** + * This class is used to obtain a valid GraphicsConfiguration that can be used by Java 3D. + * A user instantiates one of these objects and then sets all + * non-default attributes as desired. The getBestConfiguration() + * method in the GraphicsDevice class is then called with this + * GraphicsConfigTemplate and the "best" GraphicsConfiguration is returned. The "best" + * GraphicsConfiguration means that this GraphicsConfiguration is supported and it + * meets or exceeds what was requested in the GraphicsConfigTemplate. + * Null is returned if no such "best" GraphicsConfiguration is found. + * + * @see GraphicsConfigTemplate + * @see GraphicsDevice + * @see GraphicsConfiguration + */ +public class GraphicsConfigTemplate3D extends GraphicsConfigTemplate { + + int depthSize; + int doubleBuffer; + int blueSize; + int greenSize; + int redSize; + int sceneAntialiasing; + int stereo; + + // Temporary variables use for passing argument to/from Request Renderer + Object testCfg; + + static Object globalLock = new Object(); + static Object monitorLock = new Object(); + static boolean threadWaiting = false; + + /** + * The platform dependent template. Since there is no + * template-specific instance data in the NativeConfigTemplate3D + * class, we can create one statically. + */ + static NativeConfigTemplate3D nativeTemplate = + new NativeConfigTemplate3D(); + + /** + * Constructs a GraphicsConfigTemplate3D object with default parameters. + * The default values are as follows: + * <ul> + * depthSize : 16<br> + * doubleBuffer : REQUIRED<br> + * sceneAntialiasing : UNNECESSARY<br> + * stereo : UNNECESSARY<br> + * redSize : 2<br> + * greenSize : 2<br> + * blueSize : 2<br> + * </ul> + */ + public GraphicsConfigTemplate3D() { + doubleBuffer = REQUIRED; + stereo = UNNECESSARY; + depthSize = 16; + redSize = greenSize = blueSize = 2; + sceneAntialiasing = UNNECESSARY; + } + + /** + * Sets the double buffering requirement. It should be + * GraphicsConfigTemplate.REQUIRED, GraphicsConfigTemplate.PREFERRED, + * or GraphicsConfigTemplate.UNNECESSARY. + * If an invalid value is passed in, it is ignored. + * If the value of double buffering is + * GraphicsConfigTemplate.REQUIRED, and no GraphicsConfiguration is found + * that meets this requirement, null will be returned in getBestConfiguration(). + * @param value the value to set this field to + */ + public void setDoubleBuffer(int value) { + if (value < REQUIRED && value > UNNECESSARY) + return; + + doubleBuffer = value; + } + + /** + * Retrieves the double buffering value. + * @return the current value of the doubleBuffer attribute + */ + public int getDoubleBuffer() { + return doubleBuffer; + } + + /** + * Sets the stereo requirement. It should be + * GraphicsConfigTemplate.REQUIRED, GraphicsConfigTemplate.PREFERRED, + * or GraphicsConfigTemplate.UNNECESSARY. If an invalid value + * is passed in, it is ignored. If the value of stereo requirement is + * GraphicsConfigTemplate.REQUIRED, and no GraphicsConfiguration is found + * that meets this requirement, null will be returned in getBestConfiguration(). + * @param value the value to set this field to + */ + public void setStereo(int value) { + if (value < REQUIRED && value > UNNECESSARY) + return; + + stereo = value; + } + + /** + * Retrieves the stereo value. + * @return the current value of the stereo attribute. + */ + public int getStereo() { + return stereo; + } + + /** + * Sets the scene antialiasing requirement. It should be + * GraphicsConfigTemplate.REQUIRED, GraphicsConfigTemplate.PREFERRED, + * or GraphicsConfigTemplate.UNNECESSARY. If an invalid value + * is passed in, it is ignored. If the value of scene antialiasing is + * GraphicsConfigTemplate.REQUIRED, and no GraphicsConfiguration is found + * that meets this requirement, null will be returned in getBestConfiguration(). + * @param value the value to set this field to + */ + public void setSceneAntialiasing(int value) { + if (value < REQUIRED && value > UNNECESSARY) + return; + + sceneAntialiasing = value; + } + + /** + * Retrieves the scene antialiasing value. + * @return the current value of the scene antialiasing attribute. + */ + public int getSceneAntialiasing() { + return sceneAntialiasing; + } + + /** + * Sets the depth buffer size requirement. This is the minimum requirement. + * If no GraphicsConfiguration is found that meets or + * exceeds this minimum requirement, null will be returned in + * getBestConfiguration(). + * @param value the value to set this field to + */ + public void setDepthSize(int value) { + if (value < 0) + return; + + depthSize = value; + } + + /** + * Retrieves the size of the depth buffer. + * @return the current value of the depthSize attribute + */ + public int getDepthSize() { + return depthSize; + } + + /** + * Sets the number of red bits required. This is the minimum requirement. + * If no GraphicsConfiguration is found that meets or + * exceeds this minimum requirement, null will be returned in + * getBestConfiguration(). + * @param value the value to set this field to + */ + public void setRedSize(int value) { + if (value < 0) + return; + + redSize = value; + } + + /** + * Retrieves the number of red bits requested by this template. + * @return the current value of the redSize attribute. + */ + public int getRedSize() { + return redSize; + } + + + /** + * Sets the number of green bits required. This is the minimum requirement. + * If no GraphicsConfiguration is found that meets or + * exceeds this minimum requirement, null will be returned in + * getBestConfiguration(). + * @param value the value to set this field to + */ + public void setGreenSize(int value) { + if (value < 0) + return; + + greenSize = value; + } + + /** + * Retrieves the number of green bits requested by this template. + * @return the current value of the greenSize attribute. + */ + public int getGreenSize() { + return greenSize; + } + + /** + * Sets the number of blue bits required. This is the minimum requirement. + * If no GraphicsConfiguration is found that meets or + * exceeds this minimum requirement, null will be returned in + * getBestConfiguration(). + * @param value the value to set this field to + */ + public void setBlueSize(int value) { + if (value < 0) + return; + + blueSize = value; + } + + /** + * Retrieves the number of blue bits requested by this template. + * @return the current value of the blueSize attribute. + */ + public int getBlueSize() { + return blueSize; + } + + /** + * Implement the abstract function of getBestConfiguration() in GraphicsConfigTemplate. + * Usually this function is not directly called by the user. It is + * implicitly called by getBestConfiguration() in GraphicsDevice. + * The method getBestConfiguration() in GraphicsDevice will return whatever this function returns. + * This function will return the "best" GraphicsConfiguration. The "best" GraphicsConfiguration + * means that this GraphicsConfiguration is supported and it meets or exceeds what was requested in the + * GraphicsConfigTemplate. If no such "best" GraphicsConfiguration is found, null is returned. + * @param gc the array of GraphicsConfigurations to choose from + * + * @return the best GraphicsConfiguration + * + * @see GraphicsDevice + */ + public GraphicsConfiguration + getBestConfiguration(GraphicsConfiguration[] gc) { + if ((gc == null) || (gc.length == 0) || (gc[0] == null)) { + return null; + } + + synchronized (globalLock) { + testCfg = gc; + + // It is possible that the followign postRequest will + // cause request renderer run immediately before + // runMonitor(WAIT). So we need to set + // threadWaiting to true. + threadWaiting = true; + + // Prevent deadlock if invoke from Behavior callback since + // this thread has to wait Renderer thread to finish but + // MC can only handle postRequest and put it in Renderer + // queue when free. + if (Thread.currentThread() instanceof BehaviorScheduler) { + VirtualUniverse.mc.sendRenderMessage(gc[0], this, + MasterControl.GETBESTCONFIG); + } else { + VirtualUniverse.mc.postRequest(MasterControl.GETBESTCONFIG, this); + } + runMonitor(J3dThread.WAIT); + return (GraphicsConfiguration) testCfg; + } + } + + /** + * Returns a boolean indicating whether or not the given + * GraphicsConfiguration can be used to create a drawing + * surface that can be rendered to. + * + * @param gc the GraphicsConfiguration object to test + * + * @return <code>true</code> if this GraphicsConfiguration object + * can be used to create surfaces that can be rendered to, + * <code>false</code> if the GraphicsConfiguration can not be used + * to create a drawing surface usable by this API. + */ + public boolean isGraphicsConfigSupported(GraphicsConfiguration gc) { + if (gc == null) { + return false; + } + synchronized (globalLock) { + testCfg = gc; + threadWaiting = true; + if (Thread.currentThread() instanceof BehaviorScheduler) { + VirtualUniverse.mc.sendRenderMessage(gc, this, MasterControl.ISCONFIGSUPPORT); + } else { + VirtualUniverse.mc.postRequest(MasterControl.ISCONFIGSUPPORT, this); + } + runMonitor(J3dThread.WAIT); + return ((Boolean) testCfg).booleanValue(); + } + } + + /** + * Set the stereo/doubleBuffer/sceneAntialiasingAccum + * and hasSceneAntialiasingMultiSamples flags in Canvas3D + */ + static void getGraphicsConfigFeatures(Canvas3D c) { + synchronized (globalLock) { + threadWaiting = true; + if (Thread.currentThread() instanceof BehaviorScheduler) { + VirtualUniverse.mc.sendRenderMessage(c.graphicsConfiguration, c, + MasterControl.SET_GRAPHICSCONFIG_FEATURES); + } else { + VirtualUniverse.mc.postRequest(MasterControl.SET_GRAPHICSCONFIG_FEATURES, c); + } + runMonitor(J3dThread.WAIT); + } + } + + + + /** + * Set the queryProperties() map in Canvas3D + */ + static void setQueryProps(Canvas3D c) { + synchronized (globalLock) { + threadWaiting = true; + if (Thread.currentThread() instanceof BehaviorScheduler) { + VirtualUniverse.mc.sendRenderMessage(c.graphicsConfiguration, c, + MasterControl.SET_QUERYPROPERTIES); + } else { + VirtualUniverse.mc.postRequest(MasterControl.SET_QUERYPROPERTIES, c); + } + runMonitor(J3dThread.WAIT); + } + } + + + static void runMonitor(int action) { + // user thread will locked the globalLock when Renderer + // thread invoke this function so we can't use + // the same lock. + synchronized (monitorLock) { + switch (action) { + case J3dThread.WAIT: + if (threadWaiting) { + try { + monitorLock.wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + } + break; + case J3dThread.NOTIFY: + monitorLock.notify(); + threadWaiting = false; + break; + } + } + } +} diff --git a/src/classes/share/javax/media/j3d/GraphicsContext3D.java b/src/classes/share/javax/media/j3d/GraphicsContext3D.java new file mode 100644 index 0000000..22d5fc0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GraphicsContext3D.java @@ -0,0 +1,2999 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Vector; +import java.util.Enumeration; +import java.awt.Dimension; + +/** + * A GraphicsContext3D object is used for immediate mode rendering into + * a 3D canvas. It is created by, and associated with, a specific + * Canvas3D object. A GraphicsContext3D defines methods to set 3D graphics + * state and draw 3D geometric primitives. There are no public + * constructors of GraphicsContext3D. An application obtains a 3D graphics + * context object from the Canvas3D object that the application wishes + * to render into by using the getGraphicsContext3D method. A new graphics + * context is created if one does not already exist. A new GraphicsContext3D + * initializes its state variables to the following defaults: + * <UL> + * <LI> Background object: null </LI> + * <LI> Fog object: null </LI> + * <LI> ModelClip object: null </LI> + * <LI> Appearance object: null </LI> + * <LI> List of Light objects: empty </LI> + * <LI> high-res coordinate: (0, 0, 0) </LI> + * <LI> modelTransform: identity </LI> + * <LI> AuralAttributes object: null </LI> + * <LI> List of Sound objects: empty </LI> + * <LI> buffer override: false </LI> + * <LI> front buffer rendering: false </LI> + * <LI> stereo mode: <code>STEREO_BOTH</code> </LI> + * </UL> + * + * <p> + * Note that the drawing methods in this class are not necessarily + * executed immediately. They may be buffered up for future + * execution. Applications must call the + * <code><a href="#flush(boolean)">flush</a>(boolean)</code> + * method to ensure that the rendering actually happens. The flush + * method is implicitly called in the following cases: + * + * <ul> + * <li>The <code>readRaster</code> method calls + * <code>flush(true)</code></li> + * <li>The <code>Canvas3D.swap</code> method calls + * <code>flush(true)</code></li> + * <li>The Java 3D renderer calls <code>flush(true)</code> prior to + * swapping the buffer for a double buffered on-screen Canvas3D</li> + * <li>The Java 3D renderer calls <code>flush(true)</code> prior to + * copying into the off-screen buffer of an off-screen Canvas3D</li> + * <li>The Java 3D renderer calls <code>flush(false)</code> after + * calling the preRender, renderField, postRender, and postSwap + * Canvas3D callback methods.</li> + * </ul> + * + * <p> + * A single-buffered, pure-immediate mode application must explicitly + * call flush to ensure that the graphics will be rendered to the + * Canvas3D. + * + * @see Canvas3D#getGraphicsContext3D + */ +public class GraphicsContext3D extends Object { + /** + * Specifies that rendering is done to the left eye. + * @see #setStereoMode + * @since Java 3D 1.2 + */ + public static final int STEREO_LEFT = 0; + + /** + * Specifies that rendering is done to the right eye. + * @see #setStereoMode + * @since Java 3D 1.2 + */ + public static final int STEREO_RIGHT = 1; + + /** + * Specifies that rendering is done to both eyes. This is the + * default. + * @see #setStereoMode + * @since Java 3D 1.2 + */ + public static final int STEREO_BOTH = 2; + + + /** + * Canvas3D in which this GraphicsContext3D will render. + */ + Canvas3D canvas3d = null; + + int objectId = -1; + +// +// Graphics state +// +// current user specified graphics state + Background uBackground = null; + Fog uFog = null; + Appearance uAppearance = null; + Vector uLights = new Vector(); + HiResCoord uHiRes = new HiResCoord(); + Vector uSounds = new Vector(); + AuralAttributes uAuralAttributes = null; + boolean uBufferOverride = false; + boolean uFrontBufferRendering = false; + int uStereoMode = STEREO_BOTH; + ModelClip uModelClip = null; + +// Current rendering graphics state + // Current background + Background background = null; + + // Background to use if background is null; + BackgroundRetained black = new BackgroundRetained(); + + // Current fog + Fog fog = null; + + // Current modelClip + ModelClip modelClip = null; + + // Current appearance object + Appearance appearance = null; + + // default appearance retained object + AppearanceRetained defaultAppearanceRetained = new AppearanceRetained(); + + // The vector of lights + Vector lights = new Vector(); + + // Current High resolution coordinate + HiResCoord hiRes = new HiResCoord(); + + // Current modeling transform + Transform3D modelTransform = new Transform3D(); + Transform3D identityTransform = new Transform3D(); + + Transform3D modelClipTransform = null; + Transform3D normalTransform = null; + boolean normalTransformNeedToUpdate = true; + + // The vector of sounds + Vector sounds = new Vector(); + + // Current AuralAttributes state parameters + AuralAttributes auralAttributes = null; + + // The render object associated with this context + LightSet ls = null; + + // The current list of lights + LightRetained[] lightlist = null; + + // Ambient lights + Color3f sceneAmbient = new Color3f(0.0f, 0.0f, 0.0f); + + // The current number of lights, may be less than lightlist.length + int numLights = 0; + + // Current composite transform: hi-res + modelTransform + Transform3D compTransform = new Transform3D(); + + // Draw transform: hi-res + modelTransform + view + Transform3D drawTransform = new Transform3D(); + + // The view transform (VPC to EC). + // NOTE that this is *read-only* + Transform3D vpcToEc; + + // A boolean that indicates the lights have changed + boolean lightsChanged = false; + + // A boolean that indicates the sounds have changed + // TODO: the soundsChanged flag are set like lights methods set + // lightsChanged? but where is this supposed to be check??? + // lightsChanged tested in 'draw'; but Sound are not processed + // in draw. + boolean soundsChanged = false; + + // Buffer override flag; enables frontBufferRendering and stereoMode + // attributes. + boolean bufferOverride = false; + + // Forces rendering to the front buffer (if bufferOverride is true) + boolean frontBufferRendering = false; + + // Stereo mode for this buffer (if bufferOverride is true) + int stereoMode = STEREO_BOTH; + + // Read Buffer for reading raster of color image + byte[] byteBuffer = new byte[1]; + + // Read Buffer for reading floating depth image + float[] floatBuffer = new float[1]; + + // Read Buffer for reading integer depth image + int[] intBuffer = new int[1]; + + /** + * The cached ColoringAttributes color value. It is + * 1.0, 1.0, 1.0 if there is no ColoringAttributes. + */ + float red = 1.0f; + float green = 1.0f; + float blue = 1.0f; + + + /** + * Cached diffuse color value + */ + float dRed = 1.0f; + float dGreen = 1.0f; + float dBlue = 1.0f; + + /** + * The cached TransparencyAttributes transparency value. It is + * 0.0 if there is no TransparencyAttributes. + */ + float alpha = 0.0f; + + /** + * The cached visible flag for geometry. + */ + boolean visible = true; + + /** + * Cached values for polygonMode, line antialiasing, and point antialiasing + */ + int polygonMode = PolygonAttributes.POLYGON_FILL; + boolean lineAA = false; + boolean pointAA = false; + + + /** + /** + * A boolean indicating whether or not lighting should be on. + */ + boolean enableLighting = false; + + private Appearance defaultAppearance = null; + + private boolean geometryIsLocked = false; + + private boolean ignoreVertexColors = false; + + static final int CLEAR = 0; + static final int DRAW = 1; + static final int SWAP = 2; + static final int READ_RASTER = 3; + static final int SET_APPEARANCE = 4; + static final int SET_BACKGROUND = 5; + static final int SET_FOG = 6; + static final int SET_LIGHT = 7; + static final int INSERT_LIGHT = 8; + static final int REMOVE_LIGHT = 9; + static final int ADD_LIGHT = 10; + static final int SET_HI_RES = 11; + static final int SET_MODEL_TRANSFORM = 12; + static final int MULTIPLY_MODEL_TRANSFORM = 13; + static final int SET_SOUND = 14; + static final int INSERT_SOUND = 15; + static final int REMOVE_SOUND = 16; + static final int ADD_SOUND = 17; + static final int SET_AURAL_ATTRIBUTES = 18; + static final int SET_BUFFER_OVERRIDE = 19; + static final int SET_FRONT_BUFFER_RENDERING = 20; + static final int SET_STEREO_MODE = 21; + static final int FLUSH = 22; + static final int FLUSH2D = 23; + static final int DRAWANDFLUSH2D = 24; + static final int SET_MODELCLIP = 25; + static final int NCOMMANDS = 26; // needs to be incremented + // when a new command is to be + // added to the list + + static Integer commands[] = new Integer[NCOMMANDS]; + static Integer stereoModes[] = {new Integer(STEREO_LEFT), + new Integer(STEREO_RIGHT), + new Integer(STEREO_BOTH)}; + + // dirty bits + static final int BUFFER_MODE = 0x1; + private int dirtyMask = 0; + + + // multi-texture + int numActiveTexUnit = 0; + int lastActiveTexUnitIndex = 0; + boolean toSimulateMultiTex = true; + + // for read raster + volatile boolean readRasterReady = false; + + // for runMonitor + boolean gcReady = false; + int waiting = 0; + + + /** + * Constructs and creates a GraphicsContext3D object with default + * values. Users do not call this directly, rather they get a + * graphics context from a Canvas3D. + */ + GraphicsContext3D(Canvas3D canvas3d) { + this.canvas3d = canvas3d; + } + + /** + * Gets the Canvas3D that created this GraphicsContext3D. + * @return the Canvas3D that created this GraphicsContext3D + */ + public Canvas3D getCanvas3D() { + return this.canvas3d; + } + +// +// Methods to set/get graphics state +// + + /** + * Sets the current Appearance object to the specified + * Appearance component object. + * The graphics context stores a reference to the specified + * Appearance object. This means that the application may modify + * individual appearance attributes by using the appropriate + * methods on the Appearance object. + * If the Appearance object is null, default values will be used + * for all appearance attributes - it is as if an + * Appearance node were created using the default constructor. + * @param appearance the new Appearance object + */ + public void setAppearance(Appearance appearance) { + + if(appearance == null) { + if(defaultAppearance == null) { + defaultAppearance = new Appearance(); + } + appearance = defaultAppearance; + } + + NodeComponentRetained nc = ((AppearanceRetained)appearance.retained).material; + uAppearance = appearance; + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetAppearance(appearance); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_APPEARANCE, appearance, null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_APPEARANCE, appearance, null); + } + } + + void doSetAppearance(Appearance appearance) { + + if (appearance != null) { + NodeComponentRetained nc; + nc = ((AppearanceRetained)appearance.retained).material; + if (nc != null) { + nc.setInImmCtx(true); + enableLighting = ((MaterialRetained) nc).lightingEnable; + dRed = ((MaterialRetained) nc).diffuseColor.x; + dGreen = ((MaterialRetained) nc).diffuseColor.y; + dBlue = ((MaterialRetained) nc).diffuseColor.z; + } + else { + enableLighting = false; + } + + if (((AppearanceRetained)appearance.retained).texUnitState != null) { + TextureUnitStateRetained[] texUnitState = + ((AppearanceRetained)appearance.retained).texUnitState; + + for (int i = 0 ; i < texUnitState.length; i++) { + if (texUnitState[i] != null) { + texUnitState[i].setInImmCtx(true); + } + } + } + + nc = ((AppearanceRetained)appearance.retained).texture; + if (nc != null) { + nc.setInImmCtx(true); + } + + nc = ((AppearanceRetained)appearance.retained).texCoordGeneration; + if (nc != null) { + nc.setInImmCtx(true); + } + + nc = ((AppearanceRetained)appearance.retained).textureAttributes; + if (nc != null) { + nc.setInImmCtx(true); + } + + nc = ((AppearanceRetained)appearance.retained).coloringAttributes; + if (nc != null) { + nc.setInImmCtx(true); + red = ((ColoringAttributesRetained)nc).color.x; + green = ((ColoringAttributesRetained)nc).color.y; + blue = ((ColoringAttributesRetained)nc).color.z; + } + else { + red = 1.0f; + green = 1.0f; + blue = 1.0f; + } + + nc = ((AppearanceRetained)appearance.retained).transparencyAttributes; + if (nc != null) { + nc.setInImmCtx(true); + alpha = 1.0f - ((TransparencyAttributesRetained) nc).transparency; + } else { + alpha = 1.0f; + } + + nc = ((AppearanceRetained)appearance.retained).renderingAttributes; + if (nc != null) { + nc.setInImmCtx(true); + visible = ((RenderingAttributesRetained)nc).visible; + } + else + visible = true; + + nc = ((AppearanceRetained)appearance.retained).polygonAttributes; + if (nc != null) { + nc.setInImmCtx(true); + polygonMode = ((PolygonAttributesRetained)nc).polygonMode; + } + else { + polygonMode = PolygonAttributes.POLYGON_FILL; + } + + nc = ((AppearanceRetained)appearance.retained).lineAttributes; + if (nc != null) { + nc.setInImmCtx(true); + lineAA = ((LineAttributesRetained)nc).lineAntialiasing; + + } + else { + lineAA = false; + } + + nc = ((AppearanceRetained)appearance.retained).pointAttributes; + if (nc != null) { + if (nc.source.isLive()) + nc.setInImmCtx(true); + pointAA = ((PointAttributesRetained)nc).pointAntialiasing; + } + else { + pointAA = false; + } + + + if (this.appearance != null) { + AppearanceRetained app = (AppearanceRetained)this.appearance.retained; + app.setInImmCtx(false); + if (app.material != null) { + app.material.setInImmCtx(false); + } + if (app.texUnitState != null) { + for (int i = 0; i < app.texUnitState.length; i++) { + if (app.texUnitState[0] != null) + app.texUnitState[0].setInImmCtx(false); + } + } + if (app.texture != null) { + app.texture.setInImmCtx(false); + } + if (app.texCoordGeneration != null) { + app.texCoordGeneration.setInImmCtx(false); + } + if (app.textureAttributes != null) { + app.textureAttributes.setInImmCtx(false); + } + if (app.coloringAttributes != null) { + app.coloringAttributes.setInImmCtx(false); + } + if (app.transparencyAttributes != null) { + app.transparencyAttributes.setInImmCtx(false); + } + if (app.renderingAttributes != null) { + app.renderingAttributes.setInImmCtx(false); + } + if (app.polygonAttributes != null) { + app.polygonAttributes.setInImmCtx(false); + } + if (app.lineAttributes != null) { + app.lineAttributes.setInImmCtx(false); + } + if (app.pointAttributes != null) { + app.pointAttributes.setInImmCtx(false); + } + } + ((AppearanceRetained)appearance.retained).setInImmCtx(true); + } + this.appearance = appearance; + } + + /** + * Retrieves the current Appearance component object. + * @return the current Appearance object + */ + public Appearance getAppearance() { + return this.uAppearance; + } + + /** + * Sets the current Background to the specified Background + * leaf node object. + * The graphics context stores a reference to the specified + * Background node. This means that the application may modify + * the background color or image by using the appropriate + * methods on the Background node. The Background node must + * not be part of a live scene graph, nor may it subsequently + * be made part of a live scene graph-an IllegalSharingException + * is thrown in such cases. If the Background object is null, + * the default background color of black (0,0,0) is used to clear + * the canvas prior to rendering a new frame. The Background + * node's application region is ignored for immediate-mode + * rendering. + * @param background the new Background object + * @exception IllegalSharingException if the Background node + * is part of or is subsequently made part of a live scene graph. + */ + public void setBackground(Background background) { + if (background.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D11")); + } + if (((BackgroundRetained)background.retained).geometryBranch != null) + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D22")); + uBackground = background; + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetBackground(background); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_BACKGROUND, background, null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_BACKGROUND, background, null); + } + } + + void doSetBackground(Background background) { + BackgroundRetained bg; + + if (this.background != null) { + bg = (BackgroundRetained)this.background.retained; + bg.setInImmCtx(false); + if (bg.image != null) { + bg.image.freeSurface(); + } + } + bg = (BackgroundRetained)background.retained; + bg.setInImmCtx(true); + if (bg.image != null) { + bg.image.freeSurface(); + } + + this.background = background; + } + + /** + * Retrieves the current Background leaf node object. + * @return the current Background object + */ + public Background getBackground() { + return this.uBackground; + } + + /** + * Sets the current Fog to the specified Fog + * leaf node object. + * The graphics context stores a reference to the specified + * Fog node. This means that the application may modify the + * fog attributes using the appropriate methods on the Fog + * node object. The Fog node must not be part of a live + * scene graph, nor may it subsequently be made part of a + * live scene graph-an IllegalSharingException is thrown in + * such cases. If the Fog object is null, fog is disabled. + * Both the region of influence and the hierarchical scope + * of the Fog node are ignored for immediate-mode rendering. + * @param fog the new Fog object + * @exception IllegalSharingException if the Fog node + * is part of or is subsequently made part of a live scene graph. + */ + public void setFog(Fog fog) { + if (fog != null && fog.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D12")); + } + uFog = fog; + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetFog(fog); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_FOG, fog, null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_FOG, fog, null); + } + } + + void doSetFog(Fog fog) { + if (this.fog != null) { + ((FogRetained)this.fog.retained).setInImmCtx(false); + } + this.fog = fog; + if (fog != null) { + ((FogRetained)fog.retained).setInImmCtx(true); + + + if (fog.retained instanceof LinearFogRetained) + updateFogState((LinearFogRetained)fog.retained); + } + } + + /** + * Retrieves the current Fog leaf node object. + * @return the current Fog object + */ + public Fog getFog() { + return this.uFog; + } + + + /** + * Sets the current ModelClip leaf node to the specified object. + * The graphics context stores a reference to the specified + * ModelClip node. This means that the application may modify the + * model clipping attributes using the appropriate methods on the + * ModelClip node object. The ModelClip node must not be part of a + * live scene graph, nor may it subsequently be made part of a + * live scene graph-an IllegalSharingException is thrown in such + * cases. If the ModelClip object is null, model clipping is + * disabled. Both the region of influence and the hierarchical + * scope of the ModelClip node are ignored for immediate-mode + * rendering. + * + * @param modelClip the new ModelClip node + * + * @exception IllegalSharingException if the ModelClip node + * is part of or is subsequently made part of a live scene graph. + * + * @since Java 3D 1.2 + */ + public void setModelClip(ModelClip modelClip) { + if ((modelClip != null) && modelClip.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D25")); + } + uModelClip = modelClip; + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetModelClip(modelClip); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_MODELCLIP, + modelClip, null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_MODELCLIP, + modelClip, null); + } + } + + void doSetModelClip(ModelClip modelClip) { + ModelClipRetained mc = null; + + this.modelClip = modelClip; + + if (this.modelClip != null) { + mc = (ModelClipRetained)this.modelClip.retained; + mc.setInImmCtx(true); + + if (modelClipTransform == null) + modelClipTransform = new Transform3D(); + + // save the current model Transform + modelClipTransform.set(compTransform); + } + } + + /** + * Retrieves the current ModelClip leaf node object. + * @return the current ModelClip object + * + * @since Java 3D 1.2 + */ + public ModelClip getModelClip() { + return this.uModelClip; + } + + + /** + * Replaces the specified light with the light provided. + * The graphics context stores a reference to each light + * object in the list of lights. This means that the + * application may modify the light attributes for + * any of the lights using the appropriate methods on that + * Light node object. None of the Light nodes in the list + * of lights may be part of a live scene graph, nor may + * they subsequently be made part of a live scene graph - + * an IllegalSharingException is thrown in such cases. + * @param light the new light + * @param index which light to replace + * @exception IllegalSharingException if the Light node + * is part of or is subsequently made part of a live scene graph. + * @exception NullPointerException if the Light object is null. + */ + public void setLight(Light light, int index) { + if (light == null) { + throw new NullPointerException(J3dI18N.getString("GraphicsContext3D13")); + } + if (light.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D14")); + } + uLights.setElementAt(light, index); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetLight(light, index); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_LIGHT, light, + new Integer(index)); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_LIGHT, light, + new Integer(index)); + } + } + + void doSetLight(Light light, int index) { + + Light oldlight; + oldlight = (Light)this.lights.elementAt(index); + if (oldlight != null) { + ((LightRetained)oldlight.retained).setInImmCtx(false); + } + ((LightRetained)light.retained).setInImmCtx(true); + updateLightState((LightRetained)light.retained); + this.lights.setElementAt(light, index); + this.lightsChanged = true; + } + + /** + * Inserts the specified light at the specified index location. + * @param light the new light + * @param index at which location to insert + * @exception IllegalSharingException if the Light node + * is part of or is subsequently made part of a live scene graph. + * @exception NullPointerException if the Light object is null. + */ + public void insertLight(Light light, int index) { + if (light == null) { + throw new NullPointerException(J3dI18N.getString("GraphicsContext3D13")); + } + if (light.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D14")); + } + uLights.insertElementAt(light, index); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doInsertLight(light, index); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.INSERT_LIGHT, light, + new Integer(index)); + } else { + sendRenderMessage(true, GraphicsContext3D.INSERT_LIGHT, light, + new Integer(index)); + } + } + + void doInsertLight(Light light, int index) { + ((LightRetained)light.retained).setInImmCtx(true); + updateLightState((LightRetained)light.retained); + this.lights.insertElementAt(light, index); + this.lightsChanged = true; + } + + /** + * Removes the light at the specified index location. + * @param index which light to remove + */ + public void removeLight(int index) { + uLights.removeElementAt(index); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doRemoveLight(index); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.REMOVE_LIGHT, + new Integer(index), null); + } else { + sendRenderMessage(true, GraphicsContext3D.REMOVE_LIGHT, + new Integer(index), null); + } + } + + void doRemoveLight(int index) { + Light light = (Light) this.lights.elementAt(index); + + ((LightRetained)light.retained).setInImmCtx(false); + this.lights.removeElementAt(index); + this.lightsChanged = true; + } + + /** + * Retrieves the index selected light. + * @param index which light to return + * @return the light at location index + */ + public Light getLight(int index) { + return (Light) uLights.elementAt(index); + } + + /** + * Retrieves the enumeration object of all the lights. + * @return the enumeration object of all the lights + */ + public Enumeration getAllLights() { + return uLights.elements(); + } + + /** + * Appends the specified light to this graphics context's list of lights. + * Adding a null Light object to the list will result + * in a NullPointerException. Both the region of influence + * and the hierarchical scope of all lights in the list + * are ignored for immediate-mode rendering. + * @param light the light to add + * @exception IllegalSharingException if the Light node + * is part of or is subsequently made part of a live scene graph. + * @exception NullPointerException if the Light object is null. + */ + public void addLight(Light light) { + if (light == null) { + throw new NullPointerException(J3dI18N.getString("GraphicsContext3D13")); + } + + if (light.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D14")); + } + uLights.addElement(light); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doAddLight(light); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.ADD_LIGHT, light, null); + } else { + sendRenderMessage(true, GraphicsContext3D.ADD_LIGHT, light, null); + } + } + + void doAddLight(Light light) { + + ((LightRetained)light.retained).setInImmCtx(true); + updateLightState((LightRetained)light.retained); + this.lights.addElement(light); + this.lightsChanged = true; + } + + /** + * Retrieves the current number of lights in this graphics context. + * @return the current number of lights + */ + public int numLights() { + return this.uLights.size(); + } + + + private Transform3D getNormalTransform() { + if (compTransform.isRigid()) { + return compTransform; + } + if (normalTransform == null) { + normalTransform = new Transform3D(); + } + + if (normalTransformNeedToUpdate) { + normalTransform.invert(compTransform); + normalTransform.transpose(); + normalTransformNeedToUpdate = false; + } + return normalTransform; + } + + + void updateFogState(LinearFogRetained lfog) { + lfog.localToVworldScale = modelTransform.getDistanceScale(); + } + + + void updateLightState(LightRetained light) { + + if (light instanceof DirectionalLightRetained) { + DirectionalLightRetained dl = (DirectionalLightRetained) light; + + Transform3D xform = getNormalTransform(); + xform.transform(dl.direction, dl.xformDirection); + dl.xformDirection.normalize(); + + } else if (light instanceof SpotLightRetained) { + SpotLightRetained sl = (SpotLightRetained) light; + + Transform3D xform = getNormalTransform(); + xform.transform(sl.direction, sl.xformDirection); + sl.xformDirection.normalize(); + this.modelTransform.transform(sl.position, sl.xformPosition); + + } else if (light instanceof PointLightRetained) { + PointLightRetained pl = (PointLightRetained) light; + + this.modelTransform.transform(pl.position,pl.xformPosition); + + pl.localToVworldScale = modelTransform.getDistanceScale(); + + } + } + + /** + * Sets the HiRes coordinate of this context to the location + * specified by the parameters provided. + * The parameters x, y, and z are arrays of eight 32-bit + * integers that specify the high-resolution coordinates point. + * @param x an eight element array specifying the x position + * @param y an eight element array specifying the y position + * @param z an eight element array specifying the z position + * @see HiResCoord + */ + public void setHiRes(int[] x, int[] y, int[] z) { + HiResCoord hiRes = new HiResCoord(x, y, z); + setHiRes(hiRes); + } + + /** + * Sets the HiRes coordinate of this context + * to the location specified by the HiRes argument. + * @param hiRes the HiRes coordinate specifying the a new location + */ + public void setHiRes(HiResCoord hiRes) { + uHiRes.setHiResCoord(hiRes); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetHiRes(hiRes); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_HI_RES, hiRes, null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_HI_RES, hiRes, null); + } + } + + void doSetHiRes(HiResCoord hiRes) { + this.hiRes.setHiResCoord(hiRes); + computeCompositeTransform(); + } + + /** + * Retrieves the current HiRes coordinate of this context. + * @param hiRes a HiResCoord object that will receive the + * HiRes coordinate of this context + */ + public void getHiRes(HiResCoord hiRes) { + uHiRes.getHiResCoord(hiRes); + } + + /** + * Sets the current model transform to a copy of the specified + * transform. + * A BadTransformException is thrown if an attempt is made + * to specify an illegal Transform3D. + * @param t the new model transform + * @exception BadTransformException if the transform is not affine. + */ + public void setModelTransform(Transform3D t) { + + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetModelTransform(t); + } + else { + Transform3D uModelTransform = VirtualUniverse.mc.getTransform3D(t); + //Transform3D uModelTransform = t; + if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_MODEL_TRANSFORM, + uModelTransform, null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_MODEL_TRANSFORM, + uModelTransform, null); + } + } + } + + void doSetModelTransform(Transform3D t) { + this.modelTransform.set(t); + computeCompositeTransform(); + normalTransformNeedToUpdate = true; + } + + /** + * Multiplies the current model transform by the specified + * transform and stores the result back into the current + * transform. The specified transformation must be affine. + * @param t the model transform to be concatenated with the + * current model transform + * @exception BadTransformException if the transform is not affine. + */ + public void multiplyModelTransform(Transform3D t) { + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doMultiplyModelTransform(t); + } else { + Transform3D tt = VirtualUniverse.mc.getTransform3D(t); + if (Thread.currentThread() == canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.MULTIPLY_MODEL_TRANSFORM, + tt, null); + } else { + sendRenderMessage(true, GraphicsContext3D.MULTIPLY_MODEL_TRANSFORM, + tt, null); + } + } + } + + void doMultiplyModelTransform(Transform3D t) { + this.modelTransform.mul(t); + computeCompositeTransform(); + normalTransformNeedToUpdate = true; + } + + /** + * Retrieves the current model transform. + * @param t the model transform that will receive the current + * model transform + */ + public void getModelTransform(Transform3D t) { + t.set(modelTransform); + } + + /** + * Replaces the specified sound with the sound provided. + * The graphics context stores a reference to each sound + * object in the list of sounds. This means that the + * application may modify the sound attributes for + * any of the sounds by using the appropriate methods on + * that Sound node object. + * @param sound the new sound + * @param index which sound to replace + * @exception IllegalSharingException if the Sound node + * is part of or is subsequently made part of a live scene graph. + * @exception NullPointerException if the Sound object is null. + */ + public void setSound(Sound sound, int index) { + if (sound == null) { + throw new NullPointerException(J3dI18N.getString("GraphicsContext3D17")); + } + if (sound.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D23")); + } + uSounds.setElementAt(sound, index); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetSound(sound, index); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_SOUND, sound, + new Integer(index)); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_SOUND, sound, + new Integer(index)); + } + } + + void doSetSound(Sound sound, int index) { + Sound oldSound; + oldSound = (Sound)(this.sounds.elementAt(index)); + ((SoundRetained)sound.retained).setInImmCtx(true); + if (oldSound != null) { + ((SoundRetained)oldSound.retained).setInImmCtx(false); + } + ((SoundRetained)sound.retained).setInImmCtx(true); + updateSoundState((SoundRetained)(sound.retained)); + this.sounds.setElementAt(sound, index); + this.soundsChanged = true; + + sendSoundMessage(GraphicsContext3D.SET_SOUND, sound, oldSound); + } + + /** + * Inserts the specified sound at the specified index location. + * Inserting a sound to the list of sounds implicitly starts the + * sound playing. Once a sound is finished playing, it can be + * restarted by setting the sound's enable flag to true. + * The scheduling region of all sounds in the list is ignored + * for immediate-mode rendering. + * @param sound the new sound + * @param index at which location to insert + * @exception IllegalSharingException if the Sound node + * is part or is subsequently made part of a live scene graph. + * @exception NullPointerException if the Sound object is null. + */ + public void insertSound(Sound sound, int index) { + if (sound == null) { + throw new NullPointerException(J3dI18N.getString("GraphicsContext3D17")); } + + if (sound.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D23")); + } + uSounds.insertElementAt(sound, index); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doInsertSound(sound, index); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.INSERT_SOUND, sound, + new Integer(index)); + } else { + sendRenderMessage(true, GraphicsContext3D.INSERT_SOUND, sound, + new Integer(index)); + } + } + + void doInsertSound(Sound sound, int index) { + updateSoundState((SoundRetained)sound.retained); + this.sounds.insertElementAt(sound, index); + this.soundsChanged = true; + sendSoundMessage(GraphicsContext3D.INSERT_SOUND, sound, null); + } + + /** + * Removes the sound at the specified index location. + * @param index which sound to remove + */ + public void removeSound(int index) { + uSounds.removeElementAt(index); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doRemoveSound(index); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.REMOVE_SOUND, + new Integer(index), null); + } else { + sendRenderMessage(true, GraphicsContext3D.REMOVE_SOUND, + new Integer(index), null); + } + } + + void doRemoveSound(int index) { + Sound sound = (Sound)(this.sounds.elementAt(index)); + SoundScheduler soundScheduler = getSoundScheduler(); + ((SoundRetained)(sound.retained)).setInImmCtx(false); + this.sounds.removeElementAt(index); + this.soundsChanged = true; + // stop sound if playing on audioDevice + sendSoundMessage(GraphicsContext3D.REMOVE_SOUND, null, sound); + } + + /** + * Retrieves the index selected sound. + * @param index which sound to return + * @return the sound at location index + */ + public Sound getSound(int index) { + Sound sound = (Sound)(uSounds.elementAt(index)); + return sound; + } + + /** + * Retrieves the enumeration object of all the sounds. + * @return the enumeration object of all the sounds + */ + public Enumeration getAllSounds() { + return uSounds.elements(); + } + + /** + * Appends the specified sound to this graphics context's list of sounds. + * Adding a sound to the list of sounds implicitly starts the + * sound playing. Once a sound is finished playing, it can be + * restarted by setting the sound's enable flag to true. + * The scheduling region of all sounds in the list is ignored + * for immediate-mode rendering. + * @param sound the sound to add + * @exception IllegalSharingException if the Sound node + * is part of or is subsequently made part of a live scene graph. + * @exception NullPointerException if the Sound object is null. + */ + public void addSound(Sound sound) { + if (sound == null) { + throw new NullPointerException(J3dI18N.getString("GraphicsContext3D17")); } + + if (sound.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D23")); + + } + uSounds.addElement(sound); + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doAddSound(sound); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.ADD_SOUND, sound, null); + } else { + sendRenderMessage(true, GraphicsContext3D.ADD_SOUND, sound, null); + } + } + + void doAddSound(Sound sound) { + ((SoundRetained)(sound.retained)).setInImmCtx(true); + updateSoundState((SoundRetained)(sound.retained)); + this.sounds.addElement(sound); + this.soundsChanged = true; + sendSoundMessage(GraphicsContext3D.ADD_SOUND, sound, null); + } + + /** + * Retrieves the current number of sounds in this graphics context. + * @return the current number of sounds + */ + public int numSounds() { + return uSounds.size(); + } + + SoundScheduler getSoundScheduler() { + if (canvas3d != null && canvas3d.view != null) + return canvas3d.view.soundScheduler; // could be null as well + else + return (SoundScheduler)null; + } + + void updateSoundState(SoundRetained sound) { + View view = null; + if (canvas3d != null) + view = canvas3d.view; + // Make sure that: + // . Current view is not null + // . The sound scheduler running (reference to it is not null) + if (view != null) { + SoundScheduler soundScheduler = getSoundScheduler(); + if (soundScheduler == null) { + // TODO: Re-implement + // start up SoundScheduler since it hasn't already been started + } + } + + // Update sound fields related to transforms + if (sound instanceof ConeSoundRetained) { + ConeSoundRetained cs = (ConeSoundRetained) sound; + this.modelTransform.transform(cs.direction, cs.xformDirection); + cs.xformDirection.normalize(); + this.modelTransform.transform(cs.position, cs.xformPosition); + // TODO (Question) Is drawTranform equivalent to Vworld-to-Local? + cs.trans.setWithLock(drawTransform); + + } else if (sound instanceof PointSoundRetained) { + PointSoundRetained ps = (PointSoundRetained) sound; + this.modelTransform.transform(ps.position, ps.xformPosition); + // TODO (Question) Is drawTranform equivalent to Vworld-to-Local? + ps.trans.setWithLock(drawTransform); + } + } + + /** + * Retrieves the sound playing flag. + * @param index which sound + * @return flag denoting if sound is currently playing + */ + public boolean isSoundPlaying(int index) { + Sound sound; + // uSounds isPlaying field is NOT updated, sounds elements are used + sound = (Sound)(this.sounds.elementAt(index)); + return sound.isPlaying(); + } + + /** + * Sets the current AuralAttributes object to the specified + * AuralAttributes component object. + * This means that the application may modify individual + * audio attributes by using the appropriate methods in + * the Aural-Attributes object. + * @param attributes the new AuralAttributes object + */ + public void setAuralAttributes(AuralAttributes attributes) { + uAuralAttributes = attributes; + + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetAuralAttributes(attributes); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_AURAL_ATTRIBUTES, + attributes, null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_AURAL_ATTRIBUTES, + attributes, null); + } + } + + void doSetAuralAttributes(AuralAttributes attributes) { + this.auralAttributes = attributes; + sendSoundMessage(GraphicsContext3D.SET_AURAL_ATTRIBUTES, attributes, null); + } + /** + * Retrieves the current AuralAttributes component object. + * @return the current AuralAttributes object + */ + public AuralAttributes getAuralAttributes() { + return uAuralAttributes; + } + + + /** + * Sets a flag that specifies whether the double buffering and + * stereo mode from the Canvas3D are overridden. When set to + * true, this attribute enables the + * <code>frontBufferRendering</code> and <code>stereoMode</code> + * attributes. + * + * @param bufferOverride the new buffer override flag + * + * @see #setFrontBufferRendering + * @see #setStereoMode + * + * @since Java 3D 1.2 + */ + public void setBufferOverride(boolean bufferOverride) { + uBufferOverride = bufferOverride; + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetBufferOverride(bufferOverride); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_BUFFER_OVERRIDE, + new Boolean(bufferOverride), null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_BUFFER_OVERRIDE, + new Boolean(bufferOverride), null); + } + } + + void doSetBufferOverride(boolean bufferOverride) { + if (bufferOverride != this.bufferOverride) { + this.bufferOverride = bufferOverride; + dirtyMask |= BUFFER_MODE; + } + } + + + /** + * Returns the current buffer override flag. + * @return true if buffer override is enabled; otherwise, + * false is returned + * + * @since Java 3D 1.2 + */ + public boolean getBufferOverride() { + return uBufferOverride; + } + + + /** + * Sets a flag that enables or disables immediate mode rendering + * into the front buffer of a double buffered Canvas3D. + * This attribute is only used when the + * <code>bufferOverride</code> flag is enabled. + * <p> + * Note that this attribute has no effect if double buffering + * is disabled or is not available on the Canvas3D. + * + * @param frontBufferRendering the new front buffer rendering flag + * + * @see #setBufferOverride + * + * @since Java 3D 1.2 + */ + public void setFrontBufferRendering(boolean frontBufferRendering) { + uFrontBufferRendering = frontBufferRendering; + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetFrontBufferRendering(frontBufferRendering); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_FRONT_BUFFER_RENDERING, + new Boolean(frontBufferRendering), null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_FRONT_BUFFER_RENDERING, + new Boolean(frontBufferRendering), null); + } + } + + void doSetFrontBufferRendering(boolean frontBufferRendering) { + if (frontBufferRendering != this.frontBufferRendering) { + this.frontBufferRendering = frontBufferRendering; + dirtyMask |= BUFFER_MODE; + } + } + + + /** + * Returns the current front buffer rendering flag. + * @return true if front buffer rendering is enabled; otherwise, + * false is returned + * + * @since Java 3D 1.2 + */ + public boolean getFrontBufferRendering() { + return uFrontBufferRendering; + } + + + /** + * Sets the stereo mode for immediate mode rendering. The + * parameter specifies which stereo buffer or buffers is rendered + * into. This attribute is only used when the + * <code>bufferOverride</code> flag is enabled. + * <ul> + * <li> + * <code>STEREO_LEFT</code> specifies that rendering is done into + * the left eye. + * </li> + * <li> + * <code>STEREO_RIGHT</code> specifies that rendering is done into + * the right eye. + * </li> + * <li> + * <code>STEREO_BOTH</code> specifies that rendering is done into + * both eyes. This is the default. + * </li> + * </ul> + * + * <p> + * Note that this attribute has no effect if stereo is disabled or + * is not available on the Canvas3D. + * + * @param stereoMode the new stereo mode + * + * @see #setBufferOverride + * + * @since Java 3D 1.2 + */ + public void setStereoMode(int stereoMode) { + uStereoMode = stereoMode; + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doSetStereoMode(stereoMode); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.SET_STEREO_MODE, + stereoModes[stereoMode], null); + } else { + sendRenderMessage(true, GraphicsContext3D.SET_STEREO_MODE, + stereoModes[stereoMode], null); + } + } + + void doSetStereoMode(int stereoMode) { + if (stereoMode != this.stereoMode) { + this.stereoMode = stereoMode; + dirtyMask |= BUFFER_MODE; + } + } + + + /** + * Returns the current stereo mode. + * @return the stereo mode, one of <code>STEREO_LEFT</code>, + * <code>STEREO_RIGHT</code>, or <code>STEREO_BOTH</code>. + * + * @since Java 3D 1.2 + */ + public int getStereoMode() { + return uStereoMode; + } + + +// +// Methods to draw graphics objects +// + + /** + * Clear the Canvas3D to the color or image specified by the + * current background node. + */ + public void clear() { + if ((canvas3d.view == null) || (canvas3d.view.universe == null) || + (!canvas3d.view.active)) { + return; + } else if (Thread.currentThread() == canvas3d.screen.renderer) { + doClear(); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.CLEAR, null, null); + } else { + sendRenderMessage(true, GraphicsContext3D.CLEAR, null, null); + } + } + + void doClear() { + + if (!canvas3d.firstPaintCalled) + return; + + RenderBin rb = canvas3d.view.renderBin; + BackgroundRetained back = null; + + + if (this.background != null) + back = (BackgroundRetained)this.background.retained; + else + back = this.black; + + // TODO: This should ideally be done by the renderer (or by the + // canvas itself) when the canvas is first added to a view. + /* + if ((canvas3d.screen.renderer != null) && + (canvas3d.screen.renderer.renderBin == null)) + canvas3d.screen.renderer.renderBin = rb; + */ + // If we are in pure immediate mode, update the view cache + if (!canvas3d.isRunning) + updateViewCache(rb); + + // We need to catch NullPointerException when the dsi + // gets yanked from us during a remove. + + try { + if (canvas3d.drawingSurfaceObject.renderLock()) { + // TODO : Fix texture + /* + if (canvas3d.useSharedCtx) { + if (canvas3d.screen.renderer.sharedCtx == 0) { + synchronized (VirtualUniverse.mc.contextCreationLock) { + canvas3d.screen.renderer.sharedCtx = canvas3d.createContext( + canvas3d.screen.display, + canvas3d.window, canvas3d.vid, 0, true, + canvas3d.offScreen); + canvas3d.screen.renderer.sharedCtxTimeStamp = + VirtualUniverse.mc.getContextTimeStamp(); + canvas3d.screen.renderer.needToRebuildDisplayList = true; + } + } + } + */ + + if (canvas3d.ctx == 0) { + synchronized (VirtualUniverse.mc.contextCreationLock) { + canvas3d.ctx = + canvas3d.createContext(canvas3d.screen.display, + canvas3d.window, + canvas3d.vid, + canvas3d.visInfo, + 0, false, + canvas3d.offScreen); + if (canvas3d.ctx == 0) { + canvas3d.drawingSurfaceObject.unLock(); + return; + } + + canvas3d.ctxTimeStamp = + VirtualUniverse.mc.getContextTimeStamp(); + canvas3d.screen.renderer.listOfCtxs.add( + new Long(canvas3d.ctx)); + canvas3d.screen.renderer.listOfCanvases.add(canvas3d); + + canvas3d.beginScene(); + + if (canvas3d.graphics2D != null) { + canvas3d.graphics2D.init(); + } + + // query for the number of texture units + // supported + if (canvas3d.multiTexAccelerated) { + canvas3d.numTexUnitSupported = + canvas3d.getTextureUnitCount(canvas3d.ctx); + } + + // enable separate specular color + canvas3d.enableSeparateSpecularColor(); + } + + // create the cache texture state in canvas + // for state download checking purpose + + if (canvas3d.texUnitState == null) { + canvas3d.texUnitState = + new TextureUnitStateRetained[ + canvas3d.numTexUnitSupported]; + for (int t = 0; t < canvas3d.numTexUnitSupported; t++) { + canvas3d.texUnitState[t] = + new TextureUnitStateRetained(); + canvas3d.texUnitState[t].texture = null; + canvas3d.texUnitState[t].mirror = null; + } + } + + + // also create the texture unit state map + // which is a mapping from texture unit state to + // the actual underlying texture unit + + if (canvas3d.texUnitStateMap == null) { + canvas3d.texUnitStateMap = + new int[canvas3d.numTexUnitSupported]; + } + + + canvas3d.drawingSurfaceObject.contextValidated(); + canvas3d.screen.renderer.currentCtx = canvas3d.ctx; + initializeState(); + canvas3d.ctxChanged = true; + canvas3d.canvasDirty = 0xffff; + // Update Appearance + updateState(rb, RenderMolecule.SURFACE); + + canvas3d.currentLights = new + LightRetained[canvas3d.getNumCtxLights(canvas3d.ctx)]; + + for (int j=0; j<canvas3d.currentLights.length; j++) { + canvas3d.currentLights[j] = null; + } + } + + + canvas3d.makeCtxCurrent(); + + if ((dirtyMask & BUFFER_MODE) != 0) { + if (bufferOverride) { + canvas3d.setRenderMode(canvas3d.ctx, stereoMode, + canvas3d.useDoubleBuffer && !frontBufferRendering); + } else { + if (!canvas3d.isRunning) { + canvas3d.setRenderMode(canvas3d.ctx, + Canvas3D.FIELD_ALL, + canvas3d.useDoubleBuffer); + } + } + dirtyMask &= ~BUFFER_MODE; + } + + Dimension size = canvas3d.getSize(); + int winWidth = size.width; + int winHeight = size.height; + + if (back.image != null && back.image.isByReference()) { + back.image.geomLock.getLock(); + back.image.evaluateExtensions(canvas3d.extensionsSupported); + if (!VirtualUniverse.mc.isBackgroundTexture) { + canvas3d.clear(canvas3d.ctx, + back.color.x, back.color.y, + back.color.z, winWidth, winHeight, back.image, + back.imageScaleMode, + back.image != null?back.image.imageYdown[0]:null); + } + else { + + // this is if the background image resizes with the canvas +// Dimension size = null; +// canvas3d.getSize(size); +// int xmax = size.width; +// int ymax = size.height; + if (objectId == -1) { + objectId = VirtualUniverse.mc.getTexture2DId(); + } + + canvas3d.textureclear(canvas3d.ctx, + back.xmax, back.ymax, + back.color.x, back.color.y, + back.color.z, winWidth, winHeight, + objectId, back.imageScaleMode, back.texImage, true); + } + back.image.geomLock.unLock(); + } + else { + if (!VirtualUniverse.mc.isBackgroundTexture) { + canvas3d.clear(canvas3d.ctx, + back.color.x, back.color.y, + back.color.z, winWidth, winHeight, back.image, + back.imageScaleMode, + back.image != null?back.image.imageYdown[0]:null); + } + else { + + // this is if the background image resizes with the canvas +// Dimension size = null; +// canvas3d.getSize(size); +// int xmax = size.width; +// int ymax = size.height; + if (objectId == -1) { + objectId = VirtualUniverse.mc.getTexture2DId(); + } + + canvas3d.textureclear(canvas3d.ctx, + back.xmax, back.ymax, + back.color.x, back.color.y, + back.color.z, + winWidth, winHeight, + objectId, back.imageScaleMode, back.texImage, true); + } + } + + // Set the viewport and view matrices + if (!canvas3d.isRunning) { + CanvasViewCache cvCache = canvas3d.canvasViewCache; + canvas3d.setViewport(canvas3d.ctx, + 0, 0, + cvCache.getCanvasWidth(), + cvCache.getCanvasHeight()); + if (bufferOverride && (stereoMode == STEREO_RIGHT)) { + canvas3d.setProjectionMatrix(canvas3d.ctx, + cvCache.getRightProjection().mat); + canvas3d.setModelViewMatrix(canvas3d.ctx, + cvCache.getRightVpcToEc().mat, + rb.vworldToVpc); + } + else { + canvas3d.setProjectionMatrix(canvas3d.ctx, + cvCache.getLeftProjection().mat); + canvas3d.setModelViewMatrix(canvas3d.ctx, + cvCache.getLeftVpcToEc().mat, + rb.vworldToVpc); + } + } + + canvas3d.drawingSurfaceObject.unLock(); + } + } catch (NullPointerException ne) { + canvas3d.drawingSurfaceObject.unLock(); + throw ne; + } + } + + // Method to update compTransform. + private void computeCompositeTransform() { + ViewPlatform vp; + + if ((canvas3d == null) || + (canvas3d.view == null) || + (((vp = canvas3d.view.getViewPlatform()) == null)) || + (((ViewPlatformRetained)(vp.retained)) == null)) { + compTransform.set(modelTransform); + return; + } + + ViewPlatformRetained vpR = (ViewPlatformRetained)vp.retained; + if ((vpR == null) || (vpR.locale == null)) { + compTransform.set(modelTransform); + return; + } + + HiResCoord localeHiRes = vpR.locale.hiRes; + + if (localeHiRes.equals(hiRes)) { + compTransform.set(modelTransform); + } else { + Transform3D trans = new Transform3D(); + Vector3d localeTrans = new Vector3d(); + localeHiRes.difference( hiRes, localeTrans ); + trans.setTranslation( localeTrans ); + compTransform.mul(trans, modelTransform); + } + } + + // Method to update the view cache in pure immediate mode + private void updateViewCache(RenderBin rb) { + + ViewPlatform vp = canvas3d.view.getViewPlatform(); + + if (vp == null) + return; + + ViewPlatformRetained vpR = (ViewPlatformRetained)vp.retained; + + if (!canvas3d.isRunning) { + // in pure immediate mode, notify platform transform change + vpR.evaluateInitViewPlatformTransform(); + } + + + // rb.setVworldToVpc(vp.getVworldToVpc()); + // rb.setVpcToVworld(vp.getVpcToVworld()); + + // TODO: Fix this + rb.vpcToVworld = vpR.getVpcToVworld(); + rb.vworldToVpc = vpR.getVworldToVpc(); + + canvas3d.updateViewCache(true, null, null, false); + } + + void doDraw(Geometry geometry) { + + boolean useAlpha; + GeometryRetained drawGeo; + GeometryArrayRetained geoRetained = null; + + + if (!canvas3d.firstPaintCalled || !visible) { + return; + } + + RenderBin rb = canvas3d.view.renderBin; + int i, nlights, activeLights; + LightRetained light; + boolean lightingOn = true; + + if (canvas3d.ctx == 0) { + // Force an initial clear if one has not yet been done + doClear(); + } + + + if (J3dDebug.devPhase && J3dDebug.debug) { + J3dDebug.doAssert(canvas3d.ctx != 0, "canvas3d.ctx != 0"); + } + + // We need to catch NullPointerException when the dsi + // gets yanked from us during a remove. + try { + if (canvas3d.drawingSurfaceObject.renderLock()) { + + // Make the context current + canvas3d.makeCtxCurrent(); + + if ((dirtyMask & BUFFER_MODE) != 0) { + if (bufferOverride) { + canvas3d.setRenderMode(canvas3d.ctx, stereoMode, + canvas3d.useDoubleBuffer && !frontBufferRendering); + } else { + canvas3d.setRenderMode(canvas3d.ctx, Canvas3D.FIELD_ALL, + canvas3d.useDoubleBuffer); + } + dirtyMask &= ~BUFFER_MODE; + } + + CanvasViewCache cvCache = canvas3d.canvasViewCache; + Transform3D proj; + +// vpcToEc = cvCache.getLeftVpcToEc(); + if (bufferOverride) { + switch(stereoMode) { + case STEREO_RIGHT: + vpcToEc = cvCache.getRightVpcToEc(); + // TODO: move this under check for + // (dirtyMask & BUFFER_MODE) above after testing + // PureImmediate mode + canvas3d.setProjectionMatrix(canvas3d.ctx, + cvCache.getRightProjection(). + mat); + break; + case STEREO_LEFT: + case STEREO_BOTH: + default: + vpcToEc = cvCache.getLeftVpcToEc(); + // TODO: move this under check for + // (dirtyMask & BUFFER_MODE) above after testing + // PureImmediate mode + canvas3d.setProjectionMatrix(canvas3d.ctx, + cvCache.getLeftProjection(). + mat); + } + } + else if (!canvas3d.isRunning || + // vpcToEc is not set in the first frame + // of preRender() callback + (canvas3d.vpcToEc == null)) { + vpcToEc = cvCache.getLeftVpcToEc(); + } + else { + vpcToEc = canvas3d.vpcToEc; + } + + // referred by RenderQueue.updateState + // canvas3d.screen.renderer.vpcToEc = vpcToEc; + // rb.updateState(canvas3d.screen.renderer.rId, ro, canvas3d, true); + + + // this.drawTransform.mul(rb.vworldToVpc, + // this.compTransform); + + boolean isNonUniformScale = !drawTransform.isCongruent(); + + int geometryType = 0; + switch (((GeometryRetained)geometry.retained).geoType) { + case GeometryRetained.GEO_TYPE_POINT_SET: + case GeometryRetained.GEO_TYPE_INDEXED_POINT_SET: + geometryType = RenderMolecule.POINT; + break; + case GeometryRetained.GEO_TYPE_LINE_SET: + case GeometryRetained.GEO_TYPE_LINE_STRIP_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_STRIP_SET: + geometryType = RenderMolecule.LINE; + break; + case GeometryRetained.GEO_TYPE_RASTER: + geometryType = RenderMolecule.RASTER; + break; + case GeometryRetained.GEO_TYPE_COMPRESSED: + geometryType = RenderMolecule.COMPRESSED; + + switch (((CompressedGeometryRetained)geometry.retained).getBufferType()) { + case CompressedGeometryHeader.POINT_BUFFER: + geometryType |= RenderMolecule.POINT ; + break ; + case CompressedGeometryHeader.LINE_BUFFER: + geometryType |= RenderMolecule.LINE ; + break ; + default: + case CompressedGeometryHeader.TRIANGLE_BUFFER: + geometryType |= RenderMolecule.SURFACE ; + break ; + } + break; + default: + geometryType = RenderMolecule.SURFACE; + break; + } + + useAlpha = updateState(rb, geometryType); + + canvas3d.setModelViewMatrix(canvas3d.ctx, + vpcToEc.mat, + rb.vworldToVpc); + updateLightAndFog(); + + updateModelClip(rb.vworldToVpc); + + this.drawTransform.mul(rb.vworldToVpc, this.compTransform); + canvas3d.setModelViewMatrix(canvas3d.ctx, + vpcToEc.mat, this.drawTransform); + + if (geometry.retained instanceof GeometryArrayRetained) { + geoRetained = (GeometryArrayRetained)geometry.retained; + + geoRetained.geomLock.getLock(); + // If the geometry is by refernence, then see if we are using alpha + // and that there is no global alpha sun extension defined .. + if ((( geoRetained.vertexFormat & GeometryArray.BY_REFERENCE)!=0) && + (geoRetained.c4fAllocated == 0) && + ((geoRetained.vertexFormat & GeometryArray.COLOR) != 0) && + useAlpha && (canvas3d.extensionsSupported &Canvas3D.SUN_GLOBAL_ALPHA) == 0 ) { + + if ((geoRetained.vertexFormat & GeometryArray.INTERLEAVED) != 0) { + geoRetained.setupMirrorInterleavedColorPointer(true); + } + else { + geoRetained.setupMirrorColorPointer((geoRetained.vertexType & GeometryArrayRetained.COLOR_DEFINED),true); + } + } + + if ((geometry.retained instanceof IndexedGeometryArrayRetained) && + ((((GeometryArrayRetained)geometry.retained).vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0)) { + if (geoRetained.dirtyFlag != 0) { + geoRetained.mirrorGeometry = (GeometryRetained) + ((IndexedGeometryArrayRetained)geoRetained).cloneNonIndexedGeometry(); + // Change the source geometry dirtyFlag + // drawGeo.execute() will change the + // destination geometry dirtyFlag only. + geoRetained.dirtyFlag = 0; + } + drawGeo = (GeometryRetained)geoRetained.mirrorGeometry; + } else { + drawGeo = geoRetained; + } + + geoRetained.setVertexFormat(false, ignoreVertexColors, canvas3d.ctx ); + + } else if (geometry.retained instanceof Text3DRetained) { + ((Text3DRetained)geometry.retained).setModelViewMatrix( + vpcToEc, this.drawTransform); + drawGeo = (GeometryRetained)geometry.retained; + } else if (geometry.retained instanceof RasterRetained) { + ImageComponent2DRetained img = ((RasterRetained)geometry.retained).image; + if (img != null && img.isByReference()) { + img.geomLock.getLock(); + img.evaluateExtensions(canvas3d.extensionsSupported); + img.geomLock.unLock(); + } + drawGeo = (GeometryRetained)geometry.retained; + } else { + drawGeo = (GeometryRetained)geometry.retained; + } + + if (!toSimulateMultiTex) { + drawGeo.execute(canvas3d, null, isNonUniformScale, + false, alpha, + ((canvas3d.view.getScreens()).length > 1), + canvas3d.screen.screen, + ignoreVertexColors, + -1); + } else { + // TODO: need to leverage the code in textureBin + boolean startToSimulate = false; + if (numActiveTexUnit < 1) { + // no active texture unit + drawGeo.execute(canvas3d, null, isNonUniformScale, + false, alpha, + ((canvas3d.view.getScreens()).length > 1), + canvas3d.screen.screen, + ignoreVertexColors, + 0); + } else if (numActiveTexUnit == 1) { + // one active texture unit + drawGeo.execute(canvas3d, null, isNonUniformScale, + false, alpha, + ((canvas3d.view.getScreens()).length > 1), + canvas3d.screen.screen, + ignoreVertexColors, + lastActiveTexUnitIndex); + } else { + // simulate multiple texture units + AppearanceRetained app = + (AppearanceRetained)appearance.retained; + + // first turn off fog + if (fog != null) + canvas3d.setFogEnableFlag(canvas3d.ctx, false); + + for (i = 0; i < app.texUnitState.length; i++) { + if (app.texUnitState[i] != null && + app.texUnitState[i].isTextureEnabled()) { + + // turn on fog for the last pass + if (i == lastActiveTexUnitIndex) + canvas3d.setFogEnableFlag(canvas3d.ctx, true); + + app.texUnitState[i].updateNative(-1, canvas3d, + false, startToSimulate); + + startToSimulate = true; + drawGeo.execute(canvas3d, null, + isNonUniformScale, false, alpha, + ((canvas3d.view.getScreens()).length > 1), + canvas3d.screen.screen, + ignoreVertexColors, + i); + } + } + + // adjust the depth test back to what it was + // and adjust the blend func to what it it was + if (startToSimulate) { + app.transparencyAttributes.updateNative( + canvas3d.ctx, alpha, geometryType, + polygonMode, lineAA, pointAA); + } + + if (fog != null) { + canvas3d.setFogEnableFlag(canvas3d.ctx, true); + } + } + } + if (geoRetained != null) + geoRetained.geomLock.unLock(); + + canvas3d.drawingSurfaceObject.unLock(); + } + } catch (NullPointerException ne) { + canvas3d.drawingSurfaceObject.unLock(); + throw ne; + } + } + + /** + * Draw the specified Geometry component object. + * @param geometry the Geometry object to draw. + */ + public void draw(Geometry geometry) { + if ((canvas3d.view == null) || (canvas3d.view.universe == null) || + (!canvas3d.view.active)) { + return; + } else if (Thread.currentThread() == canvas3d.screen.renderer) { + doDraw(geometry); + } else { + if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.DRAW, + geometry, null); + } else { + sendRenderMessage(true, GraphicsContext3D.DRAW, geometry, + null); + } + } + } + + /** + * Draw the specified Shape3D leaf node object. This is + * a convenience method that is identical to calling the + * setAppearance(Appearance) and draw(Geometry) methods + * passing the appearance and geometry component objects of + * the specified shape node as arguments. + * @param shape the Shape3D node containing the Appearance component + * object to set and Geometry component object to draw + * @exception IllegalSharingException if the Shape3D node + * is part of or is subsequently made part of a live scene graph. + */ + public void draw(Shape3D shape) { + if (shape.isLive()) { + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D26")); + } + ((Shape3DRetained)shape.retained).setInImmCtx(true); + setAppearance(shape.getAppearance()); + draw(shape.getGeometry()); + } + + /** + * Native method for readRaster + */ + native void readRasterNative(long d3dctx, + int type, int xSrcOffset, int ySrcOffset, + int width, int height, int hCanvas, int format, + ImageComponentRetained image, + DepthComponentRetained depth, + GraphicsContext3D ctx); + + /** + * Read an image from the frame buffer and copy it into the + * ImageComponent and/or DepthComponent + * objects referenced by the specified Raster object. + * All parameters of the Raster object and the component ImageComponent + * and/or DepthComponentImage objects must be set to the desired values + * prior to calling this method. These values determine the location, + * size, and format of the pixel data that is read. + * This method calls <code>flush(true)</code> prior to reading the + * frame buffer. + * + * @param raster the Raster object used to read the + * contents of the frame buffer + * + * @exception IllegalArgumentException if the Raster's + * ImageComponent2D is in by-reference mode and its RenderedImage + * is not an instance of a BufferedImage. + * + * @exception IllegalSharingException if the Raster object + * is part of a live scene graph. + * + * @see #flush + * @see ImageComponent + * @see DepthComponent + */ + public void readRaster(Raster raster) { + if ((canvas3d.view == null) || (canvas3d.view.universe == null) || + (!canvas3d.view.active)) { + return; + } else if (Thread.currentThread() == canvas3d.screen.renderer) { + doReadRaster(raster); + } else if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + readRasterReady = false; + sendRenderMessage(false, GraphicsContext3D.READ_RASTER, raster, null); + while (!readRasterReady) { + MasterControl.threadYield(); + } + } else { + // call from user thread + readRasterReady = false; + sendRenderMessage(true, GraphicsContext3D.READ_RASTER, raster, null); + while (!readRasterReady) { + MasterControl.threadYield(); + } + } + } + + + + void doReadRaster(Raster raster) { + + + if (!canvas3d.firstPaintCalled) { + readRasterReady = true; + return; + } + + RasterRetained ras = (RasterRetained)raster.retained; + Dimension canvasSize = canvas3d.getSize(); + int format = 0; // Not use in case of DepthComponent read + + if (raster.isLive()) { + readRasterReady = true; + throw new IllegalSharingException(J3dI18N.getString("GraphicsContext3D21")); + } + + // TODO: implement illegal argument exception + /* + if (ras.image.byReference && + !(ras.image.imageReference instanceof BufferedImage)) { + + throw new IllegalArgumentException(...); + } + */ + + if (canvas3d.ctx == 0) { + // Force an initial clear if one has not yet been done + doClear(); + } + + if (J3dDebug.devPhase && J3dDebug.debug) { + J3dDebug.doAssert(canvas3d.ctx != 0, "canvas3d.ctx != 0"); + } + + + // allocate read buffer space + if ( (ras.type & Raster.RASTER_COLOR) != 0) { + int bpp = ras.image.getEffectiveBytesPerPixel(); + int size = ras.image.height * ras.image.width + * bpp; + format = ras.image.getEffectiveFormat(); + if ((ras.width > ras.image.width) || + (ras.height > ras.image.height)) { + throw new RuntimeException(J3dI18N.getString("GraphicsContext3D27")); + } + if (byteBuffer.length < size) + byteBuffer = new byte[size]; + } + + if ( (ras.type & Raster.RASTER_DEPTH) != 0) { + int size = ras.depthComponent.height * ras.depthComponent.width; + if (ras.depthComponent.type + == DepthComponentRetained.DEPTH_COMPONENT_TYPE_FLOAT) { + if (floatBuffer.length < size) + floatBuffer = new float[size]; + } else { // type INT or NATIVE + if (intBuffer.length < size) + intBuffer = new int[size]; + } + if ((ras.width > ras.depthComponent.width) || + (ras.height > ras.depthComponent.height)) { + throw new RuntimeException(J3dI18N.getString("GraphicsContext3D28")); + } + } + + if ( (ras.type & Raster.RASTER_COLOR) != 0) { + // If by reference, check if a copy needs to be made + // and also evaluate the storedFormat .. + if (ras.image.isByReference()) { + ras.image.geomLock.getLock(); + ras.image.evaluateExtensions(canvas3d.extensionsSupported); + ras.image.geomLock.unLock(); + } + } + + // We need to catch NullPointerException when the dsi + // gets yanked from us during a remove. + try { + if (canvas3d.drawingSurfaceObject.renderLock()) { + // Make the context current and read the raster information + canvas3d.makeCtxCurrent(); + canvas3d.syncRender(canvas3d.ctx, true); + readRasterNative(canvas3d.ctx, + ras.type, ras.xSrcOffset, ras.ySrcOffset, + ras.width, ras.height, canvasSize.height, format, + ras.image, ras.depthComponent, this); + canvas3d.drawingSurfaceObject.unLock(); + } + } catch (NullPointerException ne) { + canvas3d.drawingSurfaceObject.unLock(); + throw ne; + } + + // flip color image: yUp -> yDown and convert to BufferedImage + if ( (ras.type & Raster.RASTER_COLOR) != 0) { + ras.image.retrieveImage(byteBuffer, ras.width, ras.height); + } + + if ( (ras.type & Raster.RASTER_DEPTH) != 0) { + if (ras.depthComponent.type == + DepthComponentRetained.DEPTH_COMPONENT_TYPE_FLOAT) + ((DepthComponentFloatRetained)ras.depthComponent).retrieveDepth( + floatBuffer, ras.width, ras.height); + else if (ras.depthComponent.type == + DepthComponentRetained.DEPTH_COMPONENT_TYPE_INT) + ((DepthComponentIntRetained)ras.depthComponent).retrieveDepth( + intBuffer, ras.width, ras.height); + else if (ras.depthComponent.type == + DepthComponentRetained.DEPTH_COMPONENT_TYPE_NATIVE) + ((DepthComponentNativeRetained)ras.depthComponent).retrieveDepth( + intBuffer, ras.width, ras.height); + } + readRasterReady = true; + } + + /** + * Flushes all previously executed rendering operations to the + * drawing buffer for this 3D graphics context. + * + * @param wait flag indicating whether or not to wait for the + * rendering to be complete before returning from this call. + * + * @since Java 3D 1.2 + */ + public void flush(boolean wait) { + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) || + (!canvas3d.view.active) || + (Thread.currentThread() == canvas3d.screen.renderer)) { + doFlush(wait); + } else { + Boolean waitArg = (wait ? Boolean.TRUE : Boolean.FALSE); + + if (Thread.currentThread() == + canvas3d.view.universe.behaviorScheduler) { + sendRenderMessage(false, GraphicsContext3D.FLUSH, waitArg, + null); + } else { + sendRenderMessage(true, GraphicsContext3D.FLUSH, waitArg, + null); + } + if (wait && canvas3d.active && canvas3d.isRunningStatus && + !canvas3d.offScreen) { + // No need to wait if renderer thread is not schedule + runMonitor(J3dThread.WAIT); + } + } + } + + void doFlush(boolean wait) { + try { + if (canvas3d.drawingSurfaceObject.renderLock()) { + canvas3d.syncRender(canvas3d.ctx, wait); + canvas3d.drawingSurfaceObject.unLock(); + if (wait) { + runMonitor(J3dThread.NOTIFY); + } + } + } catch (NullPointerException ne) { + canvas3d.drawingSurfaceObject.unLock(); + throw ne; + } + } + + void updateLightAndFog() { + int enableMask = 0; + int i; + sceneAmbient.x = 0.0f; + sceneAmbient.y = 0.0f; + sceneAmbient.z = 0.0f; + + int n = 0; + int nLight = lights.size();; + for (i = 0; i < nLight;i++) { + LightRetained lt = (LightRetained)((Light)lights.get(i)).retained; + if (lt instanceof AmbientLightRetained) { + sceneAmbient.x += lt.color.x; + sceneAmbient.y += lt.color.y; + sceneAmbient.z += lt.color.z; + continue; + } + + lt.update(canvas3d.ctx, n, + canvas3d.canvasViewCache.getVworldToCoexistenceScale()); + if (lt.lightOn) + enableMask |= (1 << n); + n++; + } + if (sceneAmbient.x > 1.0f) { + sceneAmbient.x = 1.0f; + } + if (sceneAmbient.y > 1.0f) { + sceneAmbient.y = 1.0f; + } + if (sceneAmbient.z > 1.0f) { + sceneAmbient.z = 1.0f; + } + + canvas3d.canvasDirty |= Canvas3D.AMBIENTLIGHT_DIRTY; + canvas3d.setSceneAmbient(canvas3d.ctx, sceneAmbient.x, + sceneAmbient.y, sceneAmbient.z); + + if (canvas3d.enableMask != enableMask) { + canvas3d.canvasDirty |= Canvas3D.LIGHTENABLES_DIRTY; + // TODO: 32 => renderBin.maxLights + canvas3d.setLightEnables(canvas3d.ctx, enableMask, 32); + canvas3d.enableMask = enableMask; + } + + canvas3d.lightBin = null; + + + // Mark the envset as dirty for the canvas for scene graph rendering + canvas3d.environmentSet = null; + + if (fog != null) { + if (fog.retained != canvas3d.fog) { + ((FogRetained)fog.retained).update(canvas3d.ctx, + canvas3d.canvasViewCache.getVworldToCoexistenceScale()); + canvas3d.fog = (FogRetained) fog.retained; + canvas3d.canvasDirty |= Canvas3D.FOG_DIRTY; + } + } else { // Turn off fog + if (canvas3d.fog != null) { + canvas3d.setFogEnableFlag(canvas3d.ctx, false); + canvas3d.fog = null; + canvas3d.canvasDirty |= Canvas3D.FOG_DIRTY; + } + } + } + + void updateModelClip(Transform3D vworldToVpc) { + if (modelClip != null) { + int enableMask = 0; + for (int i = 0; i < 6; i++) { + if (((ModelClipRetained)modelClip.retained).enables[i]) + enableMask |= 1 << i; + } + // planes are already transformed to eye coordinates + // in immediate mode + if (enableMask != 0) { + this.drawTransform.mul(vworldToVpc, this.modelClipTransform); + canvas3d.setModelViewMatrix(canvas3d.ctx, vpcToEc.mat, + this.drawTransform); + } + ((ModelClipRetained)modelClip.retained).update( + canvas3d.ctx, enableMask, + this.drawTransform); + canvas3d.canvasDirty |= Canvas3D.MODELCLIP_DIRTY; + canvas3d.modelClip = (ModelClipRetained) modelClip.retained; + } else { + if (canvas3d.modelClip != null) { + canvas3d.disableModelClip(canvas3d.ctx); + canvas3d.modelClip = null; + canvas3d.canvasDirty |= Canvas3D.MODELCLIP_DIRTY; + } + } + } + + + + boolean updateState(RenderBin rb, int geometryType) { + + boolean useAlpha = false;; + toSimulateMultiTex = true; + numActiveTexUnit = 0; + lastActiveTexUnitIndex = 0; + + // Update Appearance + if (appearance != null) { + AppearanceRetained app = (AppearanceRetained) appearance.retained; + + // If the material is not null then check if the one in the canvas + // is equivalent to the one being sent down. If Yes, do nothing + // Otherwise, cache the sent down material and mark the canvas + // dirty flag so that the compiled/compiled-retained rendering + // catches the change + // if material != null, we will need to load the material + // parameter again, because the apps could have changed + // the material parameter + + if (app.material != null) { + app.material.updateNative(canvas3d.ctx, + red,green,blue, + alpha,enableLighting); + canvas3d.material = app.material; + canvas3d.canvasDirty |= Canvas3D.MATERIAL_DIRTY; + } else { + if (canvas3d.material != null) { + canvas3d.updateMaterial(canvas3d.ctx, + red, green, blue, alpha); + canvas3d.material = null; + canvas3d.canvasDirty |= Canvas3D.MATERIAL_DIRTY; + } + } + + int prevNumActiveTexUnit = canvas3d.getNumActiveTexUnit(); + + if (app.texUnitState != null) { + boolean d3dBlendMode = false; + + TextureUnitStateRetained tus; + + for (int i = 0; i < app.texUnitState.length; i++) { + tus = app.texUnitState[i]; + if (tus != null && tus.isTextureEnabled()) { + numActiveTexUnit++; + lastActiveTexUnitIndex = i; + useAlpha = useAlpha || + (tus.texAttrs.textureMode == + TextureAttributes.BLEND); + if (tus.needBlend2Pass(canvas3d)) { + // use multi-pass if one of the stage use blend mode + d3dBlendMode = true; + } + } + } + + if (canvas3d.numTexUnitSupported >= numActiveTexUnit && + canvas3d.multiTexAccelerated && !d3dBlendMode) { + + int j = 0; + + // update all active texture unit states + + for (int i = 0; i < app.texUnitState.length; i++) { + if ((app.texUnitState[i] != null) && + app.texUnitState[i].isTextureEnabled()) { + app.texUnitState[i].updateNative(j, canvas3d, + false, false); + canvas3d.setTexUnitStateMap(i, j++); + } + } + + // reset the remaining texture units + + for (int i = j; i < prevNumActiveTexUnit; i++) { + if (canvas3d.texUnitState[i].texture != null) { + canvas3d.resetTexture(canvas3d.ctx, i); + canvas3d.texUnitState[i].texture = null; + } + } + + // set the number active texture unit in Canvas3D + canvas3d.setNumActiveTexUnit(numActiveTexUnit); + + // set the active texture unit back to 0 + canvas3d.activeTextureUnit(canvas3d.ctx, 0); + + toSimulateMultiTex = false; + + } else { + + // will fall back to the multi-pass case; + // reset all the texture units first + + for (int i = 0; i < prevNumActiveTexUnit; i++) { + if (canvas3d.texUnitState[i].texture != null) { + canvas3d.resetTexture(canvas3d.ctx, i); + canvas3d.texUnitState[i].texture = null; + } + } + } + } else { + // if texUnitState is null, let's disable + // all texture units first + if (canvas3d.multiTexAccelerated) { + if (canvas3d.texUnitState != null) { + for (int i = 0; i < prevNumActiveTexUnit; i++) { + TextureUnitStateRetained tur = canvas3d.texUnitState[i]; + if ((tur != null) && (tur.texture != null)) { + canvas3d.resetTexture(canvas3d.ctx, i); + canvas3d.texUnitState[i].texture = null; + } + } + } + + // set the active texture unit back to 0 + canvas3d.activeTextureUnit(canvas3d.ctx, 0); + } + + if ((canvas3d.texUnitState != null) && + (canvas3d.texUnitState[0] != null) && + (canvas3d.texUnitState[0].texture != app.texture)) { + + // If the image is by reference, check if the image + // should be processed + if (app.texture != null) { + for (int f = 0; f < app.texture.numFaces; f++) { + for (int k = 0; k < app.texture.maxLevels; k++) { + if (app.texture.images[f][k].isByReference()) { + app.texture.images[f][k].geomLock.getLock(); + app.texture.images[f][k].evaluateExtensions(canvas3d.extensionsSupported); + app.texture.images[f][k].geomLock.unLock(); + } + } + } + app.texture.updateNative(canvas3d); + canvas3d.setTexUnitStateMap(0, 0); + canvas3d.canvasDirty |= Canvas3D.TEXTUREBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + numActiveTexUnit = 1; + lastActiveTexUnitIndex = 0; + } + else { + numActiveTexUnit = 0; + canvas3d.resetTexture(canvas3d.ctx, -1); + canvas3d.canvasDirty |= Canvas3D.TEXTUREBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + } + + canvas3d.texUnitState[0].texture = app.texture; + } + + // set the number active texture unit in Canvas3D + canvas3d.setNumActiveTexUnit(numActiveTexUnit); + + if (app.texCoordGeneration != null) { + app.texCoordGeneration.updateNative(canvas3d); + canvas3d.canvasDirty |= Canvas3D.TEXTUREBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + if ((canvas3d.texUnitState != null) && + (canvas3d.texUnitState[0] != null)) { + canvas3d.texUnitState[0].texGen = app.texCoordGeneration; + } + } + else { + // If the canvas does not alreadt have a null texCoordGeneration + // load the default + if ((canvas3d.texUnitState != null) && + (canvas3d.texUnitState[0] != null) && + (canvas3d.texUnitState[0].texGen != null)) { + canvas3d.resetTexCoordGeneration(canvas3d.ctx); + canvas3d.canvasDirty |= Canvas3D.TEXTUREBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + canvas3d.texUnitState[0].texGen = app.texCoordGeneration; + } + } + + + if (app.textureAttributes != null) { + if ((canvas3d.texUnitState != null) && + (canvas3d.texUnitState[0] != null)) { + + if (canvas3d.texUnitState[0].texture != null) { + app.textureAttributes.updateNative(canvas3d, false, + canvas3d.texUnitState[0].texture.format); + } else { + app.textureAttributes.updateNative(canvas3d, false, + Texture.RGBA); + } + canvas3d.canvasDirty |= Canvas3D.TEXTUREBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + canvas3d.texUnitState[0].texAttrs = + app.textureAttributes; + } + } + else { + // If the canvas does not already have a null texAttribute + // load the default if necessary + if ((canvas3d.texUnitState != null) && + (canvas3d.texUnitState[0] != null) && + (canvas3d.texUnitState[0].texAttrs != null)) { + canvas3d.resetTextureAttributes(canvas3d.ctx); + canvas3d.canvasDirty |= Canvas3D.TEXTUREBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + canvas3d.texUnitState[0].texAttrs = null; + } + } + } + + if (app.coloringAttributes != null) { + app.coloringAttributes.updateNative(canvas3d.ctx, + dRed, dBlue, + dGreen, + alpha, enableLighting); + canvas3d.canvasDirty |= Canvas3D.COLORINGATTRS_DIRTY; + canvas3d.coloringAttributes = app.coloringAttributes; + } + else { + if (canvas3d.coloringAttributes != null) { + canvas3d.resetColoringAttributes(canvas3d.ctx, + red, green, blue, alpha, + enableLighting); + canvas3d.canvasDirty |= Canvas3D.COLORINGATTRS_DIRTY; + canvas3d.coloringAttributes = null; + } + } + + + if (app.transparencyAttributes != null) { + app.transparencyAttributes.updateNative(canvas3d.ctx, + alpha, geometryType, + polygonMode, + lineAA, pointAA); + canvas3d.canvasDirty |= Canvas3D.TRANSPARENCYATTRS_DIRTY; + canvas3d.transparency = app.transparencyAttributes; + + useAlpha = useAlpha || ((app.transparencyAttributes.transparencyMode != + TransparencyAttributes.NONE) + && + (VirtualUniverse.mc.isD3D() + || + (!VirtualUniverse.mc.isD3D() && + (app.transparencyAttributes. transparencyMode != + TransparencyAttributes.SCREEN_DOOR)))); + } else { + canvas3d.resetTransparency(canvas3d.ctx, geometryType, + polygonMode, lineAA, pointAA); + canvas3d.canvasDirty |= Canvas3D.TRANSPARENCYATTRS_DIRTY; + canvas3d.transparency = null; + } + + + if (app.renderingAttributes != null) { + ignoreVertexColors =app.renderingAttributes.ignoreVertexColors; + app.renderingAttributes.updateNative(canvas3d.ctx, + canvas3d.depthBufferWriteEnableOverride, + canvas3d.depthBufferEnableOverride); + canvas3d.canvasDirty |= Canvas3D.ATTRIBUTEBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + canvas3d.renderingAttrs = app.renderingAttributes; + + useAlpha = useAlpha || + (app.renderingAttributes.alphaTestFunction + != RenderingAttributes.ALWAYS); + } else { + // If the canvas does not alreadt have a null renderingAttrs + // load the default + ignoreVertexColors = false; + if (canvas3d.renderingAttrs != null) { + canvas3d.resetRenderingAttributes(canvas3d.ctx, + canvas3d.depthBufferWriteEnableOverride, + canvas3d.depthBufferEnableOverride); + canvas3d.canvasDirty |= Canvas3D.ATTRIBUTEBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + canvas3d.renderingAttrs = null; + } + } + + + if (app.polygonAttributes != null) { + app.polygonAttributes.updateNative(canvas3d.ctx); + canvas3d.canvasDirty |= Canvas3D.POLYGONATTRS_DIRTY; + canvas3d.polygonAttributes = app.polygonAttributes; + } else { + // If the canvas does not alreadt have a null polygonAttr + // load the default + if (canvas3d.polygonAttributes != null) { + canvas3d.resetPolygonAttributes(canvas3d.ctx); + canvas3d.canvasDirty |= Canvas3D.POLYGONATTRS_DIRTY; + canvas3d.polygonAttributes = null; + } + } + + + + if (app.lineAttributes != null) { + app.lineAttributes.updateNative(canvas3d.ctx); + canvas3d.canvasDirty |= Canvas3D.LINEATTRS_DIRTY; + canvas3d.lineAttributes = app.lineAttributes; + } else { + // If the canvas does not already have a null lineAttr + // load the default + if (canvas3d.lineAttributes != null) { + canvas3d.resetLineAttributes(canvas3d.ctx); + canvas3d.canvasDirty |= Canvas3D.LINEATTRS_DIRTY; + canvas3d.lineAttributes = null; + } + } + + + + if (app.pointAttributes != null) { + app.pointAttributes.updateNative(canvas3d.ctx); + canvas3d.canvasDirty |= Canvas3D.POINTATTRS_DIRTY; + canvas3d.pointAttributes = app.pointAttributes; + } else { + // If the canvas does not already have a null pointAttr + // load the default + if (canvas3d.pointAttributes != null) { + canvas3d.resetPointAttributes(canvas3d.ctx); + canvas3d.canvasDirty |= Canvas3D.POINTATTRS_DIRTY; + canvas3d.pointAttributes = null; + } + } + + canvas3d.appearance = app; + + } else { + if (canvas3d.appearance != null) { + resetAppearance(); + canvas3d.appearance = null; + } + } + + + return (useAlpha ); + } + + void initializeState() { + + canvas3d.setSceneAmbient(canvas3d.ctx, 0.0f, 0.0f, 0.0f); + canvas3d.disableFog(canvas3d.ctx); + canvas3d.resetRenderingAttributes(canvas3d.ctx,false, false); + + // reset the previously enabled texture units + + int prevNumActiveTexUnit = canvas3d.getNumActiveTexUnit(); + + if (prevNumActiveTexUnit > 0) { + for (int i = 0; i < prevNumActiveTexUnit; i++) { + if (canvas3d.texUnitState[i].texture != null) { + canvas3d.resetTexture(canvas3d.ctx, i); + canvas3d.texUnitState[i].texture = null; + } + if (canvas3d.texUnitState[i].texAttrs != null) { + canvas3d.resetTextureAttributes(canvas3d.ctx); + canvas3d.texUnitState[i].texAttrs = null; + } + if (canvas3d.texUnitState[i].texGen != null) { + canvas3d.resetTexCoordGeneration(canvas3d.ctx); + canvas3d.texUnitState[i].texGen = null; + } + canvas3d.texUnitState[i].mirror = null; + } + canvas3d.setNumActiveTexUnit(0); + } + + canvas3d.resetPolygonAttributes(canvas3d.ctx); + canvas3d.resetLineAttributes(canvas3d.ctx); + canvas3d.resetPointAttributes(canvas3d.ctx); + canvas3d.resetTransparency(canvas3d.ctx, RenderMolecule.SURFACE, + PolygonAttributes.POLYGON_FILL, + false, false); + canvas3d.resetColoringAttributes(canvas3d.ctx,1.0f, 1.0f, 1.0f, 1.0f, false); + canvas3d.updateMaterial(canvas3d.ctx, 1.0f, 1.0f, 1.0f, 1.0f); + } + + + void resetAppearance() { + + if (canvas3d.material != null) { + canvas3d.updateMaterial(canvas3d.ctx, + red, green, blue, alpha); + canvas3d.material = null; + canvas3d.canvasDirty |= Canvas3D.MATERIAL_DIRTY; + } + + // reset the previously enabled texture units + + int prevNumActiveTexUnit = canvas3d.getNumActiveTexUnit(); + + if (prevNumActiveTexUnit > 0) { + for (int i = 0; i < prevNumActiveTexUnit; i++) { + if (canvas3d.texUnitState[i].texture != null) { + canvas3d.resetTexture(canvas3d.ctx, i); + canvas3d.texUnitState[i].texture = null; + } + if (canvas3d.texUnitState[i].texAttrs != null) { + canvas3d.resetTextureAttributes(canvas3d.ctx); + canvas3d.texUnitState[i].texAttrs = null; + } + if (canvas3d.texUnitState[i].texGen != null) { + canvas3d.resetTexCoordGeneration(canvas3d.ctx); + canvas3d.texUnitState[i].texGen = null; + } + canvas3d.texUnitState[i].mirror = null; + } + canvas3d.canvasDirty |= Canvas3D.TEXTUREBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + canvas3d.setNumActiveTexUnit(0); + } + + if (canvas3d.coloringAttributes != null) { + canvas3d.resetColoringAttributes(canvas3d.ctx, + red, green, blue, alpha, enableLighting); + canvas3d.coloringAttributes = null; + canvas3d.canvasDirty |= Canvas3D.COLORINGATTRS_DIRTY; + } + + if (canvas3d.transparency != null) { + canvas3d.resetTransparency(canvas3d.ctx, RenderMolecule.SURFACE, + PolygonAttributes.POLYGON_FILL, lineAA, pointAA); + canvas3d.transparency = null; + canvas3d.canvasDirty |= Canvas3D.TRANSPARENCYATTRS_DIRTY; + } + + if (canvas3d.renderingAttrs != null) { + ignoreVertexColors = false; + canvas3d.resetRenderingAttributes(canvas3d.ctx, + canvas3d.depthBufferWriteEnableOverride, + canvas3d.depthBufferEnableOverride); + canvas3d.renderingAttrs = null; + canvas3d.canvasDirty |= Canvas3D.ATTRIBUTEBIN_DIRTY|Canvas3D.TEXTUREATTRIBUTES_DIRTY; + } + + if (canvas3d.polygonAttributes != null) { + canvas3d.resetPolygonAttributes(canvas3d.ctx); + canvas3d.polygonAttributes = null; + canvas3d.canvasDirty |= Canvas3D.POLYGONATTRS_DIRTY; + } + + if (canvas3d.lineAttributes != null) { + canvas3d.resetLineAttributes(canvas3d.ctx); + canvas3d.lineAttributes = null; + canvas3d.canvasDirty |= Canvas3D.LINEATTRS_DIRTY; + } + + if (canvas3d.pointAttributes != null) { + canvas3d.resetPointAttributes(canvas3d.ctx); + canvas3d.pointAttributes = null; + canvas3d.canvasDirty |= Canvas3D.POINTATTRS_DIRTY; + } + } + + void sendRenderMessage(boolean renderRun, int command, + Object arg1, Object arg2) { + + // send a message to the request renderer + + J3dMessage renderMessage = VirtualUniverse.mc.getMessage(); + renderMessage.threads = J3dThread.RENDER_THREAD; + renderMessage.type = J3dMessage.RENDER_IMMEDIATE; + renderMessage.universe = null; + renderMessage.view = null; + renderMessage.args[0] = canvas3d; + renderMessage.args[1] = getImmCommand(command); + renderMessage.args[2] = arg1; + renderMessage.args[3] = arg2; + + while (!canvas3d.view.inRenderThreadData) { + // wait until the renderer thread data in added in + // MC:RenderThreadData array ready to receive message + MasterControl.threadYield(); + } + + canvas3d.screen.renderer.rendererStructure.addMessage(renderMessage); + + if (renderRun) { + // notify mc that there is work to do + VirtualUniverse.mc.sendRunMessage(canvas3d.view, J3dThread.RENDER_THREAD); + } else { + // notify mc that there is work for the request renderer + VirtualUniverse.mc.setWorkForRequestRenderer(); + } + } + + void sendSoundMessage(int command, Object arg1, Object arg2) { + if ((canvas3d.view == null) || + (canvas3d.view.universe == null) ) { + return; + } + // send a message to the request sound scheduling + J3dMessage soundMessage = VirtualUniverse.mc.getMessage(); + soundMessage.threads = J3dThread.SOUND_SCHEDULER; + soundMessage.type = J3dMessage.RENDER_IMMEDIATE; + soundMessage.universe = canvas3d.view.universe; + soundMessage.view = canvas3d.view; + soundMessage.args[0] = getImmCommand(command); + soundMessage.args[1] = arg1; + soundMessage.args[2] = arg2; + // notify mc that there is work to do + VirtualUniverse.mc.processMessage(soundMessage); + } + + static Integer getImmCommand(int command) { + if (commands[command] == null) { + commands[command] = new Integer(command); + } + return commands[command]; + } + + synchronized void runMonitor(int action) { + if (action == J3dThread.WAIT) { + while (!gcReady) { + waiting++; + try { + wait(); + } catch (InterruptedException e){} + waiting--; + } + gcReady = false; + } else { + gcReady = true; + if (waiting > 0) { + notify(); + } + } + } + +} + diff --git a/src/classes/share/javax/media/j3d/Group.java b/src/classes/share/javax/media/j3d/Group.java new file mode 100644 index 0000000..01cb671 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Group.java @@ -0,0 +1,532 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.BitSet; +import java.util.Vector; +import java.util.Hashtable; +import java.util.Enumeration; + +/** + * The Group node object is a general-purpose grouping node. Group + * nodes have exactly one parent and an arbitrary number of children + * that are rendered in an unspecified order (or in parallel). Null + * children are allowed; no operation is performed on a null child + * node. Operations on Group node objects include adding, removing, + * and enumerating the children of the Group node. The subclasses of + * Group node add additional semantics. + */ + +public class Group extends Node { + /** + * Specifies that this Group node allows reading its children. + */ + public static final int + ALLOW_CHILDREN_READ = CapabilityBits.GROUP_ALLOW_CHILDREN_READ; + + /** + * Specifies that this Group node allows writing its children. + */ + public static final int + ALLOW_CHILDREN_WRITE = CapabilityBits.GROUP_ALLOW_CHILDREN_WRITE; + + /** + * Specifies that this Group node allows adding new children. + */ + public static final int + ALLOW_CHILDREN_EXTEND = CapabilityBits.GROUP_ALLOW_CHILDREN_EXTEND; + + /** + * Specifies that this Group node allows reading its collision Bounds + */ + public static final int + ALLOW_COLLISION_BOUNDS_READ = + CapabilityBits.GROUP_ALLOW_COLLISION_BOUNDS_READ; + + /** + * Specifies that this Group node allows writing its collision Bounds + */ + public static final int + ALLOW_COLLISION_BOUNDS_WRITE = + CapabilityBits.GROUP_ALLOW_COLLISION_BOUNDS_WRITE; + + /** + * Creates the retained mode GroupRetained object that this + * Group component object will point to. + */ + void createRetained() { + retained = new GroupRetained(); + retained.setSource(this); + } + + + /** + * Sets the collision bounds of a node. + * @param bounds the collision bounding object for a node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setCollisionBounds(Bounds bounds) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLLISION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Group0")); + + ((GroupRetained)this.retained).setCollisionBounds(bounds); + } + + /** + * Returns the collision bounding object of this node. + * @return the node's collision bounding object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getCollisionBounds() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLLISION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Group1")); + + return ((GroupRetained)this.retained).getCollisionBounds(); + } + + /** + * Replaces the child node at the specified index in this + * group node's list of children with the specified child. + * @param child the new child + * @param index which child to replace. The <code>index</code> must + * be a value + * greater than or equal to 0 and less than <code>numChildren()</code>. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live or compiled scene graph and the child node being set is not + * a BranchGroup node + * @exception MultipleParentException if <code>child</code> has already + * been added as a child of another group node + * @exception IndexOutOfBoundsException if <code>index</code> is invalid + */ + public void setChild(Node child, int index) { + if (child instanceof SharedGroup) { + throw new IllegalArgumentException(J3dI18N.getString("Group2")); + } + + if (isLiveOrCompiled()) { + Node oldchild = + (Node) ((GroupRetained)this.retained).getChild(index); + if (! (child instanceof BranchGroup)) + throw new RestrictedAccessException(J3dI18N.getString("Group3")); + + if (!getCapability(ALLOW_CHILDREN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Group13")); + + if ((oldchild != null) && + (! ((BranchGroup)oldchild).getCapability(BranchGroup.ALLOW_DETACH))) { + throw new CapabilityNotSetException(J3dI18N.getString("Group4")); + } + } + + ((GroupRetained)retained).setChild(child, index); + } + + /** + * Inserts the specified child node in this group node's list of + * children at the specified index. + * @param child the new child + * @param index at which location to insert. The <code>index</code> + * must be a value + * greater than or equal to 0 and less than or equal to + * <code>numChildren()</code>. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live + * or compiled scene graph and the child node being inserted is not + * a BranchGroup node + * @exception MultipleParentException if <code>child</code> has already + * been added as a child of another group node. + * @exception IndexOutOfBoundsException if <code>index</code> is invalid. + */ + public void insertChild(Node child, int index) { + if (child instanceof SharedGroup) { + throw new IllegalArgumentException(J3dI18N.getString("Group2")); + } + + if (isLiveOrCompiled()) { + if (! (child instanceof BranchGroup)) + throw new RestrictedAccessException(J3dI18N.getString("Group6")); + + if (!this.getCapability(ALLOW_CHILDREN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Group14")); + } + + ((GroupRetained)this.retained).insertChild(child, index); + } + + /** + * Removes the child node at the specified index from this group node's + * list of children. + * @param index which child to remove. The <code>index</code> + * must be a value + * greater than or equal to 0 and less than <code>numChildren()</code>. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live or compiled scene graph and the child node being removed is not + * a BranchGroup node + * @exception IndexOutOfBoundsException if <code>index</code> is invalid. + */ + public void removeChild(int index) { + if (isLiveOrCompiled()) { + Node child = ((GroupRetained)this.retained).getChild(index); + if (!(child instanceof BranchGroup)) { + throw new RestrictedAccessException(J3dI18N.getString("Group7")); + } + + if (!this.getCapability(ALLOW_CHILDREN_WRITE)) { + throw new CapabilityNotSetException(J3dI18N.getString("Group15")); + } + + if (!((BranchGroup)child).getCapability(BranchGroup.ALLOW_DETACH)) { + throw new CapabilityNotSetException(J3dI18N.getString("Group4")); + } + } + + ((GroupRetained)this.retained).removeChild(index); + } + + /** + * Retrieves the child node at the specified index in + * this group node's list of children. + * @param index which child to return. + * @return the children at location index. The <code>index</code> + * must be a value + * greater than or equal to 0 and less than <code>numChildren()</code>. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * @exception IndexOutOfBoundsException if <code>index</code> is invalid. + */ + public Node getChild(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHILDREN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Group9")); + + return (Node) ((GroupRetained)this.retained).getChild(index); + } + + /** + * Returns an Enumeration object of this group node's list of children. + * @return an Enumeration object of all the children + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + */ + public Enumeration getAllChildren() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHILDREN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Group9")); + + return (Enumeration)((GroupRetained)this.retained).getAllChildren(); + } + + /** + * Appends the specified child node to this group node's list of children. + * @param child the child to add to this node's list of children + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part + * of live + * or compiled scene graph and the child node being added is not + * a BranchGroup node + * @exception MultipleParentException if <code>child</code> has already + * been added as a child of another group node. + */ + public void addChild(Node child) { + if (child instanceof SharedGroup) { + throw new IllegalArgumentException(J3dI18N.getString("Group2")); + } + + if (isLiveOrCompiled()) { + if (! (child instanceof BranchGroup)) + throw new RestrictedAccessException(J3dI18N.getString("Group12")); + + if(!this.getCapability(ALLOW_CHILDREN_EXTEND)) + throw new CapabilityNotSetException(J3dI18N.getString("Group16")); + } + + ((GroupRetained)this.retained).addChild(child); + } + + /** + * Moves the specified branch group node from its existing location to + * the end of this group node's list of children. + * @param branchGroup the branch group node to move to this node's list + * of children + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + */ + public void moveTo(BranchGroup branchGroup) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_CHILDREN_EXTEND)) + throw new CapabilityNotSetException(J3dI18N.getString("Group16")); + + if (! branchGroup.getCapability(BranchGroup.ALLOW_DETACH)) { + throw new CapabilityNotSetException(J3dI18N.getString("Group4")); + } + } + + ((GroupRetained)this.retained).moveTo(branchGroup); + } + + /** + * Returns a count of this group node's children. + * @return the number of children descendant from this node. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + */ + public int numChildren() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHILDREN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Group9")); + + return ((GroupRetained)this.retained).numChildren(); + } + + + /** + * Retrieves the index of the specified child node in + * this group node's list of children. + * + * @param child the child node to be looked up. + * @return the index of the specified child node; + * returns -1 if the object is not in the list. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int indexOfChild(Node child) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHILDREN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Group9")); + + return ((GroupRetained)this.retained).indexOfChild(child); + } + + + /** + * Removes the specified child node from this group node's + * list of children. + * If the specified object is not in the list, the list is not modified. + * + * @param child the child node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live or compiled scene graph and the child node being removed is not + * a BranchGroup node + * + * @since Java 3D 1.3 + */ + public void removeChild(Node child) { + + if (isLiveOrCompiled()) { + if (!(child instanceof BranchGroup)) { + throw new RestrictedAccessException(J3dI18N.getString("Group7")); + } + + if (!this.getCapability(ALLOW_CHILDREN_WRITE)) { + throw new CapabilityNotSetException(J3dI18N.getString("Group15")); + } + + if (!((BranchGroup)child).getCapability(BranchGroup.ALLOW_DETACH)) { + throw new CapabilityNotSetException(J3dI18N.getString("Group4")); + } + } + + ((GroupRetained)retained).removeChild(child); + } + + + /** + * Removes all children from this Group node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live or compiled scene graph and any of the children being removed are + * not BranchGroup nodes + * + * @since Java 3D 1.3 + */ + public void removeAllChildren() { + + if (isLiveOrCompiled()) { + GroupRetained groupR = (GroupRetained)this.retained; + for (int index = groupR.numChildren() - 1; index >= 0; index--) { + Node child = groupR.getChild(index); + if (! (child instanceof BranchGroup)) + throw new RestrictedAccessException(J3dI18N.getString("Group7")); + + if (!this.getCapability(ALLOW_CHILDREN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Group15")); + + if (!((BranchGroup)child).getCapability(BranchGroup.ALLOW_DETACH)) { + throw new CapabilityNotSetException(J3dI18N.getString("Group4")); + } + } + } + + ((GroupRetained)retained).removeAllChildren(); + } + + + /** + * Causes this Group node to be reported as the collision target when + * collision is being used and this node or any of its children is in + * a collision. The default value is false. For collision with + * USE_GEOMETRY set, the collision traverser will check the geometry + * of all the Group node's leaf descendants; for collision with + * USE_BOUNDS set, the collision traverser will only check the bounds + * at this Group node. In both cases, if there is a collision, this + * Group node will be reported as the colliding object in the + * SceneGraphPath. This reporting is done regardless of whether + * ENABLE_COLLISION_REPORTING + * is set for this group node (setting alternate collision target to + * true implies collision reporting). + * @param target Indicates whether this Group node can be the target + * of a collision. + * @see WakeupOnCollisionEntry + * @see WakeupOnCollisionMovement + * @see WakeupOnCollisionExit + */ + public void setAlternateCollisionTarget(boolean target) { + ((GroupRetained)this.retained).setAlternateCollisionTarget(target); + } + + /** + * Returns the collision target state. + * @return Indicates whether this Group node can be the target of a + * collision. + */ + public boolean getAlternateCollisionTarget() { + return ((GroupRetained)this.retained).getAlternateCollisionTarget(); + } + + /** + * Duplicates all the nodes of the specified sub-graph. For Group Nodes + * the group node is duplicated via a call to <code>cloneNode</code> and + * then <code>cloneTree</code> is called for each child node. For + * Leaf Nodes, component + * data can either be duplicated or be made a reference to the original + * data. Leaf Node cloneTree behavior is determined by the + * <code>duplicateOnCloneTree</code> flag found in every Leaf Node's + * component data class and by the <code>forceDuplicate</code> paramter. + * + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> + * flag to be ignored. When <code>false</code>, the value of each + * node's + * <code>duplicateOnCloneTree</code> determines whether data is + * duplicated or copied. + * + * + * @return a reference to the cloned scene graph. + * + * @see NodeComponent#setDuplicateOnCloneTree + */ + Node cloneTree(boolean forceDuplicate, Hashtable nodeHashtable) { + Group g = (Group) super.cloneTree(forceDuplicate, nodeHashtable); + GroupRetained rt = (GroupRetained) retained; + + int nChildren = rt.numChildren(); + // call cloneTree on all child nodes + for (int i = 0; i < nChildren; i++) { + Node n = rt.getChild(i); + Node clonedN = n.cloneTree(forceDuplicate, nodeHashtable); + // add the cloned child to the cloned group node + ((GroupRetained) g.retained).addChild(clonedN); + + } + return g; + } + + + + /** + * Copies all Node information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + GroupRetained attr = (GroupRetained) originalNode.retained; + GroupRetained rt = (GroupRetained) retained; + + rt.setCollisionBounds(attr.getCollisionBounds()); + rt.setAlternateCollisionTarget(attr.getAlternateCollisionTarget()); + // throw away any child create before, since some node such as + // Sphere has already created its own branch + // Without doing this, we may end up with two branches with exactly + // the same content when cloneTree() is invoked. + rt.children.clear(); + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Group g = new Group(); + g.duplicateNode(this, forceDuplicate); + return g; + } + + + /** + * Constructs a Group node with default parameters. The default + * values are as follows: + * <ul> + * collision bounds : null<br> + * alternate collision target : false<br> + * </ul> + */ + public Group() { + } +} diff --git a/src/classes/share/javax/media/j3d/GroupRetained.java b/src/classes/share/javax/media/j3d/GroupRetained.java new file mode 100644 index 0000000..5073837 --- /dev/null +++ b/src/classes/share/javax/media/j3d/GroupRetained.java @@ -0,0 +1,3110 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; +import java.util.Enumeration; +import java.util.ArrayList; +import java.util.Hashtable; + +/** + * Group node. + */ + +class GroupRetained extends NodeRetained implements BHLeafInterface { + /** + * The Group Node's children vector. + */ + ArrayList children = new ArrayList(1); + + /** + * The Group node's collision bounds in local coordinates. + */ + Bounds collisionBound = null; + + // The locale that this node is decended from + Locale locale = null; + + // The list of lights that are scoped to this node + // One such arraylist per path. If not in sharedGroup + // then only index 0 is valid + ArrayList lights = null; + + // The list of fogs that are scoped to this node + // One such arraylist per path. If not in sharedGroup + // then only index 0 is valid + ArrayList fogs = null; + + // The list of model clips that are scoped to this node + // One such arraylist per path. If not in sharedGroup + // then only index 0 is valid + ArrayList modelClips = null; + + + // The list of alternateappearance that are scoped to this node + // One such arraylist per path. If not in sharedGroup + // then only index 0 is valid + ArrayList altAppearances = null; + + + // indicates whether this Group node can be the target of a collision + boolean collisionTarget = false; + + // per child switchLinks + ArrayList childrenSwitchLinks = null; + + // the immediate childIndex of a parentSwitchLink + int parentSwitchLinkChildIndex = -1; + + // per shared path ordered path data + ArrayList orderedPaths = null; + + /** + * If collisionBound is set, this is equal to the + * transformed collisionBounds, otherwise it is equal + * to the transformed localBounds. + * This variable is set to null unless collisionTarget = true. + * This bound is only used by mirror Group. + */ + BoundingBox collisionVwcBounds; + + /** + * Mirror group of this node, it is only used when + * collisionTarget = true. Otherwise it is set to null. + * If not in shared group, + * only entry 0 is used. + * + */ + ArrayList mirrorGroup; + + /** + * key of mirror GroupRetained. + */ + HashKey key; + + /** + * sourceNode of this mirror Group + */ + GroupRetained sourceNode; + + /** + * The BHLeafNode for this GeometryAtom. + */ + BHLeafNode bhLeafNode = null; + + // + // The following variables are used during compile + // + + // true if this is the root of the scenegraph tree + boolean isRoot = false; + + boolean allocatedLights = false; + + boolean allocatedFogs = false; + + boolean allocatedMclips = false; + + boolean allocatedAltApps = false; + + // > 0 if this group is being used in scoping + int scopingRefCount = 0; + + + ArrayList compiledChildrenList = null; + + boolean isInClearLive = false; + + // List of viewes scoped to this Group, for all subclasses + // of group, except ViewSpecificGroup its a pointer to closest + // ViewSpecificGroup parent + // viewList for this node, if inSharedGroup is + // false then only viewList(0) is valid + // For VSGs, this list is an intersection of + // higher level VSGs + ArrayList viewLists = null; + + // True if this Node is descendent of ViewSpecificGroup; + boolean inViewSpecificGroup = false; + + GroupRetained() { + this.nodeType = NodeRetained.GROUP; + localBounds = new BoundingSphere(); + ((BoundingSphere)localBounds).setRadius( -1.0 ); + } + + /** + * Sets the collision bounds of a node. + * @param bounds the bounding object for the node + */ + void setCollisionBounds(Bounds bounds) { + if (bounds == null) { + this.collisionBound = null; + } else { + this.collisionBound = (Bounds)bounds.clone(); + } + + if (source.isLive()) { + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.COLLISION_BOUND_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY; + message.universe = universe; + message.args[0] = this; + VirtualUniverse.mc.processMessage(message); + } + + } + + + /** + * Gets the collision bounds of a node. + * @return the node's bounding object + */ + Bounds getCollisionBounds() { + return (collisionBound == null ? null : (Bounds)collisionBound.clone()); + } + + /** + * Replaces the specified child with the child provided. + * @param child the new child + * @param index which child to replace + */ + void setChild(Node child, int index) { + + checkValidChild(child, "GroupRetained0"); + if (this.source.isLive()) { + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + doSetChild(child, index); + universe.setLiveState.clear(); + } + universe.waitForMC(); + + } else { + doSetChild(child, index); + if (universe != null) { + synchronized (universe.sceneGraphLock) { + universe.setLiveState.clear(); + } + } + } + } + + // The method that does the work once the lock is acquired. + void doSetChild(Node child, int index) { + NodeRetained oldchildr; + J3dMessage[] messages = null; + int numMessages = 0; + int attachStartIndex = 0; + + + // since we want to make sure the replacement of the child + // including removal of the oldChild and insertion of the newChild + // all happen in the same frame, we'll send all the necessary + // messages to masterControl for processing in one call. + // So let's first find out how many messages will be sent + + oldchildr = (NodeRetained) children.get(index); + + if (this.source.isLive()) { + if (oldchildr != null) { + numMessages+=3; // REMOVE_NODES, ORDERED_GROUP_REMOVED + // VIEWSPECIFICGROUP_CLEAR + attachStartIndex = 3; + } + + if (child != null) { + numMessages+=4; // INSERT_NODES,BEHAVIOR_ACTIVATE,ORDERED_GROUP_INSERTED, + // VIEWSPECIFICGROUP_INIT + } + + messages = new J3dMessage[numMessages]; + for (int i = 0; i < numMessages; i++) { + messages[i] = VirtualUniverse.mc.getMessage(); + } + } + + if(oldchildr != null) { + oldchildr.setParent(null); + checkClearLive(oldchildr, messages, 0, index, null); + } + removeChildrenData(index); + + if(child == null) { + children.set(index, null); + if (messages != null) { + VirtualUniverse.mc.processMessage(messages); + } + return; + } + + NodeRetained childr = (NodeRetained) child.retained; + childr.setParent(this); + children.set(index, childr); + + + insertChildrenData(index); + checkSetLive(childr, index, messages, attachStartIndex, null); + if (this.source.isLive()) { + ((BranchGroupRetained)childr).isNew = true; + } + + if (messages != null) { + VirtualUniverse.mc.processMessage(messages); + } + } + + /** + * Inserts the specified child at specified index. + * @param child the new child + * @param index position to insert new child at + */ + void insertChild(Node child, int index) { + + checkValidChild(child, "GroupRetained1"); + if (this.source.isLive()) { + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + doInsertChild(child, index); + universe.setLiveState.clear(); + } + universe.waitForMC(); + } else { + doInsertChild(child, index); + if (universe != null) { + synchronized (universe.sceneGraphLock) { + universe.setLiveState.clear(); + } + } + } + } + + // The method that does the work once the lock is acquired. + void doInsertChild(Node child, int index) { + int i; + NodeRetained childi; + + insertChildrenData(index); + for (i=index; i<children.size(); i++) { + childi = (NodeRetained) children.get(i); + if(childi != null) + childi.childIndex++; + } + if(child==null) { + children.add(index, null); + return; + } + + NodeRetained childr = (NodeRetained) child.retained; + childr.setParent(this); + children.add(index, childr); + checkSetLive(childr, index, null, 0, null); + if (this.source.isLive()) { + ((BranchGroupRetained)childr).isNew = true; + } + } + + /** + * Removes the child at specified index. + * @param index which child to remove + */ + void removeChild(int index) { + + if (this.source.isLive()) { + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + doRemoveChild(index, null, 0); + universe.setLiveState.clear(); + } + universe.waitForMC(); + } else { + doRemoveChild(index, null, 0); + if (universe != null) { + synchronized (universe.sceneGraphLock) { + universe.setLiveState.clear(); + } + } + } + } + + /** + * Returns the index of the specified Node in this Group's list of Nodes + * @param Node whose index is desired + * @return index of the Node + */ + int indexOfChild(Node child) { + if(child != null) + return children.indexOf((NodeRetained)child.retained); + else + return children.indexOf(null); + } + + /** + * Removes the specified child from this Group's list of + * children. If the specified child is not found, the method returns + * quietly + * + * @param child to be removed + */ + void removeChild(Node child) { + int i = indexOfChild(child); + if(i >= 0) + removeChild(i); + } + + void removeAllChildren() { + int n = children.size(); + for(int i = n-1; i >= 0; i--) { + removeChild(i); + } + } + + + // The method that does the work once the lock is acquired. + void doRemoveChild(int index, J3dMessage messages[], int messageIndex) { + NodeRetained oldchildr, child; + int i; + + oldchildr = (NodeRetained) children.get(index); + + int size = children.size(); + for (i=index; i<size; i++) { + child = (NodeRetained) children.get(i); + if(child != null) + child.childIndex--; + } + + if(oldchildr != null) { + oldchildr.setParent(null); + checkClearLive(oldchildr, messages, messageIndex, index, null); + } + + children.remove(index); + removeChildrenData(index); + + if (nodeType == NodeRetained.SWITCH) { + // force reEvaluation of switch children + SwitchRetained sg = (SwitchRetained)this; + sg.setWhichChild(sg.whichChild, true); + } + + } + + /** + * Returns the child specified by the index. + * @param index which child to return + * @return the children at location index + */ + Node getChild(int index) { + + SceneGraphObjectRetained sgo = (SceneGraphObjectRetained) children.get(index); + if(sgo == null) + return null; + else + return (Node) sgo.source; + } + + /** + * Returns an enumeration object of the children. + * @return an enumeration object of the children + */ + Enumeration getAllChildren() { + Vector userChildren=new Vector(children.size()); + SceneGraphObjectRetained sgo; + + for(int i=0; i<children.size(); i++) { + sgo = (SceneGraphObjectRetained)children.get(i); + if(sgo != null) + userChildren.add(sgo.source); + else + userChildren.add(null); + } + + return userChildren.elements(); + } + + void checkValidChild(Node child, String s) { + + if ((child != null) && + (((child instanceof BranchGroup) && + (((BranchGroupRetained) child.retained).attachedToLocale)) || + (((NodeRetained)child.retained).parent != null))) { + throw new MultipleParentException(J3dI18N.getString(s)); + } + } + + /** + * Appends the specified child to this node's list of children. + * @param child the child to add to this node's list of children + */ + void addChild(Node child) { + checkValidChild(child, "GroupRetained2"); + + if (this.source.isLive()) { + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + doAddChild(child, null, 0); + universe.setLiveState.clear(); + } + universe.waitForMC(); + } else { + doAddChild(child, null, 0); + if (universe != null) { + synchronized (universe.sceneGraphLock) { + universe.setLiveState.clear(); + } + } + } + } + + // The method that does the work once the lock is acquired. + void doAddChild(Node child, J3dMessage messages[], int messageIndex) { + + appendChildrenData(); + + if(child == null) { + children.add(null); + return; + } + + NodeRetained childr = (NodeRetained) child.retained; + childr.setParent(this); + children.add(childr); + checkSetLive(childr, children.size()-1, messages, messageIndex, null); + if (this.source.isLive()) { + ((BranchGroupRetained)childr).isNew = true; + } + + } + + void moveTo(BranchGroup bg) { + if (this.source.isLive()) { + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + doMoveTo(bg); + universe.setLiveState.clear(); + } + universe.waitForMC(); + } else { + doMoveTo(bg); + if (universe != null) { + synchronized (universe.sceneGraphLock) { + universe.setLiveState.clear(); + } + } + } + } + + // The method that does the work once the lock is acquired. + void doMoveTo(BranchGroup branchGroup) { + J3dMessage messages[] = null; + int numMessages = 0; + int detachStartIndex = 0; + int attachStartIndex = 0; + if(branchGroup != null) { + BranchGroupRetained bg = (BranchGroupRetained) branchGroup.retained; + GroupRetained g = (GroupRetained)bg.parent; + + // Find out how many messages to be created + // Note that g can be NULL if branchGroup parent is + // a Locale, in this case the following condition + // will fail. + // Figure out the number of messages based on whether the group + // from which its moving from is live and group to which its + // moving to is live + if (g != null) { + if (g.source.isLive()) { + numMessages = 3; // REMOVE_NODES, ORDERED_GROUP_REMOVED,VIEWSPECIFICGROUP_CLEAR + attachStartIndex = 3; + } + else { + numMessages = 0; + attachStartIndex = 0; + } + + } + else { // Attached to locale + numMessages = 3; // REMOVE_NODES, ORDERED_GROUP_REMOVED, VIEWSPECIFICGROUP_CLEAR + attachStartIndex = 3; + } + // Now, do the evaluation for the group that its going to be + // attached to .. + if (this.source.isLive()) { + numMessages+=4; // INSERT_NODES, BEHAVIOR_ACTIVATE + // ORDERED_GROUP_INSERTED, VIEWSPECIFICGROUP_INIT + + } + messages = new J3dMessage[numMessages]; + for (int i=0; i<numMessages; i++) { + messages[i] = VirtualUniverse.mc.getMessage(); + messages[i].type = J3dMessage.INVALID_TYPE; + } + + // Remove it from it's parents state + if (g == null) { + if (bg.locale != null) { + bg.locale.doRemoveBranchGraph(branchGroup, + messages, detachStartIndex); + } + } else { + g.doRemoveChild(g.children.indexOf(bg), + messages, + detachStartIndex); + } + } + + + // Add it to it's new parent + doAddChild(branchGroup, messages, attachStartIndex); + + if (numMessages > 0) { + int count = 0; + for (int i=0; i < numMessages; i++) { + if (messages[i].type != J3dMessage.INVALID_TYPE) { + count++; + } + } + if (count == numMessages) { + // in most cases + VirtualUniverse.mc.processMessage(messages); + } else { + J3dMessage ms[] = null; + + if (count > 0) { + ms = new J3dMessage[count]; + } + + int k=0; + for (int i=0; i < numMessages; i++) { + if (messages[i].type != J3dMessage.INVALID_TYPE) { + ms[k++] = messages[i]; + } else { + VirtualUniverse.mc.addMessageToFreelists(messages[i]); + } + } + if (ms != null) { + VirtualUniverse.mc.processMessage(ms); + } + } + } + } + + + /** + * Returns a count of this nodes' children. + * @return the number of children descendant from this node + */ + int numChildren() { + return children.size(); + } + + // Remove a light from the list of lights + void removeLight(int numLgt, LightRetained[] removelight, HashKey key) { + ArrayList l; + int index; + if (inSharedGroup) { + int hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + l = (ArrayList)lights.get(hkIndex); + if (l != null) { + for (int i = 0; i < numLgt; i++) { + index = l.indexOf(removelight[i]); + l.remove(index); + } + } + } + else { + l = (ArrayList)lights.get(0); + for (int i = 0; i < numLgt; i++) { + index = l.indexOf(removelight[i]); + l.remove(index); + } + } + + /* + // TODO: lights may remove twice or more during clearLive(), + // one from itself and one call from every LightRetained + // reference this. So there is case that this procedure get + // called when light already removed. + if (i >= 0) + lights.remove(i); + */ + } + + + void addAllNodesForScopedLight(int numLgts, + LightRetained[] ml, + ArrayList list, + HashKey k) { + if (inSharedGroup) { + for (int i = 0; i < localToVworldKeys.length; i++) { + k.set(localToVworldKeys[i]); + processAllNodesForScopedLight(numLgts, ml, list, k); + } + } + else { + processAllNodesForScopedLight(numLgts, ml, list, k); + } + } + + void processAllNodesForScopedLight(int numLgts, LightRetained[] ml, ArrayList list, HashKey k) { + if (allocatedLights) { + addLight(ml, numLgts, k); + } + if (this.source.isLive() || this.isInSetLive()) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) { + if (child instanceof GroupRetained && (child.source.isLive() || child.isInSetLive())) + ((GroupRetained)child).processAllNodesForScopedLight(numLgts, ml, list, k); + else if (child instanceof LinkRetained && (child.source.isLive()|| child.isInSetLive())) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + ((GroupRetained)(ln.sharedGroup)). + processAllNodesForScopedLight(numLgts, ml, list, k.append("+"). + append(ln.nodeId)); + k.count = lastCount; + } else if (child instanceof Shape3DRetained && child.source.isLive()) { + ((Shape3DRetained)child).getMirrorObjects(list, k); + } else if (child instanceof MorphRetained && child.source.isLive()) { + ((MorphRetained)child).getMirrorObjects(list, k); + } + } + } + } + } + + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + void removeAllNodesForScopedLight(int numLgts, LightRetained[] ml, ArrayList list, HashKey k) { + if (inSharedGroup) { + for (int i = 0; i < localToVworldKeys.length; i++) { + k.set(localToVworldKeys[i]); + processRemoveAllNodesForScopedLight(numLgts, ml, list, k); + } + } + else { + processRemoveAllNodesForScopedLight(numLgts, ml, list, k); + } + } + + void processRemoveAllNodesForScopedLight(int numLgts, LightRetained[] ml, ArrayList list, HashKey k) { + if (allocatedLights) { + removeLight(numLgts,ml, k); + } + // If the source is live, then notify the children + if (this.source.isLive() && !isInClearLive) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) { + if (child instanceof GroupRetained &&(child.source.isLive() && + ! ((GroupRetained)child).isInClearLive)) + ((GroupRetained)child).processRemoveAllNodesForScopedLight(numLgts, ml,list, k); + else if (child instanceof LinkRetained && child.source.isLive()) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + ((GroupRetained)(ln.sharedGroup)). + processRemoveAllNodesForScopedLight(numLgts, ml, list, k.append("+"). + append(ln.nodeId)); + k.count = lastCount; + } else if (child instanceof Shape3DRetained && child.source.isLive() ) { + ((Shape3DRetained)child).getMirrorObjects(list, k); + } else if (child instanceof MorphRetained && child.source.isLive()) { + ((MorphRetained)child).getMirrorObjects(list, k); + } + } + } + } + } + + + void addAllNodesForScopedFog(FogRetained mfog, ArrayList list, HashKey k) { + if (inSharedGroup) { + for (int i = 0; i < localToVworldKeys.length; i++) { + k.set(localToVworldKeys[i]); + processAddNodesForScopedFog(mfog, list, k); + } + } + else { + processAddNodesForScopedFog(mfog, list, k); + } + } + + void processAddNodesForScopedFog(FogRetained mfog, ArrayList list, HashKey k) { + // If this group has it own scoping list then add .. + if (allocatedFogs) + addFog(mfog, k); + // If the source is live, then notify the children + if (this.source.isLive() || this.isInSetLive()) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) { + if (child instanceof GroupRetained && (child.source.isLive()|| child.isInSetLive())) + ((GroupRetained)child).processAddNodesForScopedFog(mfog, list, k); + else if (child instanceof LinkRetained && (child.source.isLive()||child.isInSetLive() )) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + ((GroupRetained)(ln.sharedGroup)). + processAddNodesForScopedFog(mfog, list, k.append("+"). + append(ln.nodeId)); + k.count = lastCount; + } else if (child instanceof Shape3DRetained && child.source.isLive()) { + ((Shape3DRetained)child).getMirrorObjects(list, k); + } else if (child instanceof MorphRetained && child.source.isLive()) { + ((MorphRetained)child).getMirrorObjects(list, k); + } + } + } + } + } + + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + void removeAllNodesForScopedFog(FogRetained mfog, ArrayList list, HashKey k) { + if (inSharedGroup) { + for (int i = 0; i < localToVworldKeys.length; i++) { + k.set(localToVworldKeys[i]); + processRemoveAllNodesForScopedFog(mfog, list, k); + } + } + else { + processRemoveAllNodesForScopedFog(mfog, list, k); + } + } + void processRemoveAllNodesForScopedFog(FogRetained mfog, ArrayList list, HashKey k) { + // If the source is live, then notify the children + if (allocatedFogs) + removeFog(mfog, k); + if (this.source.isLive() && !isInClearLive) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) { + if (child instanceof GroupRetained &&(child.source.isLive() && + ! ((GroupRetained)child).isInClearLive)) + ((GroupRetained)child).processRemoveAllNodesForScopedFog(mfog, list, k); + else if (child instanceof LinkRetained && child.source.isLive()) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + ((GroupRetained)(ln.sharedGroup)). + processRemoveAllNodesForScopedFog(mfog, list, k.append("+"). + append(ln.nodeId)); + k.count = lastCount; + } else if (child instanceof Shape3DRetained && child.source.isLive() ) { + ((Shape3DRetained)child).getMirrorObjects(list, k); + } else if (child instanceof MorphRetained && child.source.isLive()) { + ((MorphRetained)child).getMirrorObjects(list, k); + } + } + } + } + } + + void addAllNodesForScopedModelClip(ModelClipRetained mModelClip, ArrayList list, HashKey k) { + if (inSharedGroup) { + for (int i = 0; i < localToVworldKeys.length; i++) { + k.set(localToVworldKeys[i]); + processAddNodesForScopedModelClip(mModelClip, list, k); + } + } + else { + processAddNodesForScopedModelClip(mModelClip, list, k); + } + } + + void processAddNodesForScopedModelClip(ModelClipRetained mModelClip, + ArrayList list, + HashKey k) { + if (allocatedMclips) + addModelClip(mModelClip, k); + // If the source is live, then notify the children + if (this.source.isLive() || this.isInSetLive()) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) { + if (child instanceof GroupRetained && (child.source.isLive()||child.isInSetLive() )) + ((GroupRetained)child).processAddNodesForScopedModelClip( + mModelClip, list, k); + else if (child instanceof LinkRetained && (child.source.isLive()||child.isInSetLive() )) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + ((GroupRetained)(ln.sharedGroup)). + processAddNodesForScopedModelClip(mModelClip, list, + k.append("+").append(ln.nodeId)); + k.count = lastCount; + } else if (child instanceof Shape3DRetained && child.source.isLive()) { + ((Shape3DRetained)child).getMirrorObjects(list, k); + } else if (child instanceof MorphRetained && child.source.isLive()) { + ((MorphRetained)child).getMirrorObjects(list, k); + } + } + } + } + } + void removeAllNodesForScopedModelClip(ModelClipRetained mModelClip, ArrayList list, HashKey k) { + if (inSharedGroup) { + for (int i = 0; i < localToVworldKeys.length; i++) { + k.set(localToVworldKeys[i]); + processRemoveAllNodesForScopedModelClip(mModelClip, list, k); + } + } + else { + processRemoveAllNodesForScopedModelClip(mModelClip, list, k); + } + + } + + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + void processRemoveAllNodesForScopedModelClip(ModelClipRetained mModelClip, ArrayList list, HashKey k) { + // If the source is live, then notify the children + if (allocatedMclips) + removeModelClip(mModelClip, k); + if (this.source.isLive() && !isInClearLive) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) { + if (child instanceof GroupRetained &&(child.source.isLive() && + ! ((GroupRetained)child).isInClearLive)) + ((GroupRetained)child).processRemoveAllNodesForScopedModelClip(mModelClip, list, k); + else if (child instanceof LinkRetained && child.source.isLive()) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + ((GroupRetained)(ln.sharedGroup)). + processRemoveAllNodesForScopedModelClip(mModelClip, list, k.append("+"). + append(ln.nodeId)); + k.count = lastCount; + } else if (child instanceof Shape3DRetained && child.source.isLive() ) { + ((Shape3DRetained)child).getMirrorObjects(list, k); + } else if (child instanceof MorphRetained && child.source.isLive()) { + ((MorphRetained)child).getMirrorObjects(list, k); + } + } + } + } + } + + void addAllNodesForScopedAltApp(AlternateAppearanceRetained mAltApp, ArrayList list, HashKey k) { + if (inSharedGroup) { + for (int i = 0; i < localToVworldKeys.length; i++) { + k.set(localToVworldKeys[i]); + processAddNodesForScopedAltApp(mAltApp, list, k); + } + } + else { + processAddNodesForScopedAltApp(mAltApp, list, k); + } + } + + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + void processAddNodesForScopedAltApp(AlternateAppearanceRetained mAltApp, ArrayList list, HashKey k) { + // If the source is live, then notify the children + if (allocatedAltApps) + addAltApp(mAltApp, k); + if (this.source.isLive() || this.isInSetLive()) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) { + if (child instanceof GroupRetained && (child.source.isLive() || child.isInSetLive())) + ((GroupRetained)child).processAddNodesForScopedAltApp(mAltApp, list, k); + else if (child instanceof LinkRetained && child.source.isLive()) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + ((GroupRetained)(ln.sharedGroup)). + processAddNodesForScopedAltApp(mAltApp, list, k.append("+"). + append(ln.nodeId)); + k.count = lastCount; + } else if (child instanceof Shape3DRetained && child.source.isLive() ) { + ((Shape3DRetained)child).getMirrorObjects(list, k); + } else if (child instanceof MorphRetained && child.source.isLive()) { + ((MorphRetained)child).getMirrorObjects(list, k); + } + } + } + } + } + + void removeAllNodesForScopedAltApp(AlternateAppearanceRetained mAltApp, ArrayList list, HashKey k) { + if (inSharedGroup) { + for (int i = 0; i < localToVworldKeys.length; i++) { + k.set(localToVworldKeys[i]); + processRemoveNodesForScopedAltApp(mAltApp, list, k); + } + } + else { + processAddNodesForScopedAltApp(mAltApp, list, k); + } + } + + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + void processRemoveNodesForScopedAltApp(AlternateAppearanceRetained mAltApp, ArrayList list, HashKey k) { + // If the source is live, then notify the children + if (allocatedAltApps) + removeAltApp(mAltApp, k); + if (this.source.isLive() && !isInClearLive) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) { + if (child instanceof GroupRetained &&(child.source.isLive() && + ! ((GroupRetained)child).isInClearLive)) + ((GroupRetained)child).processRemoveNodesForScopedAltApp(mAltApp, list, k); + else if (child instanceof LinkRetained && child.source.isLive()) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + ((GroupRetained)(ln.sharedGroup)). + processRemoveNodesForScopedAltApp(mAltApp, list, k.append("+"). + append(ln.nodeId)); + k.count = lastCount; + } else if (child instanceof Shape3DRetained && child.source.isLive() ) { + ((Shape3DRetained)child).getMirrorObjects(list, k); + } else if (child instanceof MorphRetained && child.source.isLive()) { + ((MorphRetained)child).getMirrorObjects(list, k); + } + } + } + } + } + + synchronized void setLightScope() { + // Make group's own copy + ArrayList newLights; + if (!allocatedLights) { + allocatedLights = true; + if (lights != null) { + newLights = new ArrayList(lights.size()); + int size = lights.size(); + for (int i = 0; i < size; i++) { + ArrayList l = (ArrayList)lights.get(i); + if (l != null) { + newLights.add(l.clone()); + } + else { + newLights.add(null); + } + } + } + else { + if (inSharedGroup) { + newLights = new ArrayList(); + for (int i = 0; i < localToVworldKeys.length; i++) { + newLights.add(new ArrayList()); + } + } + else { + newLights = new ArrayList(); + newLights.add(new ArrayList()); + } + } + lights = newLights; + + } + scopingRefCount++; + } + synchronized void removeLightScope() { + scopingRefCount--; + } + + + synchronized void setFogScope() { + // Make group's own copy + ArrayList newFogs; + if (!allocatedFogs) { + allocatedFogs = true; + if (fogs != null) { + newFogs = new ArrayList(fogs.size()); + int size = fogs.size(); + for (int i = 0; i < size; i++) { + ArrayList l = (ArrayList)fogs.get(i); + if (l != null) { + newFogs.add(l.clone()); + } + else { + newFogs.add(null); + } + } + } + else { + if (inSharedGroup) { + newFogs = new ArrayList(); + for (int i = 0; i < localToVworldKeys.length; i++) { + newFogs.add(new ArrayList()); + }; + } + else { + newFogs = new ArrayList(); + newFogs.add(new ArrayList()); + } + } + fogs = newFogs; + + } + scopingRefCount++; + } + synchronized void removeFogScope() { + scopingRefCount--; + } + + + synchronized void setMclipScope() { + // Make group's own copy + ArrayList newMclips; + if (!allocatedMclips) { + allocatedMclips = true; + if (modelClips != null) { + newMclips = new ArrayList(modelClips.size()); + int size = modelClips.size(); + for (int i = 0; i < size; i++) { + ArrayList l = (ArrayList)modelClips.get(i); + if (l != null) { + newMclips.add(l.clone()); + } + else { + newMclips.add(null); + } + } + } + else { + if (inSharedGroup) { + newMclips =new ArrayList(); + for (int i = 0; i < localToVworldKeys.length; i++) { + newMclips.add(new ArrayList()); + } + } + else { + newMclips = new ArrayList(); + newMclips.add(new ArrayList()); + } + } + modelClips = newMclips; + + } + scopingRefCount++; + } + synchronized void removeMclipScope() { + scopingRefCount--; + } + + + synchronized void setAltAppScope() { + // Make group's own copy + ArrayList newAltApps; + if (!allocatedAltApps) { + allocatedAltApps = true; + if (altAppearances != null) { + newAltApps = new ArrayList(altAppearances.size()); + int size = altAppearances.size(); + for (int i = 0; i < size; i++) { + ArrayList l = (ArrayList)altAppearances.get(i); + if (l != null) { + newAltApps.add(l.clone()); + } + else { + newAltApps.add(null); + } + } + } + else { + if (inSharedGroup) { + newAltApps = new ArrayList(); + for (int i = 0; i < localToVworldKeys.length; i++) { + newAltApps.add(new ArrayList()); + } + } + else { + newAltApps = new ArrayList(); + newAltApps.add(new ArrayList()); + } + } + altAppearances = newAltApps; + + } + scopingRefCount++; + } + + synchronized void removeAltAppScope() { + scopingRefCount--; + } + + + synchronized boolean usedInScoping() { + return (scopingRefCount > 0); + } + + // Add a light to the list of lights + void addLight(LightRetained[] addlight, int numLgts, HashKey key) { + ArrayList l; + if (inSharedGroup) { + int hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + l = (ArrayList)lights.get(hkIndex); + if (l != null) { + for (int i = 0; i < numLgts; i++) { + l.add(addlight[i]); + } + } + } + else { + l = (ArrayList)lights.get(0); + for (int i = 0; i < numLgts; i++) { + l.add(addlight[i]); + } + } + + } + // Add a fog to the list of fogs + void addFog(FogRetained fog, HashKey key) { + ArrayList l; + if (inSharedGroup) { + int hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + l = (ArrayList)fogs.get(hkIndex); + if (l != null) { + l.add(fog); + } + } + else { + l = (ArrayList)fogs.get(0); + l.add(fog); + } + + } + + // Add a ModelClip to the list of ModelClip + void addModelClip(ModelClipRetained modelClip, HashKey key) { + ArrayList l; + if (inSharedGroup) { + int hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + l = (ArrayList)modelClips.get(hkIndex); + if (l != null) { + l.add(modelClip); + } + } + else { + l = (ArrayList)modelClips.get(0); + l.add(modelClip); + } + + } + // Add a alt appearance to the list of alt appearance + void addAltApp(AlternateAppearanceRetained altApp, HashKey key) { + ArrayList l; + if (inSharedGroup) { + int hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + l = (ArrayList)altAppearances.get(hkIndex); + if (l != null) { + l.add(altApp); + } + } + else { + l = (ArrayList)altAppearances.get(0); + l.add(altApp); + } + + } + + + // Remove a fog from the list of fogs + void removeFog(FogRetained fog, HashKey key) { + ArrayList l; + int index; + if (inSharedGroup) { + int hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + l = (ArrayList)fogs.get(hkIndex); + if (l != null) { + index = l.indexOf(fog); + l.remove(index); + } + } + else { + l = (ArrayList)fogs.get(0); + index = l.indexOf(fog); + l.remove(index); + } + + } + + + // Remove a ModelClip from the list of ModelClip + void removeModelClip(ModelClipRetained modelClip, HashKey key) { + ArrayList l; + int index; + if (inSharedGroup) { + int hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + l = (ArrayList)modelClips.get(hkIndex); + if (l != null) { + index = l.indexOf(modelClip); + l.remove(index); + } + } + else { + l = (ArrayList)modelClips.get(0); + index = l.indexOf(modelClip); + l.remove(index); + } + } + + + + // Remove a fog from the list of alt appearance + void removeAltApp(AlternateAppearanceRetained altApp, HashKey key) { + ArrayList l; + int index; + if (inSharedGroup) { + int hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + l = (ArrayList)altAppearances.get(hkIndex); + if (l != null) { + index = l.indexOf(altApp); + l.remove(index); + } + } + else { + l = (ArrayList)altAppearances.get(0); + index = l.indexOf(altApp); + l.remove(index); + } + + } + + + void updatePickable(HashKey keys[], boolean pick[]) { + int nchild = children.size()-1; + super.updatePickable(keys, pick); + int i=0; + NodeRetained child; + + for (i = 0; i < nchild; i++) { + child = (NodeRetained)children.get(i); + if(child != null) + child.updatePickable(keys, (boolean []) pick.clone()); + } + // No need to clone for the last value + + child = (NodeRetained)children.get(i); + if(child != null) + child.updatePickable(keys, pick); + + } + + + void updateCollidable(HashKey keys[], boolean collide[]) { + int nchild = children.size()-1; + super.updateCollidable(keys, collide); + int i=0; + NodeRetained child; + + for (i = 0; i < nchild; i++) { + child = (NodeRetained)children.get(i); + if(child != null) + child.updateCollidable(keys, (boolean []) collide.clone()); + } + // No need to clone for the last value + child = (NodeRetained)children.get(i); + if(child != null) + child.updateCollidable(keys, collide); + } + + void setAlternateCollisionTarget(boolean target) { + if (collisionTarget == target) + return; + + collisionTarget = target; + + if (source.isLive()) { + // Notify parent TransformGroup to add itself + // Since we want to update collisionVwcBounds when + // transform change in TransformStructure. + TransformGroupRetained tg; + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.threads = J3dThread.UPDATE_GEOMETRY; + message.universe = universe; + // send message to GeometryStructure to add/remove this + // group node in BHTree as AlternateCollisionTarget + + int numPath; + CachedTargets newCtArr[] = null; + + if (target) { + createMirrorGroup(); + + TargetsInterface ti = getClosestTargetsInterface( + TargetsInterface.TRANSFORM_TARGETS); + if (ti != null) { + + // update targets + CachedTargets ct; + Targets targets = new Targets(); + numPath = mirrorGroup.size(); + newCtArr = new CachedTargets[numPath]; + for (int i=0; i<numPath; i++) { + ct = ti.getCachedTargets(TargetsInterface.TRANSFORM_TARGETS, i, -1); + if (ct != null) { + targets.addNode((NnuId)mirrorGroup.get(i), + Targets.GRP_TARGETS); + newCtArr[i] = targets.snapShotAdd(ct); + } else { + newCtArr[i] = null; + } + } + + + // update target threads and propagate change to above + // nodes in scene graph + ti.updateTargetThreads(TargetsInterface.TRANSFORM_TARGETS, + newCtArr); + ti.resetCachedTargets(TargetsInterface.TRANSFORM_TARGETS, + newCtArr, -1); + } + + message.type = J3dMessage.INSERT_NODES; + message.args[0] = mirrorGroup.toArray(); + message.args[1] = ti; + message.args[2] = newCtArr; + + } else { + TargetsInterface ti = + getClosestTargetsInterface(TargetsInterface.TRANSFORM_TARGETS); + if (ti != null) { + + // update targets + Targets targets = new Targets(); + CachedTargets ct; + numPath = mirrorGroup.size(); + newCtArr = new CachedTargets[numPath]; + for (int i=0; i<numPath; i++) { + ct = ti.getCachedTargets(TargetsInterface.TRANSFORM_TARGETS, i, -1); + if (ct != null) { + targets.addNode((NnuId)mirrorGroup.get(i), + Targets.GRP_TARGETS); + //Note snapShotRemove calls targets.clearNode() + newCtArr[i] = targets.snapShotRemove(ct); + } else { + newCtArr[i] = null; + } + } + // update target threads and propagate change to above + // nodes in scene graph + ti.updateTargetThreads(TargetsInterface.TRANSFORM_TARGETS, + newCtArr); + ti.resetCachedTargets(TargetsInterface.TRANSFORM_TARGETS, + newCtArr, -1); + } + + message.type = J3dMessage.REMOVE_NODES; + message.args[0] = mirrorGroup.toArray(); + message.args[1] = ti; + message.args[2] = newCtArr; + mirrorGroup = null; // for gc + } + VirtualUniverse.mc.processMessage(message); + } + } + + boolean getAlternateCollisionTarget() { + return collisionTarget; + } + + + + /** + * This checks is setLive needs to be called. If it does, it gets the + * needed info and calls it. + */ + void checkSetLive(NodeRetained child, int childIndex, J3dMessage messages[], + int messageIndex, NodeRetained linkNode) { + checkSetLive(child, childIndex, localToVworldKeys, inSharedGroup, + messages, messageIndex, linkNode); + } + + + /** + * This checks is setLive needs to be called. If it does, it gets the + * needed info and calls it. + */ + void checkSetLive(NodeRetained child, int childIndex, HashKey keys[], + boolean isShared, J3dMessage messages[], + int messageIndex, NodeRetained linkNode) { + + SceneGraphObject me = this.source; + SetLiveState s; + J3dMessage createMessage; + boolean sendMessages = false; + boolean sendOGMessage = true; + boolean sendVSGMessage = true; + + if (me.isLive()) { + + s = universe.setLiveState; + s.reset(locale); + s.refCount = refCount; + s.inSharedGroup = isShared; + s.inBackgroundGroup = inBackgroundGroup; + s.inViewSpecificGroup = inViewSpecificGroup; + s.geometryBackground = geometryBackground; + s.keys = keys; + s.viewLists = viewLists; + s.parentBranchGroupPaths = branchGroupPaths; + // Note that there is no need to clone individual + // branchGroupArray since they will get replace (not append) + // by creating a new reference in child's group. + s.branchGroupPaths = (ArrayList) branchGroupPaths.clone(); + s.orderedPaths = orderedPaths; + + // Make the scoped fogs and lights of the child to include, the + // the scoped fog of this group + s.lights = lights; + s.altAppearances = altAppearances; + s.fogs = fogs; + s.modelClips = modelClips; + + boolean pick[]; + boolean collide[]; + + if (!inSharedGroup) { + pick = new boolean[1]; + collide = new boolean[1]; + } else { + pick = new boolean[localToVworldKeys.length]; + collide = new boolean[localToVworldKeys.length]; + } + findPickableFlags(pick); + super.updatePickable(null, pick); + s.pickable = pick; + + findCollidableFlags(collide); + super.updateCollidable(null, collide); + s.collidable = collide; + + TargetsInterface transformInterface, switchInterface; + transformInterface = initTransformStates(s, true); + switchInterface = initSwitchStates(s, this, child, linkNode, true); + + + if (s.inViewSpecificGroup && + (s.changedViewGroup == null)) { + s.changedViewGroup = new ArrayList(); + s.changedViewList = new ArrayList(); + s.keyList = new int[10]; + s.viewScopedNodeList = new ArrayList(); + s.scopedNodesViewList = new ArrayList(); + } + + childCheckSetLive(child, childIndex, s, linkNode); + + CachedTargets[] newCtArr = null; + newCtArr = updateTransformStates(s, transformInterface, true); + updateSwitchStates(s, switchInterface, true); + + // We're sending multiple messages in the call, inorder to + // have all these messages to be process as an atomic operation. + // We need to create an array of messages to MasterControl, this + // will ensure that all these messages will get the same time stamp. + + // If it is called from "moveTo", messages is not null. + if (messages == null) { + int numMessages = 2; + if(s.ogList.size() > 0) { + numMessages++; + } + else { + sendOGMessage = false; + } + if(s.changedViewGroup != null) { + numMessages++; + } + else { + sendVSGMessage = false; + } + + messages = new J3dMessage[numMessages]; + messageIndex = 0; + for(int mIndex=0; mIndex < numMessages; mIndex++) { + messages[mIndex] = VirtualUniverse.mc.getMessage(); + } + sendMessages = true; + } + + if(sendOGMessage) { + createMessage = messages[messageIndex++]; + createMessage.threads = J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.type = J3dMessage.ORDERED_GROUP_INSERTED; + createMessage.universe = universe; + createMessage.args[0] = s.ogList.toArray(); + createMessage.args[1] = s.ogChildIdList.toArray(); + createMessage.args[2] = s.ogOrderedIdList.toArray(); + createMessage.args[3] = s.ogCIOList.toArray(); + createMessage.args[4] = s.ogCIOTableList.toArray(); + } + + + if(sendVSGMessage) { + createMessage = messages[messageIndex++]; + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.type = J3dMessage.VIEWSPECIFICGROUP_INIT; + createMessage.universe = universe; + createMessage.args[0] = s.changedViewGroup; + createMessage.args[1] = s.changedViewList; + createMessage.args[2] = s.keyList; + } + + createMessage = messages[messageIndex++]; + createMessage.threads = s.notifyThreads; + createMessage.type = J3dMessage.INSERT_NODES; + createMessage.universe = universe; + createMessage.args[0] = s.nodeList.toArray(); + if (newCtArr != null) { + createMessage.args[1] = transformInterface; + createMessage.args[2] = newCtArr; + } else { + createMessage.args[1] = null; + createMessage.args[2] = null; + } + + if (s.viewScopedNodeList != null) { + createMessage.args[3] = s.viewScopedNodeList; + createMessage.args[4] = s.scopedNodesViewList; + } + + // execute user behavior's initialize methods + int sz = s.behaviorNodes.size(); + + for (int i=0; i < sz; i++) { + BehaviorRetained b; + b = (BehaviorRetained)s.behaviorNodes.get(i); + b.executeInitialize(); + } + + s.behaviorNodes.clear(); + + createMessage = messages[messageIndex++]; + + createMessage.threads = J3dThread.UPDATE_BEHAVIOR; + createMessage.type = J3dMessage.BEHAVIOR_ACTIVATE; + createMessage.universe = universe; + + if (sendMessages == true) { + VirtualUniverse.mc.processMessage(messages); + } + + if (nodeType == NodeRetained.SWITCH) { + // force reEvaluation of switch children + SwitchRetained sw = (SwitchRetained)this; + sw.setWhichChild(sw.whichChild, true); + } + + //Reset SetLiveState to free up memory. + s.reset(null); + } + } + + + + void checkClearLive(NodeRetained child, + J3dMessage messages[], int messageIndex, + int childIndex, NodeRetained linkNode) { + checkClearLive(child, localToVworldKeys, inSharedGroup, + messages, messageIndex, childIndex, linkNode); + } + + + + /** + * This checks if clearLive needs to be called. If it does, it gets the + * needed info and calls it. + */ + void checkClearLive(NodeRetained child, HashKey keys[], + boolean isShared, + J3dMessage messages[], int messageIndex, + int childIndex, NodeRetained linkNode) { + + SceneGraphObject me = this.source; + J3dMessage destroyMessage; + boolean sendMessages = false; + boolean sendOGMessage = true; + boolean sendVSGMessage = true; + + int i, j; + TransformGroupRetained tg; + + if (me.isLive()) { + SetLiveState s = universe.setLiveState; + + s.reset(locale); + s.refCount = refCount; + s.inSharedGroup = isShared; + s.inBackgroundGroup = inBackgroundGroup; + s.inViewSpecificGroup = inViewSpecificGroup; + s.keys = keys; + s.fogs = fogs; + s.lights = lights; + s.altAppearances = altAppearances; + s.modelClips = modelClips; + + + if (this instanceof OrderedGroupRetained && linkNode == null) { + // set this regardless of refCount + s.ogList.add(this); + s.ogChildIdList.add(new Integer(childIndex)); + s.ogCIOList.add(this); + int[] newArr = null; + OrderedGroupRetained og = (OrderedGroupRetained)this; + if(og.userChildIndexOrder != null) { + newArr = new int[og.userChildIndexOrder.length]; + System.arraycopy(og.userChildIndexOrder, 0, newArr, + 0, og.userChildIndexOrder.length); + } + s.ogCIOTableList.add(newArr); + + } + + if (!(this instanceof ViewSpecificGroupRetained)) { + s.viewLists = viewLists; + } + + TargetsInterface transformInterface, switchInterface; + transformInterface = initTransformStates(s, false); + switchInterface = initSwitchStates(s, this, child, linkNode, false); + + child.clearLive(s); + + CachedTargets[] newCtArr = null; + newCtArr = updateTransformStates(s, transformInterface, false); + updateSwitchStates(s, switchInterface, false); + + // We're sending multiple messages in the call, inorder to + // have all these messages to be process as an atomic operation. + // We need to create an array of messages to MasterControl, this + // will ensure that all these messages will get the same time stamp. + + // If it is called from "moveTo", messages is not null. + if (messages == null) { + int numMessages = 1; + if(s.ogList.size() > 0) { + numMessages++; + } + else { + sendOGMessage = false; + } + + if(s.changedViewGroup != null) { + numMessages++; + } + else { + sendVSGMessage = false; + } + + messages = new J3dMessage[numMessages]; + messageIndex = 0; + for(int mIndex=0; mIndex < numMessages; mIndex++) { + messages[mIndex] = VirtualUniverse.mc.getMessage(); + } + sendMessages = true; + } + + if(sendOGMessage) { + destroyMessage = messages[messageIndex++]; + destroyMessage.threads = J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_RENDERING_ENVIRONMENT; + destroyMessage.type = J3dMessage.ORDERED_GROUP_REMOVED; + destroyMessage.universe = universe; + destroyMessage.args[0] = s.ogList.toArray(); + destroyMessage.args[1] = s.ogChildIdList.toArray(); + destroyMessage.args[3] = s.ogCIOList.toArray(); + destroyMessage.args[4] = s.ogCIOTableList.toArray(); + } + + if(sendVSGMessage) { + destroyMessage = messages[messageIndex++]; + destroyMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + destroyMessage.type = J3dMessage.VIEWSPECIFICGROUP_CLEAR; + destroyMessage.universe = universe; + destroyMessage.args[0] = s.changedViewGroup; + destroyMessage.args[1] = s.keyList; + } + + destroyMessage = messages[messageIndex++]; + destroyMessage.threads = s.notifyThreads; + destroyMessage.type = J3dMessage.REMOVE_NODES; + destroyMessage.universe = universe; + destroyMessage.args[0] = s.nodeList.toArray(); + + if (newCtArr != null) { + destroyMessage.args[1] = transformInterface; + destroyMessage.args[2] = newCtArr; + } else { + destroyMessage.args[1] = null; + destroyMessage.args[2] = null; + } + if (s.viewScopedNodeList != null) { + destroyMessage.args[3] = s.viewScopedNodeList; + destroyMessage.args[4] = s.scopedNodesViewList; + } + if (sendMessages == true) { + VirtualUniverse.mc.processMessage(messages); + } + + s.reset(null); // for GC + } + } + + TargetsInterface initTransformStates(SetLiveState s, boolean isSetLive) { + + int numPaths = (inSharedGroup)? s.keys.length : 1; + TargetsInterface ti = getClosestTargetsInterface( + TargetsInterface.TRANSFORM_TARGETS); + + + if (isSetLive) { + s.currentTransforms = localToVworld; + s.currentTransformsIndex = localToVworldIndex; + s.localToVworldKeys = localToVworldKeys; + s.localToVworld = s.currentTransforms; + s.localToVworldIndex = s.currentTransformsIndex; + + s.parentTransformLink = parentTransformLink; + if (parentTransformLink != null) { + if (parentTransformLink instanceof TransformGroupRetained) { + TransformGroupRetained tg; + tg = (TransformGroupRetained) parentTransformLink; + s.childTransformLinks = tg.childTransformLinks; + } else { + SharedGroupRetained sg; + sg = (SharedGroupRetained) parentTransformLink; + s.childTransformLinks = sg.childTransformLinks; + } + } + } + + int transformLevels[] = new int[numPaths]; + findTransformLevels(transformLevels); + s.transformLevels = transformLevels; + + if (ti != null) { + Targets[] newTargets = new Targets[numPaths]; + for(int i=0; i<numPaths; i++) { + if (s.transformLevels[i] >= 0) { + newTargets[i] = new Targets(); + } else { + newTargets[i] = null; + } + } + s.transformTargets = newTargets; + + // TODO - optimization for targetThreads computation, require + // cleanup in GroupRetained.doSetLive() + //s.transformTargetThreads = 0; + } + + return ti; + } + + CachedTargets[] updateTransformStates(SetLiveState s, + TargetsInterface ti, boolean isSetLive) { + CachedTargets[] newCtArr = null; + + if (ti != null) { + if (isSetLive) { + CachedTargets ct; + int newTargetThreads = 0; + int hkIndex; + + newCtArr = new CachedTargets[localToVworld.length]; + + // update targets + if (! inSharedGroup) { + if (s.transformTargets[0] != null) { + ct = ti.getCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, 0, -1); + if (ct != null) { + newCtArr[0] = s.transformTargets[0].snapShotAdd(ct); + } + } else { + newCtArr[0] = null; + } + } else { + for (int i=0; i<s.keys.length; i++) { + + if (s.transformTargets[i] != null) { + ct = ti.getCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, i, -1); + if (ct != null) { + newCtArr[i] = + s.transformTargets[i].snapShotAdd(ct); + } + } else { + newCtArr[i] = null; + } + } + } + } else { + + CachedTargets ct; + int hkIndex; + + newCtArr = new CachedTargets[localToVworld.length]; + + if (! inSharedGroup) { + if (s.transformTargets[0] != null) { + ct = ti.getCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, 0, -1); + if (ct != null) { + newCtArr[0] = + s.transformTargets[0].snapShotRemove(ct); + } + } else { + newCtArr[0] = null; + } + } else { + for (int i=0; i<s.keys.length; i++) { + if (s.transformTargets[i] != null) { + ct = ti.getCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, i, -1); + if (ct != null) { + newCtArr[i] = + s.transformTargets[i].snapShotRemove(ct); + } + } else { + newCtArr[i] = null; + } + } + } + } + // update target threads and propagate change to above + // nodes in scene graph + + ti.updateTargetThreads(TargetsInterface.TRANSFORM_TARGETS, + newCtArr); + ti.resetCachedTargets(TargetsInterface.TRANSFORM_TARGETS, + newCtArr, -1); + + } + return newCtArr; + } + + TargetsInterface initSwitchStates(SetLiveState s, + NodeRetained parentNode, NodeRetained childNode, + NodeRetained linkNode, boolean isSetLive) { + NodeRetained child; + NodeRetained parent; + int i,j; + + findSwitchInfo(s, parentNode, childNode, linkNode); + TargetsInterface ti = getClosestTargetsInterface( + TargetsInterface.SWITCH_TARGETS); + if (ti != null) { + Targets[] newTargets = null; + int numPaths = (inSharedGroup)? s.keys.length : 1; + newTargets = new Targets[numPaths]; + for(i=0; i<numPaths; i++) { + if (s.switchLevels[i] >= 0) { + newTargets[i] = new Targets(); + } else { + newTargets[i] = null; + } + } + s.switchTargets = newTargets; + } + + if (isSetLive) { + // set switch states + if (nodeType == NodeRetained.SWITCH) { + i = parentSwitchLinkChildIndex; + s.childSwitchLinks = (ArrayList)childrenSwitchLinks.get(i); + s.parentSwitchLink = this; + + } else { + if (nodeType == NodeRetained.SHAREDGROUP) { + i = parentSwitchLinkChildIndex; + s.childSwitchLinks = (ArrayList)childrenSwitchLinks.get(i); + s.parentSwitchLink = this; + + } else { + s.parentSwitchLink = parentSwitchLink; + if (parentSwitchLink != null) { + i = parentSwitchLinkChildIndex; + s.childSwitchLinks = (ArrayList) + parentSwitchLink.childrenSwitchLinks.get(i); + } + } + } + if (ti != null) { + s.switchStates = ti.getTargetsData( + TargetsInterface.SWITCH_TARGETS, + parentSwitchLinkChildIndex); + } else { + s.switchStates = new ArrayList(1); + s.switchStates.add(new SwitchState(false)); + } + } + return ti; + } + + void updateSwitchStates(SetLiveState s, TargetsInterface ti, + boolean isSetLive) { + + // update switch leaves's compositeSwitchMask for ancestors + // and update switch leaves' switchOn flag if at top level switch + + if (ti != null) { + if (isSetLive) { + CachedTargets[] newCtArr = null; + CachedTargets ct; + + newCtArr = new CachedTargets[localToVworld.length]; + + // update targets + if (! inSharedGroup) { + + if (s.switchTargets[0] != null) { + ct = ti.getCachedTargets( + TargetsInterface.SWITCH_TARGETS, 0, + parentSwitchLinkChildIndex); + if (ct != null) { + newCtArr[0] = s.switchTargets[0].snapShotAdd(ct); + } else { + newCtArr[0] = s.switchTargets[0].snapShotInit(); + } + } else { + newCtArr[0] = null; + } + } else { + for (int i=0; i<s.keys.length; i++) { + if (s.switchTargets[i] != null) { + ct = ti.getCachedTargets( + TargetsInterface.SWITCH_TARGETS, i, + parentSwitchLinkChildIndex); + if (ct != null) { + newCtArr[i] = + s.switchTargets[i].snapShotAdd(ct); + } else { + newCtArr[i] = + s.switchTargets[i].snapShotInit(); + } + } else { + newCtArr[i] = null; + } + } + } + ti.resetCachedTargets(TargetsInterface.SWITCH_TARGETS, + newCtArr, parentSwitchLinkChildIndex); + if (ti instanceof SwitchRetained) { + ((SwitchRetained)ti).traverseSwitchParent(); + } else if (ti instanceof SharedGroupRetained) { + ((SharedGroupRetained)ti).traverseSwitchParent(); + } + } else { + CachedTargets ct; + + CachedTargets[] newCtArr = + new CachedTargets[localToVworld.length]; + + if (! inSharedGroup) { + if (s.switchTargets[0] != null) { + ct = ti.getCachedTargets( + TargetsInterface.SWITCH_TARGETS, 0, + parentSwitchLinkChildIndex); + if (ct != null) { + newCtArr[0] = + s.switchTargets[0].snapShotRemove(ct); + } + } else { + newCtArr[0] = null; + } + } else { + for (int i=0; i<s.keys.length; i++) { + if (s.switchTargets[i] != null) { + ct = ti.getCachedTargets( + TargetsInterface.SWITCH_TARGETS, i, + parentSwitchLinkChildIndex); + + if (ct != null) { + newCtArr[i] = + s.switchTargets[i].snapShotRemove(ct); + } + } else { + newCtArr[i] = null; + } + } + } + ti.resetCachedTargets(TargetsInterface.SWITCH_TARGETS, + newCtArr, parentSwitchLinkChildIndex); + } + } + } + + void appendChildrenData() { + } + void insertChildrenData(int index) { + } + void removeChildrenData(int index) { + } + + TargetsInterface getClosestTargetsInterface(int type) { + return (type == TargetsInterface.TRANSFORM_TARGETS)? + (TargetsInterface)parentTransformLink: + (TargetsInterface)parentSwitchLink; + } + + + synchronized void updateLocalToVworld() { + NodeRetained child; + + // For each children call ..... + for (int i=children.size()-1; i>=0; i--) { + child = (NodeRetained)children.get(i); + if(child != null) + child.updateLocalToVworld(); + } + } + + void setNodeData(SetLiveState s) { + super.setNodeData(s); + orderedPaths = s.orderedPaths; + } + + void removeNodeData(SetLiveState s) { + + if((!inSharedGroup) || (s.keys.length == localToVworld.length)) { + orderedPaths = null; + } + else { + // Set it back to its parent localToVworld data. This is b/c the + // parent has changed it localToVworld data arrays. + orderedPaths = s.orderedPaths; + } + super.removeNodeData(s); + } + + + + void setLive(SetLiveState s) { + doSetLive(s); + super.markAsLive(); + } + + // Note that SwitchRetained, OrderedGroupRetained and SharedGroupRetained + // override this method + void childDoSetLive(NodeRetained child, int childIndex, SetLiveState s) { + if(child!=null) + child.setLive(s); + } + + // Note that BranchRetained, OrderedGroupRetained and SharedGroupRetained + // TransformGroupRetained override this method + void childCheckSetLive(NodeRetained child, int childIndex, + SetLiveState s, NodeRetained linkNode) { + child.setLive(s); + } + + /** + * This version of setLive calls setLive on all of its chidren. + */ + void doSetLive(SetLiveState s) { + int i, nchildren; + + BoundingSphere boundingSphere = new BoundingSphere(); + NodeRetained child; + super.doSetLive(s); + locale = s.locale; + + inViewSpecificGroup = s.inViewSpecificGroup; + nchildren = children.size(); + ArrayList savedScopedLights = s.lights; + ArrayList savedScopedFogs = s.fogs; + ArrayList savedScopedAltApps = s.altAppearances; + ArrayList savedScopedMclips = s.modelClips; + + boolean oldpickableArray[] = (boolean []) s.pickable.clone(); + boolean oldcollidableArray[] = (boolean []) s.collidable.clone(); + boolean workingpickableArray[] = new boolean[oldpickableArray.length]; + boolean workingcollidableArray[] = new boolean[oldcollidableArray.length]; + ArrayList oldBranchGroupPaths = s.branchGroupPaths; + setScopingInfo(s); + + + if (!(this instanceof ViewSpecificGroupRetained)) { + viewLists = s.viewLists; + } + + for (i=0; i<nchildren; i++) { + child = (NodeRetained)children.get(i); + + // Restore old values before child.setLive(s) + System.arraycopy(oldpickableArray, 0, workingpickableArray, 0, + oldpickableArray.length); + System.arraycopy(oldcollidableArray, 0, workingcollidableArray, 0, + oldcollidableArray.length); + s.pickable = workingpickableArray; + s.collidable = workingcollidableArray; + // s.branchGroupPaths will be modified by child setLive() + // so we have to restore it every time. + s.parentBranchGroupPaths = branchGroupPaths; + s.branchGroupPaths = (ArrayList) oldBranchGroupPaths.clone(); + s.inViewSpecificGroup = inViewSpecificGroup; + childDoSetLive(child, i, s); + } + + + + if (collisionTarget) { + processCollisionTarget(s); + } + + s.lights = savedScopedLights; + s.fogs = savedScopedFogs; + s.altAppearances = savedScopedAltApps; + s.modelClips = savedScopedMclips; + + } + + void setScopingInfo(SetLiveState s) { + + int i, k, hkIndex; + // If this is a scoped group , then copy the parent's + // scoping info + if (allocatedLights) { + if (s.lights != null) { + // Add the parent's scoping info to this group + if (inSharedGroup) { + for (i=0; i < s.keys.length; i++) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + ArrayList l = (ArrayList)lights.get(hkIndex); + ArrayList src = (ArrayList)s.lights.get(i); + if (src != null) { + int size = src.size(); + for (k = 0; k < size; k++) { + l.add(src.get(k)); + } + } + + } + } + else { + ArrayList l = (ArrayList)lights.get(0); + ArrayList src = (ArrayList)s.lights.get(0); + int size = src.size(); + for (i = 0; i < size; i++) { + l.add(src.get(i)); + } + } + } + s.lights = lights; + } + else { + lights = s.lights; + } + + if (allocatedFogs) { + if (s.fogs != null) { + // Add the parent's scoping info to this group + if (inSharedGroup) { + for (i=0; i < s.keys.length; i++) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + ArrayList l = (ArrayList)fogs.get(hkIndex); + ArrayList src = (ArrayList)s.fogs.get(i); + if (src != null) { + int size = src.size(); + for (k = 0; k < size; k++) { + l.add(src.get(k)); + } + } + + } + } + else { + ArrayList l = (ArrayList)fogs.get(0); + ArrayList src = (ArrayList)s.fogs.get(0); + int size = src.size(); + for (i = 0; i < size; i++) { + l.add(src.get(i)); + } + } + } + s.fogs = fogs; + } + else { + fogs = s.fogs; + } + + if (allocatedMclips) { + if (s.modelClips != null) { + // Add the parent's scoping info to this group + if (inSharedGroup) { + for (i=0; i < s.keys.length; i++) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + ArrayList l = (ArrayList)modelClips.get(hkIndex); + ArrayList src = (ArrayList)s.modelClips.get(i); + if (src != null) { + int size = src.size(); + for (k = 0; k < size; k++) { + l.add(src.get(k)); + } + } + + } + } + else { + ArrayList l = (ArrayList)modelClips.get(0); + ArrayList src = (ArrayList)s.modelClips.get(0); + int size = src.size(); + for (i = 0; i < size; i++) { + l.add(src.get(i)); + } + } + } + s.modelClips = modelClips; + } + else { + modelClips = s.modelClips; + } + + if (allocatedAltApps) { + if (s.altAppearances != null) { + // Add the parent's scoping info to this group + if (inSharedGroup) { + for (i=0; i < s.keys.length; i++) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + ArrayList l = (ArrayList)altAppearances.get(hkIndex); + ArrayList src = (ArrayList)s.altAppearances.get(i); + if (src != null) { + int size = src.size(); + for (k = 0; k < size; k++) { + l.add(src.get(k)); + } + } + + } + } + else { + ArrayList l = (ArrayList)altAppearances.get(0); + ArrayList src = (ArrayList)s.altAppearances.get(0); + int size = src.size(); + for (i = 0; i < size; i++) { + l.add(src.get(i)); + } + } + } + s.altAppearances = altAppearances; + } + else { + altAppearances = s.altAppearances; + } + } + + void processCollisionTarget(SetLiveState s) { + + GroupRetained g; + if (mirrorGroup == null) { + mirrorGroup = new ArrayList(); + } + Bounds bound = (collisionBound != null ? + collisionBound : getEffectiveBounds()); + if (inSharedGroup) { + for (int i=0; i < s.keys.length; i++) { + int j; + g = new GroupRetained(); + g.key = s.keys[i]; + g.localToVworld = new Transform3D[1][]; + g.localToVworldIndex = new int[1][]; + + j = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + if(j < 0) { + System.out.println("GroupRetained : Can't find hashKey"); + } + + g.localToVworld[0] = localToVworld[j]; + g.localToVworldIndex[0] = localToVworldIndex[j]; + g.collisionVwcBounds = new BoundingBox(); + g.collisionVwcBounds.transform(bound, g.getCurrentLocalToVworld(0)); + g.sourceNode = this; + g.locale = locale; // need by getVisibleGeometryAtom() + mirrorGroup.add(g); + /* + System.out.println("processCollisionTarget mirrorGroup.add() : " + + g.getId() + " mirrorGroup.size() " + + mirrorGroup.size()); + */ + if (s.transformTargets != null && + s.transformTargets[i] != null) { + s.transformTargets[i].addNode(g, Targets.GRP_TARGETS); + } + s.nodeList.add(g); + } + } else { + g = new GroupRetained(); + g.localToVworld = new Transform3D[1][]; + g.localToVworldIndex = new int[1][]; + g.localToVworld[0] = localToVworld[0]; + g.localToVworldIndex[0] = localToVworldIndex[0]; + g.collisionVwcBounds = new BoundingBox(); + g.collisionVwcBounds.transform(bound, g.getCurrentLocalToVworld(0)); + g.sourceNode = this; + g.locale = locale; // need by getVisibleGeometryAtom() + mirrorGroup.add(g); + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(g, Targets.GRP_TARGETS); + } + s.nodeList.add(g); + } + } + + void computeCombineBounds(Bounds bounds) { + + if (boundsAutoCompute) { + for (int i=children.size()-1; i>=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) + child.computeCombineBounds(bounds); + } + } else { + // Should this be lock too ? ( MT safe ? ) + synchronized(localBounds) { + bounds.combine(localBounds); + } + } + } + + + /** + * Gets the bounding object of a node. + * @return the node's bounding object + */ + Bounds getBounds() { + + if ( boundsAutoCompute) { + BoundingSphere boundingSphere = new BoundingSphere(); + boundingSphere.setRadius(-1.0); + + for (int i=children.size()-1; i>=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + if(child != null) + child.computeCombineBounds((Bounds) boundingSphere); + } + + return (Bounds) boundingSphere; + } + return super.getBounds(); + } + + /** + * Gets the bounding object of a node. + * @return the node's bounding object + */ + Bounds getEffectiveBounds() { + if ( boundsAutoCompute) { + return getBounds(); + } + return super.getEffectiveBounds(); + } + + // returns true if children cannot be read/written + boolean isStaticChildren() { + if (source.getCapability(Group.ALLOW_CHILDREN_READ) || + source.getCapability(Group.ALLOW_CHILDREN_WRITE)) { + return false; + } + return true; + + } + + + boolean isStatic() { + return (super.isStatic() && isStaticChildren()); + } + + /** + * This compiles() a group + */ + void setCompiled() { + super.setCompiled(); + for (int i=children.size()-1; i>=0; i--) { + SceneGraphObjectRetained node = + (SceneGraphObjectRetained) children.get(i); + if (node != null) + node.setCompiled(); + } + } + + void traverse(boolean sameLevel, int level) { + SceneGraphObjectRetained node; + + if (!sameLevel) { + super.traverse(true, level); + + if (source.getCapability(Group.ALLOW_CHILDREN_READ)) { + System.out.print(" (r)"); + } else if (isStatic()) { + System.out.print(" (s)"); + } else if (source.getCapability(Group.ALLOW_CHILDREN_WRITE)) { + System.out.print(" (w)"); + } + } + + level++; + for (int i = 0; i < children.size(); i++) { + node = (SceneGraphObjectRetained) children.get(i); + if (node != null) { + node.traverse(false, level); + } + } + } + + void compile(CompileState compState) { + + SceneGraphObjectRetained node; + + super.compile(compState); + + mergeFlag = SceneGraphObjectRetained.MERGE; + + if (!isStatic()) { + compState.keepTG = true; + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + } + + if (isRoot || this.usedInScoping() || + (parent instanceof SwitchRetained)) { + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + } + + compiledChildrenList = new ArrayList(5); + + for (int i = 0; i < children.size(); i++) { + node = (SceneGraphObjectRetained) children.get(i); + if (node != null) { + node.compile(compState); + } + } + + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numGroups++; + } + } + + void merge(CompileState compState) { + + GroupRetained saveParentGroup = null; + SceneGraphObjectRetained node; + + if (mergeFlag != SceneGraphObjectRetained.MERGE_DONE) { + if (mergeFlag == SceneGraphObjectRetained.DONT_MERGE) { + + // don't merge/eliminate this node + super.merge(compState); + + saveParentGroup = compState.parentGroup; + compState.parentGroup = this; + } + + for (int i = 0; i < children.size(); i++) { + node = (SceneGraphObjectRetained) children.get(i); + if (node != null) { + node.merge(compState); + } + } + + if (compState.parentGroup == this) { + this.children = compiledChildrenList; + compState.doShapeMerge(); + compiledChildrenList = null; + compState.parentGroup = saveParentGroup; + } else { + // this group node can be eliminated + this.children.clear(); + + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numMergedGroups++; + } + } + + mergeFlag = SceneGraphObjectRetained.MERGE_DONE; + + } else { + if (compState.parentGroup != null) { + compState.parentGroup.compiledChildrenList.add(this); + parent = compState.parentGroup; + } + } + } + + /** + * This version of clearLive calls clearLive on all of its chidren. + */ + void clearLive(SetLiveState s) { + int i, k, hkIndex, nchildren; + NodeRetained child; + int parentScopedLtSize = 0; + int parentScopedFogSize = 0; + int parentScopedMcSize = 0; + int parentScopedAltAppSize = 0; + int groupScopedLtSize = 0; + int groupScopedFogSize = 0; + int groupScopedMcSize = 0; + int groupScopedAltAppSize = 0; + int size; + + isInClearLive = true; + + // Save this for later use in this method. Temporary. to be removed when OG cleanup. + HashKey[] savedLocalToVworldKeys = localToVworldKeys; + + super.clearLive(s); + + + nchildren = this.children.size(); + + if (!(this instanceof ViewSpecificGroupRetained)) { + viewLists = s.viewLists; + } + + ArrayList savedParentLights = s.lights; + if (allocatedLights) { + s.lights = lights; + } + + ArrayList savedParentFogs = s.fogs; + if (allocatedFogs) { + s.fogs = fogs; + } + + ArrayList savedParentMclips = s.modelClips; + if (allocatedMclips) { + s.modelClips = modelClips; + } + + + ArrayList savedParentAltApps = s.altAppearances; + if (allocatedAltApps) { + s.altAppearances = altAppearances; + } + + + for (i=nchildren-1; i >=0 ; i--) { + child = (NodeRetained)children.get(i); + if (this instanceof OrderedGroupRetained) { + OrderedGroupRetained og = (OrderedGroupRetained)this; + + // adjust refCount, which has been decremented + //in super.clearLive + if ((refCount+1) == s.refCount) { + //only need to do it once if in shared group. Add + //all the children to the list of OG_REMOVED message + s.ogList.add(this); + s.ogChildIdList.add(new Integer(i)); + } + s.orderedPaths = (ArrayList)og.childrenOrderedPaths.get(i); + } + + if (child != null) { + child.clearLive(s); + } + } + // Has its own copy + // TODO: Handle the case of + // was non-zero, gone to zero? + if (savedParentLights != null) { + if (allocatedLights) { + if (inSharedGroup) { + + for (i=0; i < s.keys.length; i++) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + ArrayList l = (ArrayList)savedParentLights.get(hkIndex); + ArrayList gl = (ArrayList)lights.get(hkIndex); + if (l != null) { + size = l.size(); + for (k = 0; k < size; k++) { + gl.remove(l.get(k)); + } + } + + } + } + else { + ArrayList l = (ArrayList)savedParentLights.get(0); + ArrayList gl = (ArrayList)lights.get(0); + size = l.size(); + for (int m = 0; m < size; m++) { + gl.remove(l.get(m)); + } + } + } + } + + if (savedParentFogs != null) { + if (allocatedFogs) { + if (inSharedGroup) { + for (i=0; i < s.keys.length; i++) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + ArrayList l = (ArrayList)savedParentFogs.get(hkIndex); + ArrayList gl = (ArrayList)fogs.get(hkIndex); + if (l != null) { + size = l.size(); + for (k = 0; k < size; k++) { + gl.remove(l.get(k)); + } + } + + } + } + else { + ArrayList l = (ArrayList)savedParentFogs.get(0); + size = l.size(); + for (int m = 0; m < size; m++) { + fogs.remove(l.get(m)); + } + } + } + } + + if (savedParentMclips != null) { + if (allocatedMclips) { + if (inSharedGroup) { + for (i=0; i < s.keys.length; i++) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + ArrayList l = (ArrayList)savedParentMclips.get(hkIndex); + ArrayList gl = (ArrayList)modelClips.get(hkIndex); + if (l != null) { + size = l.size(); + for (k = 0; k < size; k++) { + gl.remove(l.get(k)); + } + } + + } + } + else { + ArrayList l = (ArrayList)savedParentMclips.get(0); + size = l.size(); + for (int m = 0; m < size; m++) { + modelClips.remove(l.get(m)); + } + } + } + } + + if (savedParentAltApps != null) { + if (allocatedAltApps) { + if (inSharedGroup) { + for (i=0; i < s.keys.length; i++) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + ArrayList l = (ArrayList)savedParentAltApps.get(hkIndex); + ArrayList gl = (ArrayList)altAppearances.get(hkIndex); + if (l != null) { + size = l.size(); + for (k = 0; k < size; k++) { + gl.remove(l.get(k)); + } + } + + } + } + else { + ArrayList l = (ArrayList)savedParentAltApps.get(0); + size = l.size(); + for (int m = 0; m < size; m++) { + altAppearances.remove(l.get(m)); + } + } + } + } + + if (collisionTarget) { + GroupRetained g; + if (inSharedGroup) { + for (i=s.keys.length-1; i >=0; i--) { + HashKey hkey = s.keys[i]; + for (int j = mirrorGroup.size()-1; j >=0 ; j--) { + g = (GroupRetained) mirrorGroup.get(j); + if (g.key.equals(hkey)) { + s.nodeList.add(mirrorGroup.remove(j)); + if (s.transformTargets != null && + s.transformTargets[j] != null) { + s.transformTargets[j].addNode(g, Targets.GRP_TARGETS); + } + break; + } + + } + } + } else { + g = (GroupRetained)mirrorGroup.get(0); + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(g, Targets.GRP_TARGETS); + } + s.nodeList.add(mirrorGroup.remove(0)); + } + } + s.lights = savedParentLights; + s.modelClips = savedParentMclips; + s.fogs = savedParentFogs; + s.altAppearances = savedParentAltApps; + isInClearLive = false; + } + + // This is only used by alternateCollisionTarget + public BoundingBox computeBoundingHull() { + return collisionVwcBounds; + } + + // If isSwitchOn cached here, we don't need to traverse up the tree + public boolean isEnable() { + return isNodeSwitchOn(this.sourceNode, key); + } + + // If isSwitchOn cached here, we don't need to traverse up the tree + // This method does nothing with vis. + public boolean isEnable(int vis) { + return isNodeSwitchOn(this.sourceNode, key); + } + + // Can't use getLocale, it is used by BranchGroupRetained + public Locale getLocale2() { + return locale; + } + + /** + * Return true of nodeR is not under a switch group or + * nodeR is enable under a switch group. + */ + static boolean isNodeSwitchOn(NodeRetained node, HashKey key) { + NodeRetained prevNode = null; + if (key != null) { + key = new HashKey(key); + } + + synchronized (node.universe.sceneGraphLock) { + do { + if ((node instanceof SwitchRetained) && + (prevNode != null) && + !validSwitchChild((SwitchRetained) node, prevNode)) { + return false; + } + prevNode = node; + if (node instanceof SharedGroupRetained) { + // retrieve the last node ID + String nodeId = key.getLastNodeId(); + Vector parents = ((SharedGroupRetained) node).parents; + // find the matching link + for(int i=parents.size()-1; i >=0; i--) { + NodeRetained link = (NodeRetained) parents.get(i); + if (link.nodeId.equals(nodeId)) { + node = link; + break; + } + } + if (node == prevNode) { + // Fail to found a matching link, this is + // probably cause by BHTree not yet updated + // because message not yet arrive + // when collision so it return current node as target. + return false; + } + } else { + node = node.parent; + } + } while (node != null); + // reach locale + } + return true; + } + + + + /** + * Determinte if nodeR is a valid child to render for + * Switch Node swR. + */ + static boolean validSwitchChild(SwitchRetained sw, + NodeRetained node) { + + int whichChild = sw.whichChild; + + if (whichChild == Switch.CHILD_NONE) { + return false; + } + + if (whichChild == Switch.CHILD_ALL) { + return true; + } + + ArrayList children = sw.children; + + if (whichChild >= 0) { // most common case + return (children.get(whichChild) == node); + } + + // Switch.CHILD_MASK + for (int i=children.size()-1; i >=0; i--) { + if (sw.childMask.get(i) && + (children.get(i) == node)) { + return true; + } + } + return false; + } + + + /** + * Create mirror group when this Group AlternateCollisionTarget + * is set to true while live. + */ + void createMirrorGroup() { + GroupRetained g; + + mirrorGroup = new ArrayList(); + + Bounds bound = (collisionBound != null ? + collisionBound : getEffectiveBounds()); + + if (inSharedGroup) { + for (int i=0; i < localToVworldKeys.length; i++) { + g = new GroupRetained(); + g.key = localToVworldKeys[i]; + g.localToVworld = new Transform3D[1][]; + g.localToVworldIndex = new int[1][]; + g.localToVworld[0] = localToVworld[i]; + g.localToVworldIndex[0] = localToVworldIndex[i]; + g.collisionVwcBounds = new BoundingBox(); + g.collisionVwcBounds.transform(bound, g.getCurrentLocalToVworld()); + g.sourceNode = this; + g.locale = locale; // need by getVisibleGeometryAtom() + mirrorGroup.add(g); + } + } else { + g = new GroupRetained(); + g.localToVworld = new Transform3D[1][]; + g.localToVworldIndex = new int[1][]; + g.localToVworld[0] = localToVworld[0]; + g.localToVworldIndex[0] = localToVworldIndex[0]; + g.collisionVwcBounds = new BoundingBox(); + g.collisionVwcBounds.transform(bound, g.getCurrentLocalToVworld()); + g.sourceNode = this; + g.locale = locale; // need by getVisibleGeometryAtom() + mirrorGroup.add(g); + } + } + + void setBoundsAutoCompute(boolean autoCompute) { + if (autoCompute != boundsAutoCompute) { + super.setBoundsAutoCompute(autoCompute); + if (!autoCompute) { + localBounds = getEffectiveBounds(); + } + if (source.isLive() && collisionBound == null && autoCompute + && mirrorGroup != null) { + + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.COLLISION_BOUND_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY; + message.universe = universe; + message.args[0] = this; + VirtualUniverse.mc.processMessage(message); + } + } + } + + void setBounds(Bounds bounds) { + super.setBounds(bounds); + if (source.isLive() && !boundsAutoCompute && + collisionBound == null && mirrorGroup != null) { + + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.COLLISION_BOUND_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY; + message.universe = universe; + message.args[0] = this; + VirtualUniverse.mc.processMessage(message); + } + } + + + int[] processViewSpecificInfo(int mode, HashKey k, View v, ArrayList vsgList, int[] keyList, + ArrayList leafList) { + int nchildren = children.size(); + if (source.isLive()) { + for (int i = 0; i < nchildren; i++) { + NodeRetained child = (NodeRetained) children.get(i); + if (child instanceof LeafRetained) { + if (child instanceof LinkRetained) { + int lastCount = k.count; + LinkRetained ln = (LinkRetained) child; + if (k.count == 0) { + k.append(locale.nodeId); + } + keyList = ((GroupRetained)(ln.sharedGroup)). + processViewSpecificInfo(mode, k.append("+").append(ln.nodeId), v, vsgList, + keyList, leafList); + k.count = lastCount; + } + else { + ((LeafRetained)child).getMirrorObjects(leafList, k); + } + } else { + keyList = child.processViewSpecificInfo(mode, k, v, vsgList, keyList, leafList); + } + } + } + return keyList; + } + + void findSwitchInfo(SetLiveState s, NodeRetained parentNode, + NodeRetained childNode, NodeRetained linkNode) { + + NodeRetained child; + NodeRetained parent; + + parentSwitchLinkChildIndex = -1; + + // traverse up scene graph to find switch parent information + if (!inSharedGroup) { + child = (linkNode == null)? childNode: linkNode; + parent = parentNode; + while (parent != null) { + if (parent instanceof SwitchRetained) { + s.switchLevels[0]++; + if (s.closestSwitchParents[0] == null) { + s.closestSwitchParents[0] = (SwitchRetained)parent; + s.closestSwitchIndices[0] = + ((SwitchRetained)parent).switchIndexCount++; + } + if (parentSwitchLinkChildIndex == -1) { + parentSwitchLinkChildIndex = + ((GroupRetained)parent).children.indexOf(child); + } + } else if (parent instanceof SharedGroupRetained) { + if (parentSwitchLinkChildIndex == -1) { + parentSwitchLinkChildIndex = + ((GroupRetained)parent).children.indexOf(child); + } + } + child = parent; + parent = child.parent; + } + } else { + HashKey key; + int i,j; + + s.switchLevels = new int[localToVworldKeys.length]; + s.closestSwitchParents = + new SwitchRetained[localToVworldKeys.length]; + s.closestSwitchIndices = new int[localToVworldKeys.length]; + for (i=0; i<localToVworldKeys.length; i++) { + s.switchLevels[i] = -1; + s.closestSwitchParents[i] = null; + s.closestSwitchIndices[i] = -1; + } + + for (i=0; i < localToVworldKeys.length; i++) { + child = (linkNode == null)? childNode: linkNode; + parent = parentNode; + key = new HashKey(localToVworldKeys[i]); + + while (parent != null) { + + if (parent instanceof SwitchRetained) { + s.switchLevels[i]++; + if (s.closestSwitchParents[i] == null) { + s.closestSwitchParents[i] = (SwitchRetained)parent; + s.closestSwitchIndices[i] = + ((SwitchRetained)parent).switchIndexCount++; + + } + if (parentSwitchLinkChildIndex == -1) { + parentSwitchLinkChildIndex = + ((GroupRetained)parent).children.indexOf(child); + } + } else if (parent instanceof SharedGroupRetained) { + String nodeId = key.getLastNodeId(); + Vector parents = ((SharedGroupRetained) parent).parents; + NodeRetained ln; + + if (parentSwitchLinkChildIndex == -1) { + parentSwitchLinkChildIndex = + ((GroupRetained)parent).children.indexOf(child); + } + + for(j=0; j< parents.size(); j++) { + ln = (NodeRetained)parents.get(j); + if (ln.nodeId.equals(nodeId)) { + parent = ln; + break; + } + } + } + child = parent; + parent = child.parent; + } + } + } + } + + static void gatherBlUsers(ArrayList blUsers, Object[] blArr) { + ArrayList users; + + for (int i=0; i<blArr.length; i++) { + users = ((BoundingLeafRetained)blArr[i]).users; + synchronized(users) { + blUsers.addAll(users); + } + } + } + + // recursively found all geometryAtoms under this Group + void searchGeometryAtoms(UnorderList list) { + for (int i = children.size()-1; i >=0; i--) { + NodeRetained child = (NodeRetained)children.get(i); + child.searchGeometryAtoms(list); + } + } +} diff --git a/src/classes/share/javax/media/j3d/HashKey.java b/src/classes/share/javax/media/j3d/HashKey.java new file mode 100644 index 0000000..d942ad6 --- /dev/null +++ b/src/classes/share/javax/media/j3d/HashKey.java @@ -0,0 +1,240 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class HashKey extends Object { + + /** + * The value is used for character storage. + */ + char value[]; + + /** + * The count is the number of characters in the buffer. + */ + int count = 0; + + HashKey() { + this(16); + } + + HashKey(int length) { + value = new char[length]; + } + + HashKey(HashKey hashkey) { + this.set(hashkey); + } + + HashKey(String str) { + this(str.length() + 16); + append(str); + } + + void set(HashKey hashkey) { + int i; + + if (this.count < hashkey.count) { + this.value = new char[hashkey.count]; + } + + for (i=0; i<hashkey.count; i++) { + this.value[i] = hashkey.value[i]; + } + this.count = hashkey.count; + } + + void reset() { + count = 0; + } + + void ensureCapacity(int minimumCapacity) { + int maxCapacity = value.length; + + if (minimumCapacity > maxCapacity) { + int newCapacity = (maxCapacity + 1) * 2; + if (minimumCapacity > newCapacity) { + newCapacity = minimumCapacity; + } + + char newValue[] = new char[newCapacity]; + System.arraycopy(value, 0, newValue, 0, count); + value = newValue; + } + } + + HashKey append(String str) { + int len = 0; + + if (str == null) + return this; + + len = str.length(); + ensureCapacity(count + len); + str.getChars(0, len, value, count); + count += len; + return this; + } + + public int hashCode() { + int h = 0; + int off = 0; + char val[] = value; + int len = count; + + if (len < 16) { + for (int i = len ; i > 0; i--) { + h = (h * 37) + val[off++]; + } + } else { + // only sample some characters + int skip = len / 8; + for (int i = len ; i > 0; i -= skip, off += skip) { + h = (h * 39) + val[off]; + } + } + return h; + } + + public boolean equals(Object anObject) { + if ((anObject != null) && (anObject instanceof HashKey)) { + HashKey anotherHashKey = (HashKey)anObject; + int n = count; + if (n == anotherHashKey.count) { + char v1[] = value; + char v2[] = anotherHashKey.value;; + int i = 0; + int j = 0; + while (n-- != 0) { + if (v1[i++] != v2[j++]) { + return false; + } + } + return true; + } + } + return false; + } + + + /* For internal use only. */ + private int equals(HashKey hk) { + int index = 0; + + while((index < count) && (index < hk.count)) { + if(value[index] < hk.value[index]) + return -1; + else if(value[index] > hk.value[index]) + return 1; + index++; + } + + if(count == hk.count) + // Found it! + return 0; + else if(count < hk.count) + return -1; + else + return 1; + + } + + + /* For package use only. */ + int equals(HashKey localToVworldKeys[], int start, int end) { + int mid; + + mid = start +((end - start)/ 2); + if(localToVworldKeys[mid] != null) { + int test = equals(localToVworldKeys[mid]); + + if((test < 0) && (start != mid)) + return equals(localToVworldKeys, start, mid); + else if((test > 0) && (start != mid)) + return equals(localToVworldKeys, mid, end); + else if(test == 0) + return mid; + else + return -1; + } + // A null haskey encountered. + return -2; + } + + /* For package use only. */ + boolean equals(HashKey localToVworldKeys[], int[] index, + int start, int end) { + + int mid; + + mid = start +((end - start)/ 2); + if(localToVworldKeys[mid] != null) { + int test = equals(localToVworldKeys[mid]); + + if(start != mid) { + if(test < 0) { + return equals(localToVworldKeys, index, start, mid); + } + else if(test > 0) { + return equals(localToVworldKeys, index, mid, end); + } + } + else { // (start == mid) + if(test < 0) { + index[0] = mid; + return false; + } + else if(test > 0) { + index[0] = mid+1; + return false; + } + } + + // (test == 0) + index[0] = mid; + return true; + + } + // A null haskey encountered. + // But we still want to return the index where we encounter it. + index[0] = mid; + return false; + } + + public String toString() { + return new String(value, 0, count); + } + + String getLastNodeId() { + int i, j, temp; + + for(i=(count-1); i>0; i--) + if(value[i] == '+') + break; + + if(i>0) { + value[i++] = '\0'; + temp = count-i; + char v1[] = new char[temp]; + for(j=0; j<temp; j++, i++) { + v1[j] = value[i]; + value[i] = '\0'; + } + count = count - (temp+1); + return new String(v1); + } + + return new String(value, 0, count); + } + +} diff --git a/src/classes/share/javax/media/j3d/HiResCoord.java b/src/classes/share/javax/media/j3d/HiResCoord.java new file mode 100644 index 0000000..31fb48e --- /dev/null +++ b/src/classes/share/javax/media/j3d/HiResCoord.java @@ -0,0 +1,724 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.lang.Integer; +import javax.vecmath.*; + +/** + * High resolution coordinate object. + * + */ + +/** + * The HiResCoord object specifies the location of scene + * components within the Virtual Universe. + * The coordinates of all scene graph objects are relative to + * the HiResCoord of the Locale in which they are contained. + * <P> + * The HiResCoord defines a point using a set of three + * high-resolution coordinates, each of which consists of three + * two's-complement fixed-point numbers. + * Each high-resolution number consists of 256 total bits with a + * binary point at bit 128, or between the integers at index +* 3 and 4. A high-resolution coordinate of 1.0 is defined to be exactly + * 1 meter. This coordinate system is sufficient to describe a + * universe in excess of several billion light years across, yet + * still define objects smaller than a proton. + * <P> + * Java 3D uses integer arrays of length + * eight to define or extract a single 256-bit coordinate value. + * Java 3D interprets the integer at index 0 as the 32 + * most-significant bits and the integer at index 7 as the 32 + * least-significant bits. + */ + +public class HiResCoord { + /** + * The eight-element array containing the high resolution coordinate's + * x value. + */ + int x[]; + + /** + * The eight-element array containing the high resolution coordinate's + * y value. + */ + int y[]; + + /** + * The eight-element array containing the high resolution coordinate's + * z value. + */ + int z[]; + +private double scales[] = { + 79228162514264337593543950336.0, // 2^96 + 18446744073709551616.0, // 2^64 + 4294967296.0, // 2^32 + 1.0, // 2^0 + 2.3283064365386962890625e-10, // 2^-32 + 5.421010862427522170037264004349708557128906250000000000000000e-20, // 2^-64 + 1.26217744835361888865876570445245796747713029617443680763244628906250e-29, // 2^-96 + 2.938735877055718769921841343055614194546663891930218803771879265696043148636817932128906250e-39 }; + + + + /** + * Constructs and initializes a new HiResCoord using the values + * provided in the argument. + * The HiResCoord represents 768 bits of floating point 3-Space. + * @param X an eight element array specifying the x position + * @param Y an eight element array specifying the y position + * @param Z an eight element array specifying the z position + */ + public HiResCoord(int[] X, int[] Y, int[] Z) { + int i; + + this.x = new int[8]; + this.y = new int[8]; + this.z = new int[8]; + + for(i=0;i<8;i++) { + this.x[i] = X[i]; + this.y[i] = Y[i]; + this.z[i] = Z[i]; + } + + } + + /** + * Constructs and initializes a new HiResCoord using the values + * provided in the argument. + * The HiResCoord represents 768 bits of floating point 3-Space. + * @param hc the HiResCoord to copy + */ + public HiResCoord(HiResCoord hc) { + this.x = new int[8]; + this.y = new int[8]; + this.z = new int[8]; + + this.x[0] = hc.x[0]; + this.y[0] = hc.y[0]; + this.z[0] = hc.z[0]; + + this.x[1] = hc.x[1]; + this.y[1] = hc.y[1]; + this.z[1] = hc.z[1]; + + this.x[2] = hc.x[2]; + this.y[2] = hc.y[2]; + this.z[2] = hc.z[2]; + + this.x[3] = hc.x[3]; + this.y[3] = hc.y[3]; + this.z[3] = hc.z[3]; + + this.x[4] = hc.x[4]; + this.y[4] = hc.y[4]; + this.z[4] = hc.z[4]; + + this.x[5] = hc.x[5]; + this.y[5] = hc.y[5]; + this.z[5] = hc.z[5]; + + this.x[6] = hc.x[6]; + this.y[6] = hc.y[6]; + this.z[6] = hc.z[6]; + + this.x[7] = hc.x[7]; + this.y[7] = hc.y[7]; + this.z[7] = hc.z[7]; + } + + /** + * Constructs and initializes a new HiResCoord located at (0, 0, 0). + * The HiResCoord represents 768 bits of floating point 3-Space. + */ + public HiResCoord() { + this.x = new int[8]; + this.y = new int[8]; + this.z = new int[8]; + } + + /** + * Sets this HiResCoord to the location specified by the + * parameters provided. + * @param X an eight-element array specifying the x position + * @param Y an eight-element array specifying the y position + * @param Z an eight-element array specifying the z position + */ + public void setHiResCoord(int[] X, int[] Y, int[] Z) { + int i; + + for(i=0;i<8;i++) { + this.x[i] = X[i]; + this.y[i] = Y[i]; + this.z[i] = Z[i]; + } + + } + + /** + * Sets this HiResCoord to the location specified by the + * hires provided. + * @param hires the hires coordinate to copy + */ + public void setHiResCoord(HiResCoord hires) { + this.x[0] = hires.x[0]; + this.y[0] = hires.y[0]; + this.z[0] = hires.z[0]; + + this.x[1] = hires.x[1]; + this.y[1] = hires.y[1]; + this.z[1] = hires.z[1]; + + this.x[2] = hires.x[2]; + this.y[2] = hires.y[2]; + this.z[2] = hires.z[2]; + + this.x[3] = hires.x[3]; + this.y[3] = hires.y[3]; + this.z[3] = hires.z[3]; + + this.x[4] = hires.x[4]; + this.y[4] = hires.y[4]; + this.z[4] = hires.z[4]; + + this.x[5] = hires.x[5]; + this.y[5] = hires.y[5]; + this.z[5] = hires.z[5]; + + this.x[6] = hires.x[6]; + this.y[6] = hires.y[6]; + this.z[6] = hires.z[6]; + + this.x[7] = hires.x[7]; + this.y[7] = hires.y[7]; + this.z[7] = hires.z[7]; + } + + + /** + * Sets this HiResCoord's X value to that specified by the argument. + * @param X an eight-element array specifying the x position + */ + public void setHiResCoordX(int[] X) { + this.x[0] = X[0]; + this.x[1] = X[1]; + this.x[2] = X[2]; + this.x[3] = X[3]; + this.x[4] = X[4]; + this.x[5] = X[5]; + this.x[6] = X[6]; + this.x[7] = X[7]; + } + + /** + * Sets this HiResCoord's Y value to that specified by the argument. + * @param Y an eight-element array specifying the y position + */ + public void setHiResCoordY(int[] Y) { + this.y[0] = Y[0]; + this.y[1] = Y[1]; + this.y[2] = Y[2]; + this.y[3] = Y[3]; + this.y[4] = Y[4]; + this.y[5] = Y[5]; + this.y[6] = Y[6]; + this.y[7] = Y[7]; + } + + /** + * Sets this HiResCoord's Z value to that specified by the argument. + * @param Z an eight-element array specifying the z position + */ + public void setHiResCoordZ(int[] Z) { + this.z[0] = Z[0]; + this.z[1] = Z[1]; + this.z[2] = Z[2]; + this.z[3] = Z[3]; + this.z[4] = Z[4]; + this.z[5] = Z[5]; + this.z[6] = Z[6]; + this.z[7] = Z[7]; + } + + /** + * Retrieves this HiResCoord's location and saves the coordinates + * in the specified arrays. The arrays must be large enough + * to hold all of the ints. + * @param X an eight element array that will receive the x position + * @param Y an eight element array that will receive the y position + * @param Z an eight element array that will receive the z position + */ + public void getHiResCoord(int[] X, int[] Y, int[] Z) { + X[0] = this.x[0]; + X[1] = this.x[1]; + X[2] = this.x[2]; + X[3] = this.x[3]; + X[4] = this.x[4]; + X[5] = this.x[5]; + X[6] = this.x[6]; + X[7] = this.x[7]; + + Y[0] = this.y[0]; + Y[1] = this.y[1]; + Y[2] = this.y[2]; + Y[3] = this.y[3]; + Y[4] = this.y[4]; + Y[5] = this.y[5]; + Y[6] = this.y[6]; + Y[7] = this.y[7]; + + Z[0] = this.z[0]; + Z[1] = this.z[1]; + Z[2] = this.z[2]; + Z[3] = this.z[3]; + Z[4] = this.z[4]; + Z[5] = this.z[5]; + Z[6] = this.z[6]; + Z[7] = this.z[7]; + } + + /** + * Retrieves this HiResCoord's location and places it into the hires + * argument. + * @param hc the hires coordinate that will receive this node's location + */ + public void getHiResCoord(HiResCoord hc) { + hc.x[0] = this.x[0]; + hc.x[1] = this.x[1]; + hc.x[2] = this.x[2]; + hc.x[3] = this.x[3]; + hc.x[4] = this.x[4]; + hc.x[5] = this.x[5]; + hc.x[6] = this.x[6]; + hc.x[7] = this.x[7]; + + hc.y[0] = this.y[0]; + hc.y[1] = this.y[1]; + hc.y[2] = this.y[2]; + hc.y[3] = this.y[3]; + hc.y[4] = this.y[4]; + hc.y[5] = this.y[5]; + hc.y[6] = this.y[6]; + hc.y[7] = this.y[7]; + + hc.z[0] = this.z[0]; + hc.z[1] = this.z[1]; + hc.z[2] = this.z[2]; + hc.z[3] = this.z[3]; + hc.z[4] = this.z[4]; + hc.z[5] = this.z[5]; + hc.z[6] = this.z[6]; + hc.z[7] = this.z[7]; + } + + /** + * Retrieves this HiResCoord's X value and stores it in the specified + * array. The array must be large enough to hold all of the ints. + * @param X an eight-element array that will receive the x position + */ + public void getHiResCoordX(int[] X) { + X[0] = this.x[0]; + X[1] = this.x[1]; + X[2] = this.x[2]; + X[3] = this.x[3]; + X[4] = this.x[4]; + X[5] = this.x[5]; + X[6] = this.x[6]; + X[7] = this.x[7]; + } + + /** + * Retrieves this HiResCoord's Y value and stores it in the specified + * array. The array must be large enough to hold all of the ints. + * @param Y an eight-element array that will receive the y position + */ + public void getHiResCoordY(int[] Y) { + Y[0] = this.y[0]; + Y[1] = this.y[1]; + Y[2] = this.y[2]; + Y[3] = this.y[3]; + Y[4] = this.y[4]; + Y[5] = this.y[5]; + Y[6] = this.y[6]; + Y[7] = this.y[7]; + } + + /** + * Retrieves this HiResCoord's Z value and stores it in the specified + * array. The array must be large enough to hold all of the ints. + * @param Z an eight-element array that will receive the z position + */ + public void getHiResCoordZ(int[] Z) { + Z[0] = this.z[0]; + Z[1] = this.z[1]; + Z[2] = this.z[2]; + Z[3] = this.z[3]; + Z[4] = this.z[4]; + Z[5] = this.z[5]; + Z[6] = this.z[6]; + Z[7] = this.z[7]; + } + + /** + * Compares the specified HiResCoord to this HiResCoord. + * @param h1 the second HiResCoord + * @return true if equal, false if not equal + */ + public boolean equals(HiResCoord h1) { + try { + return ((this.x[0] == h1.x[0]) + && (this.x[1] == h1.x[1]) + && (this.x[2] == h1.x[2]) + && (this.x[3] == h1.x[3]) + && (this.x[4] == h1.x[4]) + && (this.x[5] == h1.x[5]) + && (this.x[6] == h1.x[6]) + && (this.x[7] == h1.x[7]) + && (this.y[0] == h1.y[0]) + && (this.y[1] == h1.y[1]) + && (this.y[2] == h1.y[2]) + && (this.y[3] == h1.y[3]) + && (this.y[4] == h1.y[4]) + && (this.y[5] == h1.y[5]) + && (this.y[6] == h1.y[6]) + && (this.y[7] == h1.y[7]) + && (this.z[0] == h1.z[0]) + && (this.z[1] == h1.z[1]) + && (this.z[2] == h1.z[2]) + && (this.z[3] == h1.z[3]) + && (this.z[4] == h1.z[4]) + && (this.z[5] == h1.z[5]) + && (this.z[6] == h1.z[6]) + && (this.z[7] == h1.z[7])); + } + catch (NullPointerException e2) {return false;} + + } + + /** + * Returns true if the Object o1 is of type HiResCoord and all of the + * data members of o1 are equal to the corresponding data members in + * this HiResCoord. + * @param o1 the second HiResCoord + * @return true if equal, false if not equal + */ + public boolean equals(Object o1) { + try { + HiResCoord h1 = (HiResCoord)o1; + return ((this.x[0] == h1.x[0]) + && (this.x[1] == h1.x[1]) + && (this.x[2] == h1.x[2]) + && (this.x[3] == h1.x[3]) + && (this.x[4] == h1.x[4]) + && (this.x[5] == h1.x[5]) + && (this.x[6] == h1.x[6]) + && (this.x[7] == h1.x[7]) + && (this.y[0] == h1.y[0]) + && (this.y[1] == h1.y[1]) + && (this.y[2] == h1.y[2]) + && (this.y[3] == h1.y[3]) + && (this.y[4] == h1.y[4]) + && (this.y[5] == h1.y[5]) + && (this.y[6] == h1.y[6]) + && (this.y[7] == h1.y[7]) + && (this.z[0] == h1.z[0]) + && (this.z[1] == h1.z[1]) + && (this.z[2] == h1.z[2]) + && (this.z[3] == h1.z[3]) + && (this.z[4] == h1.z[4]) + && (this.z[5] == h1.z[5]) + && (this.z[6] == h1.z[6]) + && (this.z[7] == h1.z[7])); + } + catch (NullPointerException e2) {return false;} + catch (ClassCastException e1) {return false;} + + + } + /** + * Adds two HiResCoords placing the results into this HiResCoord. + * @param h1 the first HiResCoord + * @param h2 the second HiResCoord + */ + public void add(HiResCoord h1, HiResCoord h2) { + // needs to handle carry bits + // move to long, add, add in carry bit + + hiResAdd( this, h1, h2 ); + + } + + /** + * Subtracts two HiResCoords placing the results into this HiResCoord. + * @param h1 the first HiResCoord + * @param h2 the second HiResCoord + */ + public void sub(HiResCoord h1, HiResCoord h2) { + HiResCoord tmpHc = new HiResCoord(); + + // negate via two's complement then add + // + hiResNegate( tmpHc, h2); + hiResAdd( this, h1, tmpHc); + + } + + /** + * Negates the specified HiResCoords and places the + * results into this HiResCoord. + * @param h1 the source HiResCoord + */ + public void negate(HiResCoord h1) { + + hiResNegate( this, h1); + + } + + /** + * Negates this HiResCoord + */ + public void negate() { + + hiResNegate( this, this ); + + } + + /** + * Scales the specified HiResCoords by the specified value and + * places the results into this HiResCoord. + * @param scale the amount to scale the specified HiResCoord + * @param h1 the source HiResCoord + */ + public void scale(int scale, HiResCoord h1) { + hiResScale( h1.x, this.x, scale); + hiResScale( h1.y, this.y, scale); + hiResScale( h1.z, this.z, scale); + } + + /** + * Scales this HiResCoord by the specified value. + * @param scale the amount to scale the specified HiResCoord + */ + public void scale(int scale) { + hiResScale( this.x, this.x, scale); + hiResScale( this.y, this.y, scale); + hiResScale( this.z, this.z, scale); + return; + } + + /** + * Subtracts the specified HiResCoord from this HiResCoord + * placing the difference vector into the specified + * double-precision vector. + * @param h1 the HiResCoord to be subtracted from this + * @param v the vector that will receive the result + */ + public void difference(HiResCoord h1, Vector3d v) { + // negate coord via two compliment, add, convert result to double + // by scaling each bit set appropriately + + hiResDiff( this, h1, v); + return; + } + + /** + * The floating point distance between the specified + * HiResCoord and this HiResCoord. + * @param h1 the second HiResCoord + */ + public double distance(HiResCoord h1) { + Vector3d diff = new Vector3d(); + + hiResDiff( this, h1, diff); + + return( Math.sqrt( diff.x*diff.x + diff.y*diff.y + diff.z*diff.z)); + } + + private void hiResNegate( HiResCoord ho, HiResCoord hi) { + + negateCoord( ho.x, hi.x); + negateCoord( ho.y, hi.y); + negateCoord( ho.z, hi.z); + + return; + } + + private void negateCoord( int cout[], int cin[] ) { + int i; + + for(i=0;i<8;i++) { + cout[i] = ~cin[i]; // take compliment of each + } + + for(i=7;i>=0;i--) { // add one + if( cout[i] == 0xffffffff) { + cout[i] = 0; + } else { + cout[i] += 1; + break; + } + } + return; + } + + private void hiResAdd(HiResCoord ho, HiResCoord h1, HiResCoord h2 ){ + int i; + long tmp1, tmp2,carry; + long signMask = Integer.MAX_VALUE; + long signBit = 1; + signBit = signBit << 31; + long carryMask = 0x7fffffff; + carryMask = carryMask <<1; + carryMask += 1; + + + carry = 0; + for(i=7;i>0;i--) { + tmp1 = 0; + tmp1 = signMask & h1.x[i]; // mask off sign bit so will not get put in msb + if( h1.x[i] < 0 ) tmp1 |= signBit; // add sign bit back + + tmp2 = 0; + tmp2 = signMask & h2.x[i]; // mask off sign bit so will not get put in msb + if( h2.x[i] < 0 ) tmp2 |= signBit; // add sign bit back + + tmp2 = tmp2+tmp1 + carry; + carry = tmp2 >> 32; // get carry bits for next operation + ho.x[i] = (int)(tmp2 & carryMask); // mask off high bits + } + ho.x[0] = h1.x[0] + h2.x[0] + (int)carry; + + + carry = 0; + for(i=7;i>0;i--) { + tmp1 = 0; + tmp1 = signMask & h1.y[i]; // mask off sign bit so will not get put in msb + if( h1.y[i] < 0 ) tmp1 |= signBit; // add sign bit back + + tmp2 = 0; + tmp2 = signMask & h2.y[i]; // mask off sign bit so will not get put in msb + if( h2.y[i] < 0 ) tmp2 |= signBit; // add sign bit back + + tmp2 = tmp2+tmp1 + carry; + carry = tmp2 >> 32; // get carry bits for next operation + ho.y[i] = (int)(tmp2 & carryMask); // mask off high bits + } + ho.y[0] = h1.y[0] + h2.y[0] + (int)carry; + + carry = 0; + for(i=7;i>0;i--) { + tmp1 = 0; + tmp1 = signMask & h1.z[i]; // mask off sign bit so will not get put in msb + if( h1.z[i] < 0 ) tmp1 |= signBit; // add sign bit back + + tmp2 = 0; + tmp2 = signMask & h2.z[i]; // mask off sign bit so will not get put in msb + if( h2.z[i] < 0 ) tmp2 |= signBit; // add sign bit back + + tmp2 = tmp2+tmp1 + carry; + carry = tmp2 >> 32; // get carry bits for next operation + ho.z[i] = (int)(tmp2 & carryMask); // mask off high bits + } + ho.z[0] = h1.z[0] + h2.z[0] + (int)carry; + return; + } + + private void hiResScale( int tin[], int tout[], double scale) { + int i; + long tmp,carry; + int signMask = Integer.MAX_VALUE; + long carryMask = 0x7fffffff; + carryMask = carryMask <<1; + carryMask += 1; + long signBit = 1; + signBit = signBit << 31; + + carry = 0; + for(i=7;i>0;i--) { + tmp = 0; + tmp = (long)(signMask & tin[i]); // mask off sign bit + if( tin[i] < 0 ) tmp |= signBit; // add sign bit back + tmp = (long)(tmp*scale + carry); + carry = tmp >> 32; // get carry bits for next operation + tout[i] = (int)(tmp & carryMask); // mask off high bits + } + tout[0] = (int)(tin[0]*scale + carry); + return; + } + private void hiResDiff( HiResCoord h1, HiResCoord h2, Vector3d diff) { + int i; + HiResCoord diffHi = new HiResCoord(); + long value; + int coordSpace[] = new int[8]; + int[] tempCoord; + int signMask = Integer.MAX_VALUE; + long signBit = 1; + signBit = signBit << 31; + + // negate via two's complement then add + // + hiResNegate( diffHi, h2); + hiResAdd( diffHi, h1, diffHi); + + + if( diffHi.x[0] < 0 ) { + tempCoord = coordSpace; + negateCoord( tempCoord, diffHi.x ); + } else { + tempCoord = diffHi.x; + } + diff.x = 0; + for(i=7;i>0;i--) { + value = (long)(tempCoord[i] & signMask); + if( tempCoord[i] < 0) value |= signBit; + diff.x += (double)(scales[i]*value); + } + diff.x += scales[0]*tempCoord[0]; + if( diffHi.x[0] < 0 )diff.x = -diff.x; + + if( diffHi.y[0] < 0 ) { + tempCoord = coordSpace; + negateCoord( tempCoord, diffHi.y ); + } else { + tempCoord = diffHi.y; + } + diff.y = 0; + for(i=7;i>0;i--) { + value = (long)(tempCoord[i] & signMask); + if( tempCoord[i] < 0) value |= signBit; + diff.y += scales[i]*value; + } + diff.y += scales[0]*tempCoord[0]; + if( diffHi.y[0] < 0 )diff.y = -diff.y; + + if( diffHi.z[0] < 0 ) { + tempCoord = coordSpace; + negateCoord( tempCoord, diffHi.z ); + } else { + tempCoord = diffHi.z; + } + diff.z = 0; + for(i=7;i>0;i--) { + value = (long)(tempCoord[i] & signMask); + if( tempCoord[i] < 0) value |= signBit; + diff.z += scales[i]*value; + } + diff.z += scales[0]*tempCoord[0]; + if( diffHi.z[0] < 0 )diff.z = -diff.z; + return; + } +} diff --git a/src/classes/share/javax/media/j3d/IllegalRenderingStateException.java b/src/classes/share/javax/media/j3d/IllegalRenderingStateException.java new file mode 100644 index 0000000..1096573 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IllegalRenderingStateException.java @@ -0,0 +1,35 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates an illegal state for rendering. This exception is currently + * unused. + */ +public class IllegalRenderingStateException extends IllegalStateException { + + /** + * Create the exception object with default values. + */ + public IllegalRenderingStateException(){ + } + + /** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public IllegalRenderingStateException(String str){ + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/IllegalSceneGraphException.java b/src/classes/share/javax/media/j3d/IllegalSceneGraphException.java new file mode 100644 index 0000000..046eec1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IllegalSceneGraphException.java @@ -0,0 +1,40 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates an illegal Java 3D scene graph. + * For example, the following is illegal: + * <ul> + * <li>A ViewPlatform node under a ViewSpecificGroup</li> + * </ul> + * + * @since Java 3D 1.3 + */ + +public class IllegalSceneGraphException extends RuntimeException { + + /** + * Create the exception object with default values. + */ + public IllegalSceneGraphException() { + } + + /** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public IllegalSceneGraphException(String str) { + super(str); + } +} diff --git a/src/classes/share/javax/media/j3d/IllegalSharingException.java b/src/classes/share/javax/media/j3d/IllegalSharingException.java new file mode 100644 index 0000000..7938541 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IllegalSharingException.java @@ -0,0 +1,60 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates an illegal attempt to share a scene graph object. For example, + * the following are illegal: + * <UL> + * <LI>referencing a shared subgraph in more than one virtual universe</LI> + * <LI>using the same node both in the scene graph and in an + * immediate mode graphics context</LI> + * <LI>including any of the following unsupported types of leaf node within a shared subgraph:</LI> + * <UL> + * <LI>AlternateAppearance</LI> + * <LI>Background</LI> + * <LI>Behavior</LI> + * <LI>BoundingLeaf</LI> + * <LI>Clip</LI> + * <LI>Fog</LI> + * <LI>ModelClip</LI> + * <LI>Soundscape</LI> + * <LI>ViewPlatform</LI> + * </UL> + * <LI>referencing a BranchGroup node in more than one of the following + * ways:</LI> + * <UL> + * <LI>attaching it to a (single) Locale</LI> + * <LI>adding it as a child of a Group Node within the scene graph</LI> + * <LI>referencing it from a (single) Background Leaf Node as + * background geometry</LI> + * </UL> + * </UL> + */ +public class IllegalSharingException extends IllegalSceneGraphException { + + /** + * Create the exception object with default values. + */ + public IllegalSharingException() { + } + + /** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public IllegalSharingException(String str) { + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/ImageComponent.java b/src/classes/share/javax/media/j3d/ImageComponent.java new file mode 100644 index 0000000..ab63f56 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ImageComponent.java @@ -0,0 +1,346 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Abstract class that is used to define 2D or 3D ImageComponent + * classes used in a Java 3D scene graph. This is used for texture + * images, background images and raster components of Shape3D nodes. + * + * <p> + * Image data may be passed to this ImageComponent object in + * one of two ways: by copying the image data into this object or by + * accessing the image data by reference. + * + * <p> + * <ul> + * <li> + * <b>By Copying:</b> + * By default, the set and get image methods copy the image + * data into or out of this ImageComponent object. This is + * appropriate for many applications, since the application may reuse + * the RenderedImage object after copying it to the ImageComponent. + * </li> + * <li><b>By Reference:</b> + * A new feature in Java 3D version 1.2 allows image data to + * be accessed by reference, directly from the RenderedImage object. + * To use this feature, you need to construct an ImageComponent object + * with the <code>byReference</code> flag set to <code>true</code>. + * In this mode, a reference to the input data is saved, but the data + * itself is not necessarily copied (although it may be, depending on + * the value of the <code>yUp</code> flag, the format of the + * ImageComponent, and the format of the RenderedImage). Image data + * referenced by an ImageComponent object can only be modified via + * the updateData method. + * Applications must exercise care not to violate this rule. If any + * referenced RenderedImage is modified outside the updateData method + * after it has been passed + * to an ImageComponent object, the results are undefined. + * Another restriction in by-reference mode is that if the specified + * RenderedImage is not an instance of BufferedImage, then + * this ImageComponent cannot be used for readRaster or + * off-screen rendering operations, since these operations modify + * the ImageComponent data. + * </li> + * </ul> + * + * <p> + * An image component object also specifies whether the orientation of + * its image data is "y-up" or "y-down" (the default). Y-up mode + * causes images to be interpreted as having their origin at the lower + * left (rather than the default upper left) of a texture or raster + * image with successive scan lines moving up. This is more + * consistent with texture mapping data onto a surface, and maps + * directly into the the way textures are used in OpenGL and other 3D + * APIs. Setting the <code>yUp</code> flag to true in conjunction + * with setting the <code>byReference</code> flag to true makes it + * possible for Java 3D to avoid copying the texture map in some + * cases. + * + * <p> + * Note that all color fields are treated as unsigned values, even though + * Java does not directly support unsigned variables. This means, for + * example, that an ImageComponent using a format of FORMAT_RGB5 can + * represent red, green, and blue values between 0 and 31, while an + * ImageComponent using a format of FORMAT_RGB8 can represent color + * values between 0 and 255. Even when byte values are used to create a + * RenderedImage with 8-bit color components, the resulting colors + * (bytes) are interpreted as if they were unsigned. + * Values greater than 127 can be assigned to a byte variable using a + * type cast. For example: + * <ul>byteVariable = (byte) intValue; // intValue can be > 127</ul> + * If intValue is greater than 127, then byteVariable will be negative. The + * correct value will be extracted when it is used (by masking off the upper + * bits). + */ + +public abstract class ImageComponent extends NodeComponent { + // + // Pixel format values + // + + /** + * Specifies that each pixel contains 3 8-bit channels: one each + * for red, green, blue. Same as FORMAT_RGB8. + */ + public static final int FORMAT_RGB = 1; + + /** + * Specifies that each pixel contains 4 8-bit channels: one each + * for red, green, blue, alpha. Same as FORMAT_RGBA8. + */ + public static final int FORMAT_RGBA = 2; + + /** + * Specifies that each pixel contains 3 8-bit channels: one each + * for red, green, blue. Same as FORMAT_RGB. + */ + public static final int FORMAT_RGB8 = FORMAT_RGB; + + /** + * Specifies that each pixel contains 4 8-bit channels: one each + * for red, green, blue, alpha. Same as FORMAT_RGBA. + */ + public static final int FORMAT_RGBA8 = FORMAT_RGBA; + + /** + * Specifies that each pixel contains 3 5-bit channels: one each + * for red, green, blue. + */ + public static final int FORMAT_RGB5 = 3; + + /** + * Specifies that each pixel contains 3 5-bit channels: one each + * for red, green, blue and 1 1-bit channel for alpha. + */ + public static final int FORMAT_RGB5_A1 = 4; + + /** + * Specifies that each pixel contains 3 4-bit channels: one each + * for red, green, blue. + */ + public static final int FORMAT_RGB4 = 5; + + /** + * Specifies that each pixel contains 4 4-bit channels: one each + * for red, green, blue, alpha. + */ + public static final int FORMAT_RGBA4 = 6; + + /** + * Specifies that each pixel contains 2 4-bit channels: one each + * for luminance and alpha. + */ + public static final int FORMAT_LUM4_ALPHA4 = 7; + + /** + * Specifies that each pixel contains 2 8-bit channels: one each + * for luminance and alpha. + */ + public static final int FORMAT_LUM8_ALPHA8 = 8; + + /** + * Specifies that each pixel contains 2 3-bit channels: one each + * for red, green, and 1 2-bit channel for blue. + */ + public static final int FORMAT_R3_G3_B2 = 9; + + /** + * Specifies that each pixel contains 1 8-bit channel: it can be + * used for only luminance or only alpha or only intensity. + */ + public static final int FORMAT_CHANNEL8 = 10; + + // Internal variable for checking validity of formats + // Change this if any more formats are added or removed + static final int FORMAT_TOTAL = 10; + + /** + * Specifies that this ImageComponent object allows reading its + * size component information (width, height, and depth). + */ + public static final int + ALLOW_SIZE_READ = CapabilityBits.IMAGE_COMPONENT_ALLOW_SIZE_READ; + + /** + * Specifies that this ImageComponent object allows reading its + * format component information. + */ + public static final int + ALLOW_FORMAT_READ = CapabilityBits.IMAGE_COMPONENT_ALLOW_FORMAT_READ; + + /** + * Specifies that this ImageComponent object allows reading its + * image component information. + */ + public static final int + ALLOW_IMAGE_READ = CapabilityBits.IMAGE_COMPONENT_ALLOW_IMAGE_READ; + + /** + * Specifies that this ImageComponent object allows writing its + * image component information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_IMAGE_WRITE = CapabilityBits.IMAGE_COMPONENT_ALLOW_IMAGE_WRITE; + + /** + * Not a public constructor, for internal use + */ + ImageComponent() { + } + + /** + * Constructs an image component object using the specified format, width, + * and height. Default values are used for all other parameters. The + * default values are as follows: + * <ul> + * byReference : false<br> + * yUp : false<br> + * </ul> + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA etc. + * @param width the number of columns of pixels in this image component + * object + * @param height the number of rows of pixels in this image component + * object + * @exception IllegalArgumentException if format is invalid, or if + * width or height are not positive. + */ + public ImageComponent(int format, int width, int height) { + ((ImageComponentRetained)this.retained).processParams(format, width, height, 1); + } + + /** + * Constructs an image component object using the specified format, width, + * height, byReference flag, and yUp flag. + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA etc. + * @param width the number of columns of pixels in this image component + * object + * @param height the number of rows of pixels in this image component + * object + * @param byReference a flag that indicates whether the data is copied + * into this image component object or is accessed by reference. + * @param yUp a flag that indicates the y-orientation of this image + * component. If yUp is set to true, the origin of the image is + * the lower left; otherwise, the origin of the image is the upper + * left. + * @exception IllegalArgumentException if format is invalid, or if + * width or height are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent(int format, + int width, + int height, + boolean byReference, + boolean yUp) { + + ((ImageComponentRetained)this.retained).setYUp(yUp); + ((ImageComponentRetained)this.retained).setByReference(byReference); + ((ImageComponentRetained)this.retained).processParams(format, width, height, 1); + } + + /** + * Retrieves the width of this image component object. + * @return the width of this image component object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getWidth() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent0")); + return ((ImageComponentRetained)this.retained).getWidth(); + } + + /** + * Retrieves the height of this image component object. + * @return the height of this image component object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getHeight() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent1")); + return ((ImageComponentRetained)this.retained).getHeight(); + } + + /** + * Retrieves the format of this image component object. + * @return the format of this image component object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getFormat() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_FORMAT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent2")); + return ((ImageComponentRetained)this.retained).getFormat(); + } + + + /** + * Retrieves the data access mode for this ImageComponent object. + * + * @return <code>true</code> if the data access mode for this + * ImageComponent object is by-reference; + * <code>false</code> if the data access mode is by-copying. + * + * @since Java 3D 1.2 + */ + public boolean isByReference() { + return ((ImageComponentRetained)this.retained).isByReference(); + } + + + /** + * Sets the y-orientation of this ImageComponent object to + * y-up or y-down. + * + * @param yUp a flag that indicates the y-orientation of this image + * component. If yUp is set to true, the origin of the image is + * the lower left; otherwise, the origin of the image is the upper + * left. + * + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.2 + */ + public void setYUp(boolean yUp) { + checkForLiveOrCompiled(); + + ((ImageComponentRetained)this.retained).setYUp(yUp); + } + + + /** + * Retrieves the y-orientation for this ImageComponent object. + * + * @return <code>true</code> if the y-orientation of this + * ImageComponent object is y-up; <code>false</code> if the + * y-orientation of this ImageComponent object is y-down. + * + * @since Java 3D 1.2 + */ + public boolean isYUp() { + return ((ImageComponentRetained)this.retained).isYUp(); + } + +} diff --git a/src/classes/share/javax/media/j3d/ImageComponent2D.java b/src/classes/share/javax/media/j3d/ImageComponent2D.java new file mode 100644 index 0000000..5fdfa18 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ImageComponent2D.java @@ -0,0 +1,534 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; + +/** + * This class defines a 2D image component. This is used for texture + * images, background images and raster components of Shape3D nodes. + * Prior to Java 3D 1.2, only BufferedImage objects could be used as the + * input to an ImageComponent2D object. As of Java 3D 1.2, an + * ImageComponent2D accepts any RenderedImage object (BufferedImage is + * an implementation of the RenderedImage interface). The methods + * that set/get a BufferedImage object are left in for compatibility. + * The new methods that set/get a RenderedImage are a superset of the + * old methods. In particular, the two set methods in the following + * example are equivalent: + * + * <p> + * <ul> + * <code> + * BufferedImage bi;<br> + * RenderedImage ri = bi;<br> + * ImageComponent2D ic;<br> + * <p> + * // Set the image to the specified BufferedImage<br> + * ic.set(bi);<br> + * <p> + * // Set the image to the specified RenderedImage<br> + * ic.set(ri);<br> + * </code> + * </ul> + * + */ + +public class ImageComponent2D extends ImageComponent { + + // non-public, no parameter constructor + ImageComponent2D() {} + + /** + * Constructs a 2D image component object using the specified + * format, width, and height, and a null image. + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA, etc. + * @param width the number of columns of pixels in this image component + * object + * @param height the number of rows of pixels in this image component + * object + * @exception IllegalArgumentException if format is invalid, or if + * width or height are not positive. + */ + public ImageComponent2D(int format, + int width, + int height) { + + ((ImageComponent2DRetained)this.retained).processParams(format, width, height, 1); + } + + /** + * Constructs a 2D image component object using the specified format + * and BufferedImage. A copy of the BufferedImage is made. + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA, etc. + * @param image the BufferedImage used to create this 2D image component. + * @exception IllegalArgumentException if format is invalid, or if + * the width or height of the image are not positive. + */ + public ImageComponent2D(int format, BufferedImage image) { + + ((ImageComponent2DRetained)this.retained).processParams(format, image.getWidth(null), image.getHeight(null), 1); + ((ImageComponent2DRetained)this.retained).set(image); + } + + /** + * Constructs a 2D image component object using the specified format + * and RenderedImage. A copy of the RenderedImage is made. + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA, etc. + * @param image the RenderedImage used to create this 2D image component + * @exception IllegalArgumentException if format is invalid, or if + * the width or height of the image are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent2D(int format, RenderedImage image) { + + + ((ImageComponent2DRetained)this.retained).processParams(format, image.getWidth(), image.getHeight(), 1); + ((ImageComponent2DRetained)this.retained).set(image); + } + + /** + * Constructs a 2D image component object using the specified + * format, width, height, byReference flag, and yUp flag, and + * a null image. + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA, etc. + * @param width the number of columns of pixels in this image component + * object + * @param height the number of rows of pixels in this image component + * object + * @param byReference a flag that indicates whether the data is copied + * into this image component object or is accessed by reference. + * @param yUp a flag that indicates the y-orientation of this image + * component. If yUp is set to true, the origin of the image is + * the lower left; otherwise, the origin of the image is the upper + * left. + * @exception IllegalArgumentException if format is invalid, or if + * width or height are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent2D(int format, + int width, + int height, + boolean byReference, + boolean yUp) { + + + ((ImageComponentRetained)this.retained).setByReference(byReference); + ((ImageComponentRetained)this.retained).setYUp(yUp); + ((ImageComponent2DRetained)this.retained).processParams(format, width, height, 1); + } + + /** + * Constructs a 2D image component object using the specified format, + * BufferedImage, byReference flag, and yUp flag. + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA, etc. + * @param image the BufferedImage used to create this 2D image component + * @param byReference a flag that indicates whether the data is copied + * into this image component object or is accessed by reference + * @param yUp a flag that indicates the y-orientation of this image + * component. If yUp is set to true, the origin of the image is + * the lower left; otherwise, the origin of the image is the upper + * left. + * @exception IllegalArgumentException if format is invalid, or if + * the width or height of the image are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent2D(int format, + BufferedImage image, + boolean byReference, + boolean yUp) { + + + ((ImageComponentRetained)this.retained).setByReference(byReference); + ((ImageComponentRetained)this.retained).setYUp(yUp); + ((ImageComponent2DRetained)this.retained).processParams(format, image.getWidth(null), image.getHeight(null), 1); + ((ImageComponent2DRetained)this.retained).set(image); + } + + /** + * Constructs a 2D image component object using the specified format, + * RenderedImage, byReference flag, and yUp flag. + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA, etc. + * @param image the RenderedImage used to create this 2D image component + * @param byReference a flag that indicates whether the data is copied + * into this image component object or is accessed by reference. + * @param yUp a flag that indicates the y-orientation of this image + * component. If yUp is set to true, the origin of the image is + * the lower left; otherwise, the origin of the image is the upper + * left. + * @exception IllegalArgumentException if format is invalid, or if + * the width or height of the image are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent2D(int format, + RenderedImage image, + boolean byReference, + boolean yUp) { + + + ((ImageComponentRetained)this.retained).setByReference(byReference); + ((ImageComponentRetained)this.retained).setYUp(yUp); + ((ImageComponent2DRetained)this.retained).processParams(format, image.getWidth(), image.getHeight(), 1); + ((ImageComponent2DRetained)this.retained).set(image); + } + + /** + * Sets this image component to the specified BufferedImage + * object. If the data access mode is not by-reference, then the + * BufferedImage data is copied into this object. If + * the data access mode is by-reference, then a reference to the + * BufferedImage is saved, but the data is not necessarily + * copied. + * + * @param image BufferedImage object containing the image. + * The format and size must be the same as the current format in this + * ImageComponent2D object. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + */ + public void set(BufferedImage image) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_WRITE)) + throw new CapabilityNotSetException( + J3dI18N.getString("ImageComponent2D1")); + } + + ((ImageComponent2DRetained)this.retained).set(image); + } + + /** + * Sets this image component to the specified RenderedImage + * object. If the data access mode is not by-reference, the + * RenderedImage data is copied into this object. If + * the data access mode is by-reference, a reference to the + * RenderedImage is saved, but the data is not necessarily + * copied. + * + * @param image RenderedImage object containing the image. + * The format and size must be the same as the current format in this + * ImageComponent2D object. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void set(RenderedImage image) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_WRITE)) + throw new CapabilityNotSetException( + J3dI18N.getString("ImageComponent2D1")); + } + + ((ImageComponent2DRetained)this.retained).set(image); + } + + /** + * Retrieves the image from this ImageComponent2D object. If the + * data access mode is not by-reference, a copy of the image + * is made. If the data access mode is by-reference, the + * reference is returned. + * + * @return either a new BufferedImage object created from the data + * in this image component, or the BufferedImage object referenced + * by this image component. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalStateException if the data access mode is + * by-reference and the image referenced by this ImageComponent2D + * object is not an instance of BufferedImage. + */ + public BufferedImage getImage() { + if (isLiveOrCompiled()) { + if(!this.getCapability(ImageComponent.ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent2D0")); + } + + RenderedImage img = ((ImageComponent2DRetained)this.retained).getImage(); + + if ((img != null) && !(img instanceof BufferedImage)) { + throw new IllegalStateException(J3dI18N.getString("ImageComponent2D5")); + } + return (BufferedImage) img; + + } + + /** + * Retrieves the image from this ImageComponent2D object. If the + * data access mode is not by-reference, a copy of the image + * is made. If the data access mode is by-reference, the + * reference is returned. + * + * @return either a new RenderedImage object created from the data + * in this image component, or the RenderedImage object referenced + * by this image component. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public RenderedImage getRenderedImage() { + + if (isLiveOrCompiled()) + if(!this.getCapability(ImageComponent.ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent2D0")); + return ((ImageComponent2DRetained)this.retained).getImage(); + } + + + /** + * Modifies a contiguous subregion of the image component. + * Block of data of dimension (width * height) + * starting at the offset (srcX, srcY) of the specified + * RenderedImage object will be copied into the image component + * starting at the offset (dstX, dstY) of the ImageComponent2D object. + * The RenderedImage object must be of the same format as the current + * format of this object. + * This method can only be used if the data access mode is + * by-copy. If it is by-reference, see updateData(). + * + * @param image RenderedImage object containing the subimage. + * @param width width of the subregion. + * @param height height of the subregion. + * @param srcX starting X offset of the subregion in the + * specified image. + * @param srcY starting Y offset of the subregion in the + * specified image. + * @param dstX starting X offset of the subregion in the image + * component of this object. + * @param dstY starting Y offset of the subregion in the image + * component of this object. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalStateException if the data access mode is + * <code>BY_REFERENCE</code>. + * @exception IllegalArgumentException if <code>width</code> or + * <code>height</code> of + * the subregion exceeds the dimension of the image of this object. + * @exception IllegalArgumentException if <code>dstX</code> < 0, or + * (<code>dstX</code> + <code>width</code>) > width of this object, or + * <code>dstY</code> < 0, or + * (<code>dstY</code> + <code>height</code>) > height of this object. + * @exception IllegalArgumentException if <code>srcX</code> < 0, or + * (<code>srcX</code> + <code>width</code>) > width of the RenderedImage + * object containing the subimage, or + * <code>srcY</code> < 0, or + * (<code>srcY</code> + <code>height</code>) > height of the + * RenderedImage object containing the subimage. + * + * @since Java 3D 1.3 + */ + public void setSubImage(RenderedImage image, int width, int height, + int srcX, int srcY, int dstX, int dstY) { + if (isLiveOrCompiled() && + !this.getCapability(ALLOW_IMAGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("ImageComponent2D1")); + } + + if (((ImageComponent2DRetained)this.retained).isByReference()) { + throw new IllegalStateException( + J3dI18N.getString("ImageComponent2D4")); + } + + int w = ((ImageComponent2DRetained)this.retained).getWidth(); + int h = ((ImageComponent2DRetained)this.retained).getHeight(); + + if ((srcX < 0) || (srcY < 0) || + ((srcX + width) > w) || ((srcY + height) > h) || + (dstX < 0) || (dstY < 0) || + ((dstX + width) > w) || ((dstY + height) > h)) { + throw new IllegalArgumentException( + J3dI18N.getString("ImageComponent2D3")); + } + + ((ImageComponent2DRetained)this.retained).setSubImage( + image, width, height, srcX, srcY, dstX, dstY); + } + + /** + * Updates image data that is accessed by reference. + * This method calls the updateData method of the specified + * ImageComponent2D.Updater object to synchronize updates to the + * image data that is referenced by this ImageComponent2D object. + * Applications that wish to modify such data must perform all + * updates via this method. + * <p> + * The data to be modified has to be within the boundary of the + * subregion + * specified by the offset (x, y) and the dimension (width*height). + * It is illegal to modify data outside this boundary. + * If any referenced data is modified outisde the updateData + * method, or any data outside the specified boundary is modified, + * the results are undefined. + * <p> + * @param updater object whose updateData callback method will be + * called to update the data referenced by this ImageComponent2D object. + * @param x starting X offset of the subregion. + * @param y starting Y offset of the subregion. + * @param width width of the subregion. + * @param height height of the subregion. + * + * @exception CapabilityNotSetException if the appropriate capability + * is not set, and this object is part of a live or compiled scene graph + * @exception IllegalStateException if the data access mode is + * <code>BY_COPY</code>. + * @exception IllegalArgumentException if <code>width</code> or + * <code>height</code> of + * the subregion exceeds the dimension of the image of this object. + * @exception IllegalArgumentException if <code>x</code> < 0, or + * (<code>x</code> + <code>width</code>) > width of this object, or + * <code>y</code> < 0, or + * (<code>y</code> + <code>height</code>) > height of this object. + * + * @since Java 3D 1.3 + */ + public void updateData(Updater updater, + int x, int y, + int width, int height) { + + if (isLiveOrCompiled() && + !this.getCapability(ALLOW_IMAGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("ImageComponent2D1")); + } + + if (!((ImageComponent2DRetained)this.retained).isByReference()) { + throw new IllegalStateException( + J3dI18N.getString("ImageComponent2D2")); + } + + int w = ((ImageComponent2DRetained)this.retained).getWidth(); + int h = ((ImageComponent2DRetained)this.retained).getHeight(); + + if ((x < 0) || (y < 0) || ((x + width) > w) || ((y + height) > h)) { + throw new IllegalArgumentException( + J3dI18N.getString("ImageComponent2D3")); + } + + ((ImageComponent2DRetained)this.retained).updateData( + updater, x, y, width, height); + } + + /** + * Creates a retained mode ImageComponent2DRetained object that this + * ImageComponent2D component object will point to. + */ + void createRetained() { + this.retained = new ImageComponent2DRetained(); + this.retained.setSource(this); + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + ImageComponent2DRetained rt = (ImageComponent2DRetained) retained; + + ImageComponent2D img = new ImageComponent2D(rt.format, + rt.width, + rt.height, + rt.byReference, + rt.yUp); + img.duplicateNodeComponent(this); + return img; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> + * into the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + RenderedImage img = ((ImageComponent2DRetained) + originalNodeComponent.retained).getImage(); + + if (img != null) { + ((ImageComponent2DRetained) retained).set(img); + } + } + + /** + * The ImageComponent2D.Updater interface is used in updating image data + * that is accessed by reference from a live or compiled ImageComponent + * object. Applications that wish to modify such data must define a + * class that implements this interface. An instance of that class is + * then passed to the <code>updateData</code> method of the + * ImageComponent object to be modified. + * + * @since Java 3D 1.3 + */ + public static interface Updater { + /** + * Updates image data that is accessed by reference. + * This method is called by the updateData method of an + * ImageComponent object to effect + * safe updates to image data that + * is referenced by that object. Applications that wish to modify + * such data must implement this method and perform all updates + * within it. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * + * @param imageComponent the ImageComponent object being updated. + * @param x starting X offset of the subregion. + * @param y starting Y offset of the subregion. + * @param width width of the subregion. + * @param height height of the subregion. + * + * @see ImageComponent2D#updateData + */ + public void updateData(ImageComponent2D imageComponent, + int x, int y, + int width, int height); + } + +} diff --git a/src/classes/share/javax/media/j3d/ImageComponent2DRetained.java b/src/classes/share/javax/media/j3d/ImageComponent2DRetained.java new file mode 100644 index 0000000..b390e67 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ImageComponent2DRetained.java @@ -0,0 +1,1384 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.image.*; +import java.awt.color.ColorSpace; + +/** + * This class defines a 2D image component. + * This is used for texture images, background images and raster components + * of Shape3D nodes. + */ + +class ImageComponent2DRetained extends ImageComponentRetained { + private int rasterRefCnt = 0; // number of raster using this object + private int textureRefCnt = 0; // number of texture using this object + + private DetailTextureImage detailTexture = null; // will reference a + // DetailTexture object if + // this image is being + // referenced as a detail image + + // use in D3D to map object to surface pointer + int hashId; + native void freeD3DSurface(int hashId); + + float[] lastAlpha = new float[1]; + + static final double EPSILON = 1.0e-6; + + // dirty mask to track if the image has been changed since the last + // alpha update. The nth bit in the mask represents the dirty bit + // of the image for screen nth. If nth bit is set, then the image + // needs to be updated with the current alpha values. + int imageChanged = 0; + + ImageComponent2DRetained() { + hashId = hashCode(); + } + + /** + * Copies the specified BufferedImage to this 2D image component object. + * @param image BufferedImage object containing the image. + * The format and size must be the same as the current format in this + * ImageComponent2D object. + */ + final void set(BufferedImage image) { + int width = image.getWidth(null); + int height = image.getHeight(null); + + if (width != this.width) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent2DRetained0")); + + if (height != this.height) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent2DRetained1")); + + int imageBytes; + // Note, currently only EXT_ABGR and EXT_BGR are not copied, if + // its going to be copied, do it at set time (at this time, the + // renderer is running in parallel), if we delay it until + // renderBin:updateObject time (when it calls evaluateExtension), + // we are stalling the renderer + geomLock.getLock(); + if (!byReference || (byReference && willBeCopied(image))) { + imageBytes = height * width * bytesPerPixelIfStored; + if (usedByTexture || ! usedByRaster) { + // ==> (usedByTexture) || (! usedByTexture && ! usedByRaster) + + if (imageYup == null || imageYup.length < imageBytes) { + imageYup = new byte[imageBytes]; + imageYupAllocated = true; + } + + // buffered image -> imageYup + storedYupFormat = internalFormat; + bytesPerYupPixelStored = getBytesStored(storedYupFormat); + copyImage(image, imageYup, true, 0, storedYupFormat, + bytesPerYupPixelStored); + imageYupClass = BUFFERED_IMAGE; + } + + if (usedByRaster) { + imageYdownClass = BUFFERED_IMAGE; + storedYdownFormat = internalFormat; + bytesPerYdownPixelStored = getBytesStored(storedYdownFormat); + + if (imageYdown[0] == null || imageYdown[0].length < imageBytes) { + imageYdown[0] = new byte[imageBytes]; + imageYdownAllocated = true; + } + + if (imageYup != null){ + //imageYup -> imageYdown + setImageYdown(imageYup, imageYdown[0]); + } else { + // buffered image -> imageYdown + copyImage(image, imageYdown[0], false, 0, + storedYdownFormat, bytesPerYdownPixelStored); + } + } + // If its byRef case, but we copied because we know that + // the underlying native API cannot support this case! + if (byReference) { + bImage[0] = image; + if (usedByTexture || !usedByRaster) + imageYupCacheDirty = false; + else + imageYupCacheDirty = true; + + if (usedByRaster) + imageYdownCacheDirty = false; + else + imageYdownCacheDirty = true; + + } + else { + imageDirty[0] = true; + } + + } + // If its by reference, then make a copy only if necessary + else { + imageYupCacheDirty = true; + imageYdownCacheDirty = true; + bImage[0] = image; + } + imageChanged = 0xffff; + lastAlpha[0] = 1.0f; + geomLock.unLock(); + + if (source.isLive()) { + freeSurface(); + + // send a IMAGE_CHANGED message in order to + // notify all the users of the change + sendMessage(IMAGE_CHANGED, null); + } + } + + + boolean willBeCopied(RenderedImage image) { + return shouldImageBeCopied(getImageType(image), + (Canvas3D.EXT_ABGR|Canvas3D.EXT_BGR), image); + } + + + + // NOTE, IMPORTANT: any additions to the biType tested , should be added to + // the willBeCopied() function + final boolean shouldImageBeCopied(int biType, int ext, RenderedImage ri) { + + if (!byReference) + return true; + + if ((((ext & Canvas3D.EXT_ABGR) != 0) && + ((biType == BufferedImage.TYPE_4BYTE_ABGR) && + (format == ImageComponent.FORMAT_RGBA8))) || + (((ext & Canvas3D.EXT_BGR) != 0) && + ((biType == BufferedImage.TYPE_3BYTE_BGR) && + (format == ImageComponent.FORMAT_RGB))) || + ((biType == BufferedImage.TYPE_BYTE_GRAY) && + (format == ImageComponent.FORMAT_CHANNEL8)) || + (is4ByteRGBAOr3ByteRGB(ri))) { + /* ||TODO: Don't do short for now! + ((biType == BufferedImage.TYPE_USHORT_GRAY) && + (format == ImageComponent.FORMAT_CHANNEL8) + */ + + return false; + } + return true; + } + + final int getStoredFormat(int biType, RenderedImage ri) { + int f = 0; + switch(biType) { + case BufferedImage.TYPE_4BYTE_ABGR: + f= BYTE_ABGR; + break; + case BufferedImage.TYPE_BYTE_GRAY: + f= BYTE_GRAY; + break; + case BufferedImage.TYPE_USHORT_GRAY: + f = USHORT_GRAY; + break; + case BufferedImage.TYPE_3BYTE_BGR: + f = BYTE_BGR; + break; + case BufferedImage.TYPE_CUSTOM: + if (is4ByteRGBAOr3ByteRGB(ri)) { + SampleModel sm = ri.getSampleModel(); + if (sm.getNumBands() == 3) { + f = BYTE_RGB; + } + else { + f = BYTE_RGBA; + } + } + break; + default: + // Should never come here + } + return f; + } + + final void set(RenderedImage image) { + + if (image instanceof BufferedImage) { + set(((BufferedImage)image)); + } + else { + /* + // Create a buffered image from renderImage + ColorModel cm = image.getColorModel(); + WritableRaster wRaster = image.copyData(null); + BufferedImage bi = new BufferedImage(cm, + wRaster, + cm.isAlphaPremultiplied() + ,null); + set(bi); + } + */ + int width = image.getWidth(); + int height = image.getHeight(); + + if (width != this.width) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent2DRetained0")); + + if (height != this.height) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent2DRetained1")); + + int imageBytes; + // Note, currently only EXT_ABGR and EXT_BGR are not copied, if + // its going to be copied, do it at set time (at this time, the + // renderer is running in parallel), if we delay it until + // renderBin:updateObject time (when it calls evaluateExtension), + // we are stalling the renderer + geomLock.getLock(); + if (!byReference ||(byReference && willBeCopied(image))) { + imageBytes = height * width * bytesPerPixelIfStored; + if (usedByTexture || ! usedByRaster) { + if (imageYup == null || imageYup.length < imageBytes) { + imageYup = new byte[imageBytes]; + imageYupAllocated = true; + } + + // buffered image -> imageYup + storedYupFormat = internalFormat; + bytesPerYupPixelStored = getBytesStored(storedYupFormat); + copyImage(image, imageYup, true, 0, storedYupFormat, + bytesPerYupPixelStored); + imageYupClass = BUFFERED_IMAGE; + } + + if (usedByRaster) { + + imageYdownClass = BUFFERED_IMAGE; + storedYdownFormat = internalFormat; + bytesPerYdownPixelStored = getBytesStored(storedYdownFormat); + + if (imageYdown[0] == null || imageYdown[0].length < imageBytes) { + imageYdown[0] = new byte[imageBytes]; + imageYdownAllocated = true; + } + + if (imageYup != null) + //imageYup -> imageYdown + setImageYdown(imageYup, imageYdown[0]); + else + // buffered image -> imageYdown + copyImage(image, imageYdown[0], false, 0, + storedYdownFormat, bytesPerYdownPixelStored); + } + if (byReference) { + bImage[0] = image; + if (usedByTexture || !usedByRaster) + imageYupCacheDirty = false; + else + imageYupCacheDirty = true; + + if (usedByRaster) + imageYdownCacheDirty = false; + else + imageYdownCacheDirty = true; + } + else { + imageDirty[0] = true; + } + } + // If its by reference, then make a copy only if necessary + else { + imageYupCacheDirty = true; + imageYdownCacheDirty = true; + bImage[0] = image; + } + + } + imageChanged = 0xffff; + lastAlpha[0] = 1.0f; + geomLock.unLock(); + if (source.isLive()) { + freeSurface(); + sendMessage(IMAGE_CHANGED, null); + } + } + + /** + * Retrieves a copy of the image in this ImageComponent2D object. + * @return a new BufferedImage object created from the image in this + * ImageComponent2D object + */ + final RenderedImage getImage() { + if (!byReference && imageDirty[0]) { + imageDirty[0] = false; + retrieveBufferedImage(0); + } + return bImage[0]; + } + + // allocate storage for imageYdown + // set imageYdown and free imageYup if necessary + final void setRasterRef() { + // Ref case will be handled by evaluateExtension(); + if (usedByRaster) + return; + + usedByRaster = true; + + if (format == ImageComponent.FORMAT_CHANNEL8) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent2DRetained2")); + + if (!byReference) { + if (imageYdown[0] == null && imageYup != null) { + imageYdown[0] = new byte[height * width * bytesPerYupPixelStored]; + imageYdownAllocated = true; + + // imageYup -> imageYdown + imageYdownClass = BUFFERED_IMAGE; + storedYdownFormat = storedYupFormat; + bytesPerYdownPixelStored = bytesPerYupPixelStored; + setImageYdown(imageYup, imageYdown[0]); + } + if (usedByTexture == false) { + imageYup = null; + imageYupAllocated = false; + } + + } + else { + if (willBeCopied(bImage[0])) { + geomLock.getLock(); + if (imageYdownCacheDirty) { + if (imageYdown[0] == null) { + + if (imageYup != null) { + storedYdownFormat = storedYupFormat; + bytesPerYdownPixelStored = bytesPerYupPixelStored; + imageYdown[0] =new byte[height*width *bytesPerYdownPixelStored]; + setImageYdown(imageYup, imageYdown[0]); + } + else { + imageYdown[0] = new byte[height * width * bytesPerPixelIfStored]; + bytesPerYdownPixelStored = bytesPerPixelIfStored; + storedYdownFormat = internalFormat; + + if (bImage[0] instanceof BufferedImage) { + copyImage(((BufferedImage)bImage[0]), + imageYdown[0], false, 0, + storedYdownFormat, + bytesPerYdownPixelStored); + } + else { + copyImage(bImage[0], imageYdown[0], false, 0, + storedYdownFormat, + bytesPerYdownPixelStored); + } + } + imageYdownClass = BUFFERED_IMAGE; + imageYdownAllocated = true; + } + imageYdownCacheDirty = false; + } + geomLock.unLock(); + } + else { + geomLock.getLock(); + imageYdownCacheDirty = true; + geomLock.unLock(); + } + /* + // Can't do this - since I don't know which extension + // will be supported, if Ydown is going away then + // this code will be useful + else if (yUp) { + geomLock.getLock(); + if (imageYdownCacheDirty) { + storeRasterImageWithFlip(bImage[0]); + imageYdownCacheDirty = false; + } + geomLock.unLock(); + } + */ + } + } + + // allocate storage for imageYup + // set imageYup and free imageYdown if necessary + final void setTextureRef() { + // Ref case will be handled by evaluateExtension(); + if (usedByTexture) + return; + + usedByTexture = true; + + if (!byReference) { + + if (imageYup == null && imageYdown[0] != null) { + storedYupFormat = storedYdownFormat; + bytesPerYupPixelStored = bytesPerYdownPixelStored; + imageYup = new byte[height * width * bytesPerYupPixelStored]; + // imageYdown -> imageYup + setImageYup(imageYdown[0], imageYup); + imageYupClass = BUFFERED_IMAGE; + imageYupAllocated = true; + } + if (usedByRaster == false) { + imageYdown[0] = null; + imageYdownAllocated = false; + } + + } + // If the image will not be stored by reference, because + // the format is not supported by the underlying API + else { + if (willBeCopied(bImage[0])) { + geomLock.getLock(); + if (imageYupCacheDirty) { + if (imageYup == null) { + if (imageYdown[0] != null) { + // imageYdown -> imageYup + storedYupFormat = storedYdownFormat; + bytesPerYupPixelStored = bytesPerYdownPixelStored; + imageYup = new byte[height * width * bytesPerYupPixelStored]; + setImageYup(imageYdown[0], imageYup); + } + else { + imageYup = new byte[height * width * bytesPerPixelIfStored]; + bytesPerYupPixelStored = bytesPerPixelIfStored; + storedYupFormat = internalFormat; + + if (bImage[0] instanceof BufferedImage) { + copyImage(((BufferedImage)bImage[0]), imageYup, + true, 0, storedYupFormat, + bytesPerYupPixelStored); + } + else { + copyImage(bImage[0], imageYup, true, 0, + storedYupFormat, + bytesPerYupPixelStored); + } + } + imageYupClass = BUFFERED_IMAGE; + imageYupAllocated = true; + } + imageYupCacheDirty = false; + } + geomLock.unLock(); + } + else { + geomLock.getLock(); + imageYupCacheDirty = true; + geomLock.unLock(); + } + /* + // Can't do this - since I don't know which extension + // will be supported, if Ydown is going away then + // this code will be useful + + // If Image will not be stored by reference because + // of wrong orienetation + else if (!yUp) { + geomLock.getLock(); + if (imageYupCacheDirty) { + storeTextureImageWithFlip(bImage[0]); + imageYupCacheDirty = false; + } + geomLock.unLock(); + } + */ + } + + } + + + // copy imageYup to imageYdown in reverse scanline order + final void setImageYdown(byte[] src, byte[] dst) { + int scanLineSize = width * bytesPerYdownPixelStored; + int srcOffset, dstOffset, i; + + for (srcOffset = (height - 1) * scanLineSize, dstOffset = 0, + i = 0; i < height; i++, + srcOffset -= scanLineSize, dstOffset += scanLineSize) { + + System.arraycopy(src, srcOffset, dst, dstOffset, + scanLineSize); + } + + } + + // Preserve the incoming format of thre renderImage, but maybe + // flip the image or make a plain copy + // Used only for raster + final void copyRImage(RenderedImage image, byte[] dst, boolean flip, + int bPerPixel) { + + int numX = image.getNumXTiles(); + int numY = image.getNumYTiles(); + int tilew = image.getTileWidth(); + int tileh = image.getTileHeight(); + int i, j, h; + java.awt.image.Raster ras; + int tileLineBytes = tilew * bPerPixel; + int x = image.getMinTileX(); + int y = image.getMinTileY(); + int srcOffset, dstOffset; + int dstLineBytes = 0; + int tileStart; + int tileBytes = tileh * tilew * numX * bPerPixel; + int dstStart; + int tmph, tmpw, curw, curh; + int rowOffset, colOffset; + int sign; + + if (flip) { + dstLineBytes =width * bPerPixel; + tileStart = (height - 1) * dstLineBytes; + dstLineBytes = -(dstLineBytes); + } + else { + tileStart = 0; + dstLineBytes = width * bPerPixel; + } + // convert from Ydown to Yup for texture + int minX = image.getMinX(); + int minY = image.getMinY(); + int xoff = image.getTileGridXOffset(); + int yoff = image.getTileGridYOffset(); + int endXTile = x * tilew + xoff+tilew; + int endYTile = y * tileh + yoff+tileh; + tmpw = width; + tmph = height; + // Check if the width is less than the tile itself .. + curw = (endXTile - minX); + curh = (endYTile - minY); + + if (tmpw < curw) { + curw = tmpw; + } + + if (tmph < curh) { + curh = tmph; + } + int startw = curw ; + + rowOffset = (tilew - curw) * bPerPixel; + colOffset = tilew * (tileh - curh) * bPerPixel; + int bytesCopied = 0; + srcOffset = rowOffset + colOffset; + + + for (i = y; i < y+numY; i++) { + dstStart = tileStart; + curw = startw; + tmpw = width; + for (j = x; j < x+numX; j++) { + ras = image.getTile(j,i); + byte[] src = ((DataBufferByte)ras.getDataBuffer()).getData(); + dstOffset = dstStart; + bytesCopied = curw * bPerPixel; + for (h = 0;h < curh; h++) { + System.arraycopy(src, srcOffset, dst, dstOffset, + bytesCopied); + srcOffset += tileLineBytes; + dstOffset += dstLineBytes; + } + srcOffset = colOffset; + dstStart += curw * bPerPixel; + tmpw -= curw; + if (tmpw < tilew) + curw = tmpw; + else + curw = tilew; + } + srcOffset = rowOffset; + colOffset = 0; + tileStart += curh * dstLineBytes; + tmph -= curh; + if (tmph < tileh) + curh = tmph; + else + curh = tileh; + } + } + + // copy imageYdown to imageYup in reverse scanline order + final void setImageYup(byte[] src, byte[] dst) { + int scanLineSize = width * bytesPerYupPixelStored; + int srcOffset, dstOffset, i; + + for (srcOffset = 0, dstOffset = (height - 1) * scanLineSize, + i = 0; i < height; i++, + srcOffset += scanLineSize, dstOffset -= scanLineSize) { + + System.arraycopy(src, srcOffset, dst, dstOffset, + scanLineSize); + } + } + + // Lock out user thread from modifying usedByRaster and + // usedByTexture variables by using synchronized routines + final void evaluateExtensions(int ext) { + int i; + int imageBytes; + RenderedImage image = bImage[0]; + + // System.out.println("!!!!!!!!!!!!!imageYupCacheDirty = "+imageYupCacheDirty); + // System.out.println("!!!!!!!!!!!!!imageYdownCacheDirty = "+imageYdownCacheDirty); + // System.out.println("!!!!!!!!!!!!!usedByTexture = "+usedByTexture); + // System.out.println("!!!!!!!!!!!!!usedByRaster = "+usedByRaster); + + + if (!imageYupCacheDirty && !imageYdownCacheDirty) { + return; + } + + int riType = getImageType(image); + + // Thread.dumpStack(); + if (usedByTexture == true || ! usedByRaster) { + // If the image is already allocated, then return + // nothing to do! + // Since this is a new image, the app may have changed the image + // when it was not live, so re-compute, until the image is allocated + // for this pass! + // System.out.println("!!!!!!!!!!!!!imageYupCacheDirty = "+imageYupCacheDirty); + if (!imageYupCacheDirty) { + evaluateRaster(riType, ext); + return; + } + if (shouldImageBeCopied(riType, ext, image)) { + + imageBytes = height * width * bytesPerPixelIfStored; + if (imageYup == null || !imageYupAllocated) { + imageYup = new byte[imageBytes]; + imageYupAllocated = true; + } + // buffered image -> imageYup + bytesPerYupPixelStored = bytesPerPixelIfStored; + storedYupFormat = internalFormat; + copyImage(image, imageYup, true, 0, + storedYupFormat, bytesPerYupPixelStored); + imageYupClass = BUFFERED_IMAGE; + imageYupCacheDirty = false; + } + else { + // This image is already valid .. + if (!imageYupCacheDirty) { + evaluateRaster(riType, ext); + return; + } + storedYupFormat = getStoredFormat(riType, image); + bytesPerYupPixelStored = getBytesStored(storedYupFormat); + + // It does not have to be copied, but we + // have to copy because the incoming image is + // ydown + if (!yUp) { + storeTextureImageWithFlip(image); + } + else { + if (image instanceof BufferedImage) { + byte[] tmpImage = ((DataBufferByte)((BufferedImage)image).getRaster().getDataBuffer()).getData(); + imageYup = tmpImage; + imageYupAllocated = false; + imageYupClass = BUFFERED_IMAGE; + } + else { + numXTiles = image.getNumXTiles(); + numYTiles = image.getNumYTiles(); + tilew = image.getTileWidth(); + tileh = image.getTileHeight(); + minTileX = image.getMinTileX(); + minTileY = image.getMinTileY(); + minX = image.getMinX(); + minY = image.getMinY(); + tileGridXOffset =image.getTileGridXOffset(); + tileGridYOffset = image.getTileGridYOffset(); + imageYupAllocated = false; + imageYupClass = RENDERED_IMAGE; + imageYup = null; + } + } + + } + if (usedByRaster == false) { + imageYdown[0] = null; + imageYdownAllocated = false; + } + } + evaluateRaster(riType, ext); + } + + void evaluateRaster(int riType, int ext) { + int i; + int imageBytes; + RenderedImage image = bImage[0]; + + if (usedByRaster) { + // If the image is already allocated, then return + // nothing to do! + if (!imageYdownCacheDirty) { + return; + } + if (shouldImageBeCopied(riType, ext, image)) { + // System.out.println("Raster Image is copied"); + imageBytes = height * width * bytesPerPixelIfStored; + if (imageYdown[0] == null || !imageYdownAllocated || imageYdown[0].length < imageBytes){ + imageYdown[0] = new byte[imageBytes]; + imageYdownAllocated = true; + } + if (imageYup != null) { + storedYdownFormat = storedYupFormat; + bytesPerYdownPixelStored = bytesPerYupPixelStored; + setImageYdown(imageYup, imageYdown[0]); + } + else { + // buffered image -> imageYup + storedYdownFormat = internalFormat; + bytesPerYdownPixelStored = bytesPerPixelIfStored; + copyImage(image, imageYdown[0], false, 0, + storedYdownFormat, bytesPerYdownPixelStored); + } + imageYdownCacheDirty = false; + imageYdownClass = BUFFERED_IMAGE; + } + else { + // This image is already valid .. + if (!imageYdownCacheDirty) { + return; + } + storedYdownFormat = getStoredFormat(riType, image); + bytesPerYdownPixelStored = getBytesStored(storedYdownFormat); + if (yUp) { + storeRasterImageWithFlip(image); + } + else { + if (image instanceof BufferedImage) { + byte[] tmpImage = ((DataBufferByte)((BufferedImage)image).getRaster().getDataBuffer()).getData(); + imageYdown[0] = tmpImage; + imageYdownAllocated = false; + imageYdownClass = BUFFERED_IMAGE; + // System.out.println("Raster Image is stored by ref"); + } + else { + // Right now, always copy since opengl rasterpos is + // too restrictive + imageBytes = width*height*bytesPerYdownPixelStored; + if (imageYdown[0] == null || !imageYdownAllocated ||imageYdown[0].length < imageBytes){ + imageYdown[0] = new byte[imageBytes]; + imageYdownAllocated = true; + } + imageYdownClass = BUFFERED_IMAGE; + copyRImage(image,imageYdown[0], false, bytesPerYdownPixelStored); + // System.out.println("Copying by ref RImage"); + + /* + numXTiles = image.getNumXTiles(); + numYTiles = image.getNumYTiles(); + tilew = image.getTileWidth(); + tileh = image.getTileHeight(); + minTileX = image.getMinTileX(); + minTileY = image.getMinTileY(); + imageYdownAllocated = false; + imageYdownClass = RENDERED_IMAGE; + imageYdown = null; + */ + } + imageYdownCacheDirty = false; + } + + + } + if (usedByTexture == false) { + imageYup = null; + imageYupAllocated = false; + } + } + } + + void storeRasterImageWithFlip(RenderedImage image) { + int imageBytes; + + if (image instanceof BufferedImage) { + imageBytes = width*height*bytesPerYdownPixelStored; + if (imageYdown[0] == null || !imageYdownAllocated ||imageYdown[0].length < imageBytes){ + imageYdown[0] = new byte[imageBytes]; + imageYdownAllocated = true; + } + imageYdownClass = BUFFERED_IMAGE; + imageYdownCacheDirty = false; + byte[] tmpImage = ((DataBufferByte)((BufferedImage)image).getRaster().getDataBuffer()).getData(); + setImageYdown(tmpImage, imageYdown[0]); + } + else { + // Right now, always copy since opengl rasterpos is + // too restrictive + + imageBytes = width*height*bytesPerYdownPixelStored; + if (imageYdown[0] == null || !imageYdownAllocated ||imageYdown[0].length < imageBytes){ + imageYdown[0] = new byte[imageBytes]; + imageYdownAllocated = true; + } + imageYdownClass = BUFFERED_IMAGE; + imageYdownCacheDirty = false; + copyRImage(image, imageYdown[0], true, bytesPerYdownPixelStored); + } + } + + void storeTextureImageWithFlip(RenderedImage image) { + int imageBytes; + + if (image instanceof BufferedImage) { + byte[] tmpImage = ((DataBufferByte)((BufferedImage)image).getRaster().getDataBuffer()).getData(); + + if (imageYup == null || !imageYupAllocated) { + imageBytes = width*height*bytesPerYupPixelStored; + imageYup = new byte[imageBytes]; + imageYupAllocated = true; + } + imageYupClass = BUFFERED_IMAGE; + setImageYup(tmpImage, imageYup); + imageYupCacheDirty = false; + + } + else { + if (imageYup == null || !imageYupAllocated) { + imageBytes = width*height*bytesPerYupPixelStored; + imageYup = new byte[imageBytes]; + imageYupAllocated = true; + } + imageYupClass = BUFFERED_IMAGE; + copyRImage(image, imageYup, true, bytesPerYupPixelStored); + imageYupCacheDirty = false; + } + } + + void setLive(boolean inBackgroundGroup, int refCount) { + super.setLive(inBackgroundGroup, refCount); + } + + void clearLive(int refCount) { + super.clearLive(refCount); + if (this.refCount <= 0) { + freeSurface(); + } + } + + void freeSurface() { + if (VirtualUniverse.mc.isD3D()) { + freeD3DSurface(hashId); + } + } + + protected void finalize() { + // For Pure immediate mode, there is no clearLive so + // surface will free when JVM do GC + freeSurface(); + } + void updateAlpha(Canvas3D cv, int screen, float alpha) { + // if alpha is smaller than EPSILON, set it to EPSILON, so that + // even if alpha is equal to 0, we will not completely lose + int i, j; + byte byteAlpha; + float rndoff = 0.0f; + + // the original alpha value + if (alpha <= EPSILON) { + alpha = (float)EPSILON; + } + // System.out.println("========> updateAlpha, this = "+this); + // Lock out the other renderers .. + synchronized (this) { + // If by reference, the image has been copied, but aset has occured + // or if the format is not RGBA, then copy + // Thread.dumpStack(); + if (isByReference() && ((storedYdownFormat != internalFormat) || ((imageChanged & 1) != 0))) { + int imageBytes = height * width * bytesPerPixelIfStored; + if (imageYdown[0] == null || !imageYdownAllocated|| imageYdown[0].length < imageBytes) + imageYdown[0] = new byte[imageBytes]; + bytesPerYdownPixelStored = bytesPerPixelIfStored; + storedYdownFormat = internalFormat; + copyImage(bImage[0],imageYdown[0], false, 0, + storedYdownFormat, bytesPerYdownPixelStored); + imageYdownCacheDirty = false; + imageYdownClass = BUFFERED_IMAGE; + imageYdownAllocated = true; + imageChanged &= ~1; + freeSurface(); + } + + // allocate an entry for the last alpha of the screen if needed + if (lastAlpha == null) { + lastAlpha = new float[screen+1]; + lastAlpha[screen] = 1.0f; + } + else if (lastAlpha.length <= screen) { + float[] la = new float[screen+1]; + for (i = 0; i < lastAlpha.length; i++) { + la[i] = lastAlpha[i]; + } + lastAlpha = la; + lastAlpha[screen] = 1.0f; + } + // allocate a copy of the color data for the screen if needed. + // this piece of code is mainly for multi-screens case + if (imageYdown.length <= screen) { + byte[][] bdata = new byte[screen+1][]; + byte[] idata; + int refScreen = -1; + + int imageBytes = height * width * bytesPerYdownPixelStored; + idata = bdata[screen] = new byte[imageBytes]; + for (i = 0; i < imageYdown.length; i++) { + bdata[i] = imageYdown[i]; + if (Math.abs(lastAlpha[i] - alpha) < EPSILON) { + refScreen = i; + } + } + + if (noAlpha) { + byteAlpha = (byte) (alpha * 255.0f + 0.5); + for (j=3,i=0; i< width * height; i++,j+=4) { + idata[j] = byteAlpha; + } + } + else { + + // copy the data from a reference screen which has the closest + // alpha values + if (refScreen >= 0) { + System.arraycopy(imageYdown[refScreen], 0, idata, 0, imageBytes); + lastAlpha[screen] = lastAlpha[refScreen]; + } + else { + float m = alpha/lastAlpha[0]; + if (m < 1.0f) + rndoff = 0.5f; + else + rndoff = -0.5f; + + byte[] srcData = imageYdown[0]; + for (i = 0, j = 0; i < width * height; i++, j+= 4) { + System.arraycopy(srcData, j, idata, j, 3); + idata[j+3] =(byte)( ((int)srcData[j+3] & 0xff) * m + rndoff); + } + lastAlpha[screen] = alpha; + } + } + imageYdown = bdata; + + imageChanged &= ~(1 << screen); + freeSurface(); + return; + } + + if ((imageChanged & (1<< screen)) == 0) { + // color data is not modified + // if alpha is different, update the alpha values + int val = -1; + if (Math.abs(lastAlpha[screen] - alpha) > EPSILON) { + byte[] idata = imageYdown[screen]; + if (noAlpha) { + byteAlpha = (byte) (alpha * 255.0f + 0.5); + for (j=3,i=0; i< width * height; i++,j+=4) { + idata[j] = byteAlpha; + } + } + else { + float m = alpha/lastAlpha[screen]; + if (m < 1.0f) + rndoff = 0.5f; + else + rndoff = -0.5f; + + for (i = 0, j = 3; i < width * height; i++, j+= 4) { + idata[j] =(byte)( ((int)idata[j] & 0xff) * m + rndoff); + } + } + freeSurface(); + } + } + else { + // color data is modified + if (screen == 0) { + // just update alpha values since screen 0 data is + // already updated in copyImage() + byte[] idata = imageYdown[0]; + if (noAlpha) { + byteAlpha = (byte) (alpha * 255.0f + 0.5); + for (j=3,i=0; i< width * height; i++,j+=4) { + idata[j] = byteAlpha; + } + } + else { + for (i = 0, j = 3; i < width * height; i++, j+= 4) { + idata[j] =(byte)( ((int)idata[j] & 0xff) * alpha + 0.5); + } + } + + } + else { + // update color values from screen 0 data + float m; + byte[] ddata = imageYdown[screen]; + if (noAlpha) { + byteAlpha = (byte) (alpha * 255.0f + 0.5); + for (j=3,i=0; i< width * height; i++,j+=4) { + ddata[j] = byteAlpha; + } + } + else { + if ((imageChanged & 1) == 0) { + // alpha is up to date in screen 0 + m = alpha / lastAlpha[0]; + } + else { + m = alpha; + } + + if (m < 1.0f) + rndoff = 0.5f; + else + rndoff = -0.5f; + + byte[] sdata = imageYdown[0]; + + for (i = 0, j = 0; i < width * height; i++, j+= 4) { + System.arraycopy(sdata, j, ddata, j, 3); + ddata[j+3] =(byte)( ((int)sdata[j+3] & 0xff) * m + rndoff); + } + } + } + freeSurface(); + } + lastAlpha[screen] = alpha; + imageChanged &= ~(1 << screen); + } + } + + + int getEffectiveBytesPerPixel() { + if (byReference) { + if (usedByTexture || !usedByRaster) + return bytesPerYupPixelStored; + else + return bytesPerYdownPixelStored; + } + else { + return bytesPerPixelIfStored; + } + } + + + int getEffectiveFormat() { + if (byReference) { + if (usedByTexture || !usedByRaster) + return storedYupFormat; + else + return storedYdownFormat; + } + else { + return internalFormat; + } + } + + /** + * retrieve image data from read buffer to ImageComponent's + * internal representation + */ + final void retrieveImage(byte[] buf, int wRead, int hRead) { + retrieveImage(buf, 0, 0, wRead, hRead); + } + + + /** + * retrieve a subimage data from read buffer to ImageComponent's + * internal representation + */ + final void retrieveImage(byte[] buf, int xRead, int yRead, int + wRead, int hRead) { + + int srcOffset, dstOffset, h,w,i,j; + int dstWidth; + + byte[] bdata; + + + // If byReference, then copy to the reference image immediately after + // readRaster or offscreenbuffer + + // In the by reference case, they should have set and image, before + // calling readRaster, so there should be a valid imageYup or imageYdown + // as used by texture or raster + + // Note the format of the glReadPixels is based on storedFormat + // so, we can do a direct copy + + int bpp = getEffectiveBytesPerPixel(); + int format = getEffectiveFormat(); + + if (!byReference) { + + dstWidth = width * bytesPerPixelIfStored; + if ((usedByTexture || !usedByRaster)&& (imageYup == null)) { + imageYup = new byte[height * dstWidth]; + bytesPerYupPixelStored = bytesPerPixelIfStored; + storedYupFormat = internalFormat; + imageYupAllocated = true; + } + if (usedByRaster && imageYdown[0] == null) { + imageYdown[0] = new byte[height * dstWidth]; + bytesPerYdownPixelStored = bytesPerPixelIfStored; + storedYdownFormat = internalFormat; + imageYdownAllocated = true; + } + } + + + int srcWidth = wRead * bpp; + int srcLineBytes = width * bpp; + + /* + System.out.println("bytesPerYdownPixelStored = "+bytesPerYdownPixelStored+" bpp = "+bpp); + System.out.println("storedYdownFormat = "+storedYdownFormat+" format = "+format); + System.out.println("bytesPerPixelIfStored = "+bytesPerPixelIfStored+" internalformat = "+internalFormat); + System.out.println("imageYup = "+imageYup+" imageYdown = "+imageYdown[0]); + System.out.println("===> usedByRaster = "+usedByRaster); + */ + + // array copy by scanline + + //position of a raster specifies the upper-left corner + // copy yUp -> yDown + imageDirty [0] = true; + + if (usedByRaster) { + dstWidth = width * bytesPerYdownPixelStored; + srcOffset = (yRead * srcLineBytes) + (xRead * bpp); + + dstOffset = ((height - yRead - 1)) * dstWidth + + (xRead * bytesPerYdownPixelStored); + // If by Reference and a copy has not been made ... + if (byReference && storedYdownFormat != internalFormat) { + bdata =((DataBufferByte) ((BufferedImage)bImage[0]).getRaster().getDataBuffer()).getData(); + imageDirty [0] = false; + } + else { + bdata = imageYdown[0]; + } + if (storedYdownFormat == format) { + for (h = 0; h < hRead; h++, + srcOffset += srcLineBytes, dstOffset -= dstWidth) { + System.arraycopy(buf, srcOffset, bdata, dstOffset, srcWidth); + } + } + else { // Would be one of the stored formats to RGBA + // Convert from one of the byRef formats to RGBA + switch(format) { + case BYTE_ABGR: + for (h = 0; h < hRead; h++, + srcOffset += srcLineBytes, dstOffset -= dstWidth) { + int offset = dstOffset; + for (w = 0; w < srcWidth; w +=bpp) { + bdata[offset++] = buf[srcOffset+w+3]; + bdata[offset++] = buf[srcOffset+w+2]; + bdata[offset++] = buf[srcOffset+w+1]; + bdata[offset++] = buf[srcOffset+w]; + + } + } + break; + case BYTE_BGR: + + for (h = 0; h < hRead; h++, + srcOffset += srcLineBytes, dstOffset -= dstWidth) { + int offset = dstOffset; + for (w = 0; w < srcWidth; w +=bpp) { + bdata[offset++] = buf[srcOffset+w+2]; + bdata[offset++] = buf[srcOffset+w+1]; + bdata[offset++] = buf[srcOffset+w]; + bdata[offset++] = (byte)0xff; + + } + } + break; + + } + } + } + + if (usedByTexture || !usedByRaster) { + imageYupCacheDirty = true; + dstWidth = width * bytesPerYupPixelStored; + srcOffset = (yRead * srcLineBytes) + (xRead * bpp); + + // If by Reference and a copy has not been made ... + if (byReference && storedYupFormat != internalFormat) { + bdata =((DataBufferByte) ((BufferedImage)bImage[0]).getRaster().getDataBuffer()).getData(); + imageDirty [0] = false; + } + else { + bdata = imageYup; + } + // If used by texture, then storedYupFormat is always equal to format + if (storedYupFormat == format) { + for (dstOffset = srcOffset, + h = 0; h < hRead; h++, + srcOffset += srcLineBytes, dstOffset += dstWidth) { + System.arraycopy(buf, srcOffset, bdata, dstOffset, srcWidth); + } + } + } + // If its by reference and a copy has been made, make the user's copy + // up-to-date + + if (byReference && imageDirty[0]) { + imageDirty [0] = false; + if (usedByTexture || !usedByRaster) { + copyBufferedImageWithFormatConversion(true, 0); + } + else { + copyBufferedImageWithFormatConversion(false, 0); + } + } + imageChanged = 0xffff; + lastAlpha[0] = 1.0f; + } + + /** + * Update data. + * x and y specifies the x & y offset of the image data in + * ImageComponent. It assumes that the origin is (0, 0). + */ + void updateData(ImageComponent2D.Updater updater, + int x, int y, int width, int height) { + + geomLock.getLock(); + + // call the user supplied updateData method to update the data + updater.updateData((ImageComponent2D)source, x, y, width, height); + + // update the internal copy of the image data if a copy has been + // made + if (imageYupAllocated) { + copyImage(bImage[0], (x + bImage[0].getMinX()), + (y + bImage[0].getMinY()), imageYup, x, y, + true, 0, width, height, storedYupFormat, + bytesPerYupPixelStored); + } + + + if (imageYdownAllocated) { + copyImage(bImage[0], (x + bImage[0].getMinX()), + (y + bImage[0].getMinY()), imageYdown[0], x, y, + false, 0, width, height, storedYdownFormat, + bytesPerYdownPixelStored); + } + + imageDirty[0] = true; + + geomLock.unLock(); + + + if (source.isLive()) { + + //TODO: check whether this is needed + freeSurface(); + + // send a SUBIMAGE_CHANGED message in order to + // notify all the users of the change + + ImageComponentUpdateInfo info; + + info = VirtualUniverse.mc.getFreeImageUpdateInfo(); + info.x = x; + info.y = y; + info.z = 0; + info.width = width; + info.height = height; + + sendMessage(SUBIMAGE_CHANGED, info); + } + } + + void setSubImage(RenderedImage image, int width, int height, + int srcX, int srcY, int dstX, int dstY) { + + geomLock.getLock(); + + if (imageYupAllocated) { + copyImage(image, srcX, srcY, imageYup, dstX, dstY, + true, 0, width, height, storedYupFormat, + bytesPerYupPixelStored); + } + + if (imageYdownAllocated) { + copyImage(image, srcX, srcY, imageYdown[0], + dstX, dstY, false, 0, width, height, + storedYdownFormat, bytesPerYdownPixelStored); + } + + imageDirty[0] = true; + + geomLock.unLock(); + + + if (source.isLive()) { + + // TODO: check whether this is needed + freeSurface(); + + // send a SUBIMAGE_CHANGED message in order to + // notify all the users of the change + + ImageComponentUpdateInfo info; + + info = VirtualUniverse.mc.getFreeImageUpdateInfo(); + info.x = dstX; + info.y = dstY; + info.z = 0; + info.width = width; + info.height = height; + + sendMessage(SUBIMAGE_CHANGED, info); + } + } + + synchronized void updateMirrorObject(int component, Object value) { + + super.updateMirrorObject(component, value); + + if (detailTexture != null) { + if (((component & IMAGE_CHANGED) != 0) || + ((component & SUBIMAGE_CHANGED) != 0)) { + + // notify detailTexture of image change + + detailTexture.notifyImageComponentImageChanged(this, value); + } + } + } + + + synchronized void setDetailTexture(DetailTextureImage tex) { + detailTexture = tex; + } + + synchronized DetailTextureImage getDetailTexture() { + if (detailTexture == null) { + detailTexture = new DetailTextureImage(this); + } + return detailTexture; + } +} diff --git a/src/classes/share/javax/media/j3d/ImageComponent3D.java b/src/classes/share/javax/media/j3d/ImageComponent3D.java new file mode 100644 index 0000000..6404784 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ImageComponent3D.java @@ -0,0 +1,711 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; + +/** + * This class defines a 3D image component. This is used for texture + * images. + * Prior to Java 3D 1.2, only BufferedImage objects could be used as + * the input to an ImageComponent3D object. As of Java 3D 1.2, an + * ImageComponent3D accepts an array of arbitrary RenderedImage + * objects (BufferedImage is an implementation of the RenderedImage + * interface). The methods that set/get a BufferedImage object are + * left in for compatibility. The new methods that set/get a + * RenderedImage are a superset of the old methods. In particular, + * the two set methods in the following example are equivalent: + * + * <p> + * <ul> + * <code> + * BufferedImage bi;<br> + * RenderedImage ri = bi;<br> + * ImageComponent3D ic;<br> + * <p> + * // Set image 0 to the specified BufferedImage<br> + * ic.set(0, bi);<br> + * <p> + * // Set image 0 to the specified RenderedImage<br> + * ic.set(0, ri);<br> + * </code> + * </ul> + */ +public class ImageComponent3D extends ImageComponent { + + // non-public, no parameter constructor + ImageComponent3D() {} + + /** + * Constructs a 3D image component object using the specified + * format, width, height, and depth. Default values are used for + * all other parameters. The default values are as follows: + * <ul> + * array of images : null<br> + * </ul> + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA, etc. + * @param width the number of columns of pixels in this image component + * object + * @param height the number of rows of pixels in this image component + * object + * @param depth the number of 2D slices in this image component object + * @exception IllegalArgumentException if format is invalid, or if + * any of width, height, or depth are not positive. + */ + public ImageComponent3D(int format, + int width, + int height, + int depth) { + + ((ImageComponent3DRetained)this.retained).processParams(format, width, height, depth); + ((ImageComponent3DRetained)this.retained).setDepth(depth); + } + + /** + * Constructs a 3D image component object using the specified format, + * and the BufferedImage array. + * Default values are used for all other parameters. + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA etc. + * @param images an array of BufferedImage objects. The + * first image in the array determines the width and height of this + * ImageComponent3D. + * @exception IllegalArgumentException if format is invalid, or if + * the width or height of the first image are not positive. + */ + public ImageComponent3D(int format, BufferedImage[] images) { + ((ImageComponent3DRetained)this.retained).processParams(format, + images[0].getWidth(null), images[0].getHeight(null), images.length); + ((ImageComponent3DRetained)this.retained).setDepth(images.length); + for (int i=0; i<images.length; i++) { + ((ImageComponent3DRetained)this.retained).set(i, images[i]); + } + } + + /** + * Constructs a 3D image component object using the specified format, + * and the RenderedImage array. + * Default values are used for all other parameters. + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA etc. + * @param images an array of RenderedImage objects. The + * first image in the array determines the width and height of this + * ImageComponent3D. + * @exception IllegalArgumentException if format is invalid, or if + * the width or height of the first image are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent3D(int format, RenderedImage[] images) { + + ((ImageComponent3DRetained)this.retained).processParams(format, + images[0].getWidth(), images[0].getHeight(), images.length); + ((ImageComponent3DRetained)this.retained).setDepth(images.length); + for (int i=0; i<images.length; i++) { + ((ImageComponent3DRetained)this.retained).set(i, images[i]); + } + } + + /** + * Constructs a 3D image component object using the specified + * format, width, height, depth, byReference flag, and yUp flag. + * Default values are used for all other parameters. + * + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA, etc. + * @param width the number of columns of pixels in this image component + * object + * @param height the number of rows of pixels in this image component + * object + * @param depth the number of 2D slices in this image component object + * @param byReference a flag that indicates whether the data is copied + * into this image component object or is accessed by reference. + * @param yUp a flag that indicates the y-orientation of this image + * component. If yUp is set to true, the origin of the image is + * the lower left; otherwise, the origin of the image is the upper + * left. + * + * @exception IllegalArgumentException if format is invalid, or if + * any of width, height, or depth are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent3D(int format, + int width, + int height, + int depth, + boolean byReference, + boolean yUp) { + + ((ImageComponentRetained)this.retained).setByReference(byReference); + ((ImageComponentRetained)this.retained).setYUp(yUp); + ((ImageComponent3DRetained)this.retained).processParams(format, width, height, depth); + ((ImageComponent3DRetained)this.retained).setDepth(depth); + } + + /** + * Constructs a 3D image component object using the specified format, + * BufferedImage array, byReference flag, and yUp flag. + * Default values are used for all other parameters. + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA etc. + * @param images an array of BufferedImage objects. The + * first image in the array determines the width and height of this + * ImageComponent3D. + * @param byReference a flag that indicates whether the data is copied + * into this image component object or is accessed by reference. + * @param yUp a flag that indicates the y-orientation of this image + * component. If yUp is set to true, the origin of the image is + * the lower left; otherwise, the origin of the image is the upper + * left. + * @exception IllegalArgumentException if format is invalid, or if + * the width or height of the first image are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent3D(int format, + BufferedImage[] images, + boolean byReference, + boolean yUp) { + + + ((ImageComponentRetained)this.retained).setByReference(byReference); + ((ImageComponentRetained)this.retained).setYUp(yUp); + ((ImageComponent3DRetained)this.retained).processParams(format, images[0].getWidth(null), images[0].getHeight(null), images.length); + ((ImageComponent3DRetained)this.retained).setDepth(images.length); + for (int i=0; i<images.length; i++) { + ((ImageComponent3DRetained)this.retained).set(i, images[i]); + } + } + + /** + * Constructs a 3D image component object using the specified format, + * RenderedImage array, byReference flag, and yUp flag. + * Default values are used for all other parameters. + * @param format the image component format, one of: FORMAT_RGB, + * FORMAT_RGBA etc. + * @param images an array of RenderedImage objects. The + * first image in the array determines the width and height of this + * ImageComponent3D. + * @param byReference a flag that indicates whether the data is copied + * into this image component object or is accessed by reference. + * @param yUp a flag that indicates the y-orientation of this image + * component. If yUp is set to true, the origin of the image is + * the lower left; otherwise, the origin of the image is the upper + * left. + * @exception IllegalArgumentException if format is invalid, or if + * the width or height of the first image are not positive. + * + * @since Java 3D 1.2 + */ + public ImageComponent3D(int format, + RenderedImage[] images, + boolean byReference, + boolean yUp) { + + + ((ImageComponentRetained)this.retained).setByReference(byReference); + ((ImageComponentRetained)this.retained).setYUp(yUp); + ((ImageComponent3DRetained)this.retained).processParams(format, images[0].getWidth(), images[0].getHeight(), images.length); + ((ImageComponent3DRetained)this.retained).setDepth(images.length); + for (int i=0; i<images.length; i++) { + ((ImageComponent3DRetained)this.retained).set(i, images[i]); + } + } + + /** + * Retrieves the depth of this 3D image component object. + * @return the format of this 3D image component object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getDepth() { + if (isLiveOrCompiled()) + if(!this.getCapability(ImageComponent.ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent3D0")); + return ((ImageComponent3DRetained)this.retained).getDepth(); + } + + /** + * Sets the array of images in this image component to the + * specified array of BufferedImage objects. If the data access + * mode is not by-reference, then the BufferedImage data is copied + * into this object. If the data access mode is by-reference, + * then a shallow copy of the array of references to the + * BufferedImage objects is made, but the BufferedImage + * data is not necessarily copied. + * + * @param images array of BufferedImage objects containing the image. + * The format and size must be the same as the current format in the + * image component. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + */ + public void set(BufferedImage[] images) { + checkForLiveOrCompiled(); + int depth = ((ImageComponent3DRetained)this.retained).getDepth(); + + if (depth != images.length) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent3D1")); + for (int i=0; i<depth; i++) { + ((ImageComponent3DRetained)this.retained).set(i, images[i]); + } + } + + /** + * Sets the array of images in this image component to the + * specified array of RenderedImage objects. If the data access + * mode is not by-reference, then the RenderedImage data is copied + * into this object. If the data access mode is by-reference, + * then a shallow copy of the array of references to the + * RenderedImage objects is made, but the RenderedImage + * data is not necessarily copied. + * + * @param images array of RenderedImage objects containing the image. + * The format and size must be the same as the current format in the + * image component. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void set(RenderedImage[] images) { + + checkForLiveOrCompiled(); + int depth = ((ImageComponent3DRetained)this.retained).getDepth(); + + if (depth != images.length) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent3D1")); + for (int i=0; i<depth; i++) { + ((ImageComponent3DRetained)this.retained).set(i, images[i]); + } + } + + /** + * Sets this image component at the specified index to the + * specified BufferedImage object. If the data access mode is not + * by-reference, then the BufferedImage data is copied into this + * object. If the data access mode is by-reference, then a + * reference to the BufferedImage is saved, but the data is not + * necessarily copied. + * + * @param index the image index + * @param image BufferedImage object containing the image. + * The format and size must be the same as the current format in this + * ImageComponent3D object. The index must not exceed the depth of this + * ImageComponent3D object. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + */ + public void set(int index, BufferedImage image) { + checkForLiveOrCompiled(); + if (image.getWidth(null) != this.getWidth()) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent3D2")); + + if (image.getHeight(null) != this.getHeight()) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent3D4")); + + ((ImageComponent3DRetained)this.retained).set(index, image); + } + + /** + * Sets this image component at the specified index to the + * specified RenderedImage object. If the data access mode is not + * by-reference, then the RenderedImage data is copied into this + * object. If the data access mode is by-reference, then a + * reference to the RenderedImage is saved, but the data is not + * necessarily copied. + * + * @param index the image index + * @param image RenderedImage object containing the image. + * The format and size must be the same as the current format in this + * ImageComponent3D object. The index must not exceed the depth of this + * ImageComponent3D object. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void set(int index, RenderedImage image) { + + checkForLiveOrCompiled(); + if (image.getWidth() != this.getWidth()) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent3D2")); + + if (image.getHeight() != this.getHeight()) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponent3D4")); + + ((ImageComponent3DRetained)this.retained).set(index, image); + } + + /** + * Retrieves the images from this ImageComponent3D object. If the + * data access mode is not by-reference, then a copy of the images + * is made. If the data access mode is by-reference, then the + * references are returned. + * + * @return either a new array of new BufferedImage objects created from + * the data + * in this image component, or a new array of + * references to the BufferedImages that this image component refers to. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalStateException if the data access mode is + * by-reference and any image referenced by this ImageComponent3D + * object is not an instance of BufferedImage. + */ + public BufferedImage[] getImage() { + if (isLiveOrCompiled()) + if(!this.getCapability(ImageComponent.ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent3D3")); + return ((ImageComponent3DRetained)this.retained).getImage(); + } + + /** + * Retrieves the images from this ImageComponent3D object. If the + * data access mode is not by-reference, then a copy of the images + * is made. If the data access mode is by-reference, then the + * references are returned. + * + * @return either a new array of new RenderedImage objects created from + * the data + * in this image component, or a new array of + * references to the RenderedImages that this image component refers to. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public RenderedImage[] getRenderedImage() { + + if (isLiveOrCompiled()) + if(!this.getCapability(ImageComponent.ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent3D3")); + return ((ImageComponent3DRetained)this.retained).getRenderedImage(); + } + + /** + * Retrieves one of the images from this ImageComponent3D object. If the + * data access mode is not by-reference, then a copy of the image + * is made. If the data access mode is by-reference, then the + * reference is returned. + * + * @param index the index of the image to retrieve + * @return either a new BufferedImage object created from the data + * in this image component, or the BufferedImage object referenced + * by this image component. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalStateException if the data access mode is + * by-reference and the image referenced by this ImageComponent3D + * object at the specified index is not an instance of BufferedImage. + */ + public BufferedImage getImage(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ImageComponent.ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent3D3")); + + RenderedImage img = ((ImageComponent3DRetained)this.retained).getImage(index); + if ((img != null) && !(img instanceof BufferedImage)) { + throw new IllegalStateException(J3dI18N.getString("ImageComponent3D9")); + } + return (BufferedImage) img; + } + + /** + * Retrieves one of the images from this ImageComponent3D object. If the + * data access mode is not by-reference, then a copy of the image + * is made. If the data access mode is by-reference, then the + * reference is returned. + * + * @param index the index of the image to retrieve + * @return either a new RenderedImage object created from the data + * in this image component, or the RenderedImage object referenced + * by this image component. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public RenderedImage getRenderedImage(int index) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ImageComponent.ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ImageComponent3D3")); + return ((ImageComponent3DRetained)this.retained).getImage(index); + } + + /** + * Modifies a contiguous subregion of a particular slice of + * image of this ImageComponent3D object. + * Block of data of dimension (width * height) + * starting at the offset (srcX, srcY) of the specified + * RenderedImage object will be copied into the particular slice of + * image component + * starting at the offset (dstX, dstY) of this ImageComponent3D object. + * The specified RenderedImage object must be of the same format as + * the current format of this object. + * This method can only be used if the data access mode is + * by-copy. If it is by-reference, see updateData(). + * + * @param index index of the image to be modified. The index must not + * exceed the depth of the object. + * @param image RenderedImage object containing the subimage. + * @param width width of the subregion. + * @param height height of the subregion. + * @param srcX starting X offset of the subregion in the specified image. + * @param srcY starting Y offset of the subregion in the specified image. + * @param dstX startin X offset of the subregion in the image + * component of this object. + * @param dstY starting Y offset of the subregion in the image + * component of this object. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalStateException if the data access mode is + * <code>BY_REFERENCE</code>. + * @exception IllegalArgumentException if <code>width</code> or + * <code>height</code> of + * the subregion exceeds the dimension of the image in this object. + * @exception IllegalArgumentException if <code>dstX</code> < 0, or + * (<code>dstX</code> + <code>width</code>) > width of this object, or + * <code>dstY</code> < 0, or + * (<code>dstY</code> + <code>height</code>) > height of this object. + * @exception IllegalArgumentException if <code>srcX</code> < 0, or + * (<code>srcX</code> + <code>width</code>) > width of the RenderedImage + * object containing the subimage, or + * <code>srcY</code> < 0, or + * (<code>srcY</code> + <code>height</code>) > height of the + * RenderedImage object containing the subimage. + * + * @since Java 3D 1.3 + */ + public void setSubImage(int index, RenderedImage image, + int width, int height, + int srcX, int srcY, int dstX, int dstY) { + if (isLiveOrCompiled() && + !this.getCapability(ALLOW_IMAGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("ImageComponent3D5")); + } + + if (((ImageComponent3DRetained)this.retained).isByReference()) { + throw new IllegalStateException( + J3dI18N.getString("ImageComponent3D8")); + } + + int w = ((ImageComponent3DRetained)this.retained).getWidth(); + int h = ((ImageComponent3DRetained)this.retained).getHeight(); + + if ((srcX < 0) || (srcY < 0) || + ((srcX + width) > w) || ((srcY + height) > h) || + (dstX < 0) || (dstY < 0) || + ((dstX + width) > w) || ((dstY + height) > h)) { + throw new IllegalArgumentException( + J3dI18N.getString("ImageComponent3D7")); + } + + ((ImageComponent3DRetained)this.retained).setSubImage( + index, image, width, height, srcX, srcY, dstX, dstY); + } + + /** + * Updates a particular slice of image data that is accessed by reference. + * This method calls the updateData method of the specified + * ImageComponent3D.Updater object to synchronize updates to the + * image data that is referenced by this ImageComponent3D object. + * Applications that wish to modify such data must perform all + * updates via this method. + * <p> + * The data to be modified has to be within the boundary of the + * subregion + * specified by the offset (x, y) and the dimension (width*height). + * It is illegal to modify data outside this boundary. If any + * referenced data is modified outside the updateData method, or + * any data outside the specified boundary is modified, the + * results are undefined. + * <p> + * @param updater object whose updateData callback method will be + * called to update the data referenced by this ImageComponent3D object. + * @param index index of the image to be modified. The index must + * not exceed the depth of this object. + * @param x starting X offset of the subregion. + * @param y starting Y offset of the subregion. + * @param width width of the subregion. + * @param height height of the subregion. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalStateException if the data access mode is + * <code>BY_COPY</code>. + * @exception IllegalArgumentException if <code>width</code> or + * <code>height</code> of + * the subregion exceeds the dimension of the image in this object. + * @exception IllegalArgumentException if <code>x</code> < 0, or + * (<code>x</code> + <code>width</code>) > width of this object, or + * <code>y</code> < 0, or + * (<code>y</code> + <code>height</code>) > height of this object. + * @exception ArrayIndexOutOfBoundsException if <code>index</code> > the + * depth of this object. + * + * @since Java 3D 1.3 + */ + public void updateData(Updater updater, int index, + int x, int y, + int width, int height) { + if (isLiveOrCompiled() && + !this.getCapability(ALLOW_IMAGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("ImageComponent3D5")); + } + + if (!((ImageComponent3DRetained)this.retained).isByReference()) { + throw new IllegalStateException( + J3dI18N.getString("ImageComponent3D6")); + } + + int w = ((ImageComponent3DRetained)this.retained).getWidth(); + int h = ((ImageComponent3DRetained)this.retained).getHeight(); + + if ((x < 0) || (y < 0) || ((x + width) > w) || ((y + height) > h)) { + throw new IllegalArgumentException( + J3dI18N.getString("ImageComponent3D7")); + } + + ((ImageComponent3DRetained)this.retained).updateData( + updater, index, x, y, width, height); + } + + + /** + * Creates a retained mode ImageComponent3DRetained object that this + * ImageComponent3D component object will point to. + */ + void createRetained() { + this.retained = new ImageComponent3DRetained(); + this.retained.setSource(this); + } + + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + ImageComponent3DRetained rt = (ImageComponent3DRetained) retained; + + ImageComponent3D img = new ImageComponent3D(rt.format, + rt.width, + rt.height, + rt.depth); + + // TODO : replace by this to duplicate other attributes + /* + ImageComponent3D img = new ImageComponent3D(rt.format, + rt.width, + rt.height, + rt.depth, + rt.byReference, + rt.yUp); + */ + img.duplicateNodeComponent(this); + return img; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + RenderedImage imgs[] = ((ImageComponent3DRetained) + originalNodeComponent.retained).getImage(); + + if (imgs != null) { + ImageComponent3DRetained rt = (ImageComponent3DRetained) retained; + + for (int i=rt.depth-1; i>=0; i--) { + if (imgs[i] != null) { + rt.set(i, imgs[i]); + } + } + } + } + + /** + * The ImageComponent3D.Updater interface is used in updating image data + * that is accessed by reference from a live or compiled ImageComponent + * object. Applications that wish to modify such data must define a + * class that implements this interface. An instance of that class is + * then passed to the <code>updateData</code> method of the + * ImageComponent object to be modified. + * + * @since Java 3D 1.3 + */ + public static interface Updater { + /** + * Updates image data that is accessed by reference. + * This method is called by the updateData method of an + * ImageComponent object to effect + * safe updates to image data that + * is referenced by that object. Applications that wish to modify + * such data must implement this method and perform all updates + * within it. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * + * @param imageComponent the ImageComponent object being updated. + * @param index index of the image to be modified. + * @param x starting X offset of the subregion. + * @param y starting Y offset of the subregion. + * @param width width of the subregion. + * @param height height of the subregion. + * + * @see ImageComponent3D#updateData + */ + public void updateData(ImageComponent3D imageComponent, + int index, + int x, int y, + int width, int height); + } + +} diff --git a/src/classes/share/javax/media/j3d/ImageComponent3DRetained.java b/src/classes/share/javax/media/j3d/ImageComponent3DRetained.java new file mode 100644 index 0000000..800ff67 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ImageComponent3DRetained.java @@ -0,0 +1,217 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.image.*; + +/** + * This class defines a 3D array of pixels. + * This is used for texture images. + */ + +class ImageComponent3DRetained extends ImageComponentRetained { + int depth; // Depth of 3D image + + void setDepth(int depth) { + this.depth = depth; + } + + /** + * Retrieves the depth of this 3D image component object. + * @return the format of this 3D image component object + */ + final int getDepth() { + return depth; + } + + /** + * Copies the specified BufferedImage to this 3D image component + * object at the specified index. + * @param index the image index + * @param images BufferedImage object containing the image. + * The format and size must be the same as the current format in this + * ImageComponent3D object. The index must not exceed the depth of this + * ImageComponent3D object. + */ + final void set(int index, BufferedImage image) { + if (imageYup == null) + imageYup = new byte[height * width * depth * bytesPerPixelIfStored]; + imageDirty[index] = true; + storedYupFormat = internalFormat; + bytesPerYupPixelStored = bytesPerPixelIfStored; + copyImage(image, imageYup, true, index, storedYupFormat, + bytesPerYupPixelStored); + if (byReference) + bImage[index] = image; + } + + final void set(int index, RenderedImage image) { + if (image instanceof BufferedImage) { + set(index, ((BufferedImage)image)); + } + else { + // Create a buffered image from renderImage + ColorModel cm = image.getColorModel(); + WritableRaster wRaster = image.copyData(null); + BufferedImage bi = new BufferedImage(cm, + wRaster, + cm.isAlphaPremultiplied() + ,null); + set(index, bi); + } + } + + /** + * Retrieves a copy of the images in this ImageComponent3D object. + * @return a new array of new BufferedImage objects created from the + * images in this ImageComponent3D object + */ + final RenderedImage[] getRenderedImage() { + int i; + RenderedImage bi[] = new RenderedImage[bImage.length]; + if (!byReference) { + for (i=0; i<depth; i++) { + if (imageDirty[i]) { + retrieveBufferedImage(i); + } + } + } + for (i = 0; i < bImage.length; i++) { + bi[i] = bImage[i]; + } + // If by reference, then the image should not be dirty + return bi; + } + + + /** + * Retrieves a copy of the images in this ImageComponent3D object. + * @return a new array of new BufferedImage objects created from the + * images in this ImageComponent3D object + */ + final BufferedImage[] getImage() { + int i; + BufferedImage bi[] = new BufferedImage[bImage.length]; + + if (!byReference) { + for (i=0; i<depth; i++) { + if (imageDirty[i]) { + retrieveBufferedImage(i); + } + } + } + + for (i = 0; i < bImage.length; i++) { + if (!(bImage[i] instanceof BufferedImage)) { + throw new IllegalStateException(J3dI18N.getString("ImageComponent3DRetained0")); + } + bi[i] = (BufferedImage) bImage[i]; + } + // If by reference, then the image should not be dirty + return bi; + } + + /** + * Retrieves a copy of one of the images in this ImageComponent3D object. + * @param index the index of the image to retrieve + * @return a new BufferedImage objects created from the + * image at the specified index in this ImageComponent3D object + */ + final RenderedImage getImage(int index) { + if (!byReference) { + if (imageDirty[index]) { + retrieveBufferedImage(index); + } + } + return bImage[index]; + } + + /** + * Update data. + * x and y specifies the x & y offset of the image data in + * ImageComponent. It assumes that the origin is (0, 0). + */ + void updateData(ImageComponent3D.Updater updater, + int index, int x, int y, int width, int height) { + + geomLock.getLock(); + + // call the user supplied updateData method to update the data + updater.updateData((ImageComponent3D)source, index, x, y, width, height); + + // update the internal copy of the image data if a copy has been + // made + if (imageYupAllocated) { + copyImage(bImage[0], (x + bImage[0].getMinX()), + (y + bImage[0].getMinY()), imageYup, x, y, + true, index, width, height, storedYupFormat, + bytesPerYupPixelStored); + } + + imageDirty[index] = true; + + geomLock.unLock(); + + + if (source.isLive()) { + + // send a SUBIMAGE_CHANGED message in order to + // notify all the users of the change + + ImageComponentUpdateInfo info; + + info = VirtualUniverse.mc.getFreeImageUpdateInfo(); + info.x = x; + info.y = y; + info.z = index; + info.width = width; + info.height = height; + + sendMessage(SUBIMAGE_CHANGED, info); + } + } + + void setSubImage(int index, RenderedImage image, int width, int height, + int srcX, int srcY, int dstX, int dstY) { + + geomLock.getLock(); + + if (imageYupAllocated) { + copyImage(image, srcX, srcY, imageYup, dstX, dstY, + true, index, width, height, storedYupFormat, + bytesPerYupPixelStored); + } + + imageDirty[index] = true; + + geomLock.unLock(); + + + if (source.isLive()) { + + // send a SUBIMAGE_CHANGED message in order to + // notify all the users of the change + + ImageComponentUpdateInfo info; + + info = VirtualUniverse.mc.getFreeImageUpdateInfo(); + info.x = dstX; + info.y = dstY; + info.z = index; + info.width = width; + info.height = height; + + sendMessage(SUBIMAGE_CHANGED, info); + } + } +} diff --git a/src/classes/share/javax/media/j3d/ImageComponentRetained.java b/src/classes/share/javax/media/j3d/ImageComponentRetained.java new file mode 100644 index 0000000..17f45be --- /dev/null +++ b/src/classes/share/javax/media/j3d/ImageComponentRetained.java @@ -0,0 +1,1559 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; +import java.awt.image.*; +import java.awt.color.ColorSpace; +import java.awt.Transparency; + +/** + * Abstract class that is used to define 2D or 3D ImageComponent classes + * used in a Java 3D scene graph. + * This is used for texture images, background images and raster components + * of Shape3D nodes. + */ + +abstract class ImageComponentRetained extends NodeComponentRetained { + int format; // PixelArray format (RGB, RGBA, ALPHA, etc.) + int width; // Width of PixelArray + int height; // Height of PixelArray + byte[][] imageYdown = new byte[1][]; // 2D array of pixel values in + // one of various formats - Y downwards + byte[] imageYup; // 2D or 3D array of pixel values in + // one of various formats - Y upwards + + int bytesPerPixel; // computed from input format + boolean usedByRaster = false; // used by a raster object? + boolean usedByTexture = false; // used by a texture object? + + boolean byReference = false; // Is the imageComponent by reference + boolean yUp = false; + + // array of Buffered image + // This will store the refImage array, if the imagecomponent + // is by reference + RenderedImage[] bImage; + boolean[] imageDirty; // array of image dirty flag + + boolean noAlpha = false; + + // Format of the Yup and Ydown image + // in the case of "by Copy" it is RGBA + // In the case of "by ref" it may be one of the original + // formats supported by OpenGL + /* + int storedFormat; + */ + int bytesPerPixelIfStored; // Number of bytes if a copy is made + int storedYupFormat; + int storedYdownFormat; + + int bytesPerYupPixelStored; + int bytesPerYdownPixelStored; + + int internalFormat; // Used when a copy is made, RGBA, LA, L + boolean imageYupAllocated = false; + boolean imageYdownAllocated = false; + + // If cache is dirty (clearLive, setLive has occureed), then + // extension based cache needs to be re-evaluated + boolean imageYupCacheDirty = false; + boolean imageYdownCacheDirty = false; + + + static final int BYTE_RGBA = 0x1; + static final int BYTE_ABGR = 0x2; + static final int BYTE_GRAY = 0x4; + static final int USHORT_GRAY = 0x8; + static final int BYTE_LA = 0x10; + static final int BYTE_BGR = 0x20; + static final int BYTE_RGB = 0x40; + + int imageYupClass = 0; + int imageYdownClass = 0; + static final int BUFFERED_IMAGE = 0x1; + static final int RENDERED_IMAGE = 0x2; + + // change flag + static final int IMAGE_CHANGED = 0x01; + static final int SUBIMAGE_CHANGED = 0x02; + + // Lock used in the "by ref case" + GeometryLock geomLock = new GeometryLock(); + + int minTileX = 0; + int minTileY = 0; + int minTileZ = 0; + + int tilew = 0; + int tileh = 0; + int tiled = 0; + + int numXTiles = 0; + int numYTiles = 0; + int numZTiles = 0; + + int tileGridXOffset = 0; + int tileGridYOffset = 0; + + + int minX = 0; + int minY = 0; + + // lists of Node Components that are referencing this ImageComponent + // object. This list is used to notify the referencing node components + // of any changes of this ImageComponent. + + ArrayList userList = new ArrayList(); + + /** + * Retrieves the width of this image component object. + * @return the width of this image component object + */ + final int getWidth() { + return width; + } + + /** + * Retrieves the height of this image component object. + * @return the height of this image component object + */ + final int getHeight() { + return height; + } + + /** + * Retrieves the format of this image component object. + * @return the format of this image component object + */ + final int getFormat() { + return format; + } + + /** + * Check if ImageComponent parameters have valid values.. + */ + void processParams(int format, int width, int height, int depth) { + if (width < 1) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponentRetained0")); + + if (height < 1) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponentRetained1")); + + if (depth < 1) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponentRetained2")); + + if (format < 1 || format > ImageComponent.FORMAT_TOTAL) + throw new IllegalArgumentException(J3dI18N.getString("ImageComponentRetained3")); + this.format = format; + this.width = width; + this.height = height; + imageDirty = new boolean[depth]; + for (int i=0; i< depth; i++) + imageDirty[i] = false; + bImage = new RenderedImage[depth]; + + noAlpha = (format == ImageComponent.FORMAT_RGB || + format == ImageComponent.FORMAT_RGB4 || + format == ImageComponent.FORMAT_R3_G3_B2 || + format == ImageComponent.FORMAT_RGB5); + + // If the format is 8bit per component, we may send it down + // to OpenGL directly if its by ref case + switch (format) { + case ImageComponent.FORMAT_RGB:// same as ImageComponent.FORMAT_RGB8 + bytesPerPixel = 3; + bytesPerPixelIfStored = 4; + internalFormat = BYTE_RGBA; + break; + case ImageComponent.FORMAT_RGBA:// same as ImageComponent.FORMAT_RGBA8 + bytesPerPixel = 4; + bytesPerPixelIfStored = 4; + internalFormat = BYTE_RGBA; + break; + case ImageComponent.FORMAT_RGB5: + bytesPerPixel = 2; + bytesPerPixelIfStored = 4; + internalFormat = BYTE_RGBA; + break; + case ImageComponent.FORMAT_RGB5_A1: + bytesPerPixel = 2; + bytesPerPixelIfStored = 4; + internalFormat = BYTE_RGBA; + break; + case ImageComponent.FORMAT_RGB4: + bytesPerPixel = 2; + bytesPerPixelIfStored = 4; + internalFormat = BYTE_RGBA; + break; + case ImageComponent.FORMAT_RGBA4: + bytesPerPixel = 2; + bytesPerPixelIfStored = 4; + internalFormat = BYTE_RGBA; + break; + case ImageComponent.FORMAT_R3_G3_B2: + bytesPerPixel = 1; + bytesPerPixelIfStored = 4; + internalFormat = BYTE_RGBA; + break; + case ImageComponent.FORMAT_LUM4_ALPHA4: + bytesPerPixel = 1; + bytesPerPixelIfStored = 2; + internalFormat = BYTE_LA; + break; + case ImageComponent.FORMAT_LUM8_ALPHA8: + bytesPerPixel = 2; + bytesPerPixelIfStored = 2; + internalFormat = BYTE_LA; + break; + case ImageComponent.FORMAT_CHANNEL8: + bytesPerPixel = 1; + bytesPerPixelIfStored = 1; + internalFormat = BYTE_GRAY; + break; + default: + // ERROR + } + } + + void setTextureRef() { + usedByTexture = true; + } + + void setRasterRef() { + usedByRaster = true; + } + + + boolean formatMatches(int format, RenderedImage ri) { + + // there is no RenderedImage format that matches BYTE_LA + if (format == BYTE_LA) { + return false; + } + + int riFormat = getImageType(ri); + + if ((format == BYTE_ABGR && riFormat == BufferedImage.TYPE_4BYTE_ABGR) + || (format == BYTE_BGR && riFormat == BufferedImage.TYPE_3BYTE_BGR) + || (format == BYTE_GRAY && riFormat == BufferedImage.TYPE_BYTE_GRAY) + || (format == USHORT_GRAY && riFormat == + BufferedImage.TYPE_USHORT_GRAY)) { + return true; + } + + if (riFormat == BufferedImage.TYPE_CUSTOM) { + if (is4ByteRGBAOr3ByteRGB(ri)) { + int numBands = ri.getSampleModel().getNumBands(); + if (numBands == 3 && format == BYTE_RGB) { + return true; + } else if (numBands == 4 && format == BYTE_RGBA) { + return true; + } + } + } + + return false; + } + + /** + * copy complete region of a RenderedImage + */ + final void copyImage(RenderedImage ri, byte[] image, + boolean usedByTexture, int depth, + int imageFormat, int imageBytesPerPixel) { + + if (ri instanceof BufferedImage) { + copyImage((BufferedImage)ri, 0, 0, image, 0, 0, + usedByTexture, depth, width, height, + imageFormat, imageBytesPerPixel); + } else { + copyImage(ri, ri.getMinX(), ri.getMinY(), + image, 0, 0, usedByTexture, depth, width, height, + imageFormat, imageBytesPerPixel); + } + } + + + /** + * copy subregion of a RenderedImage + */ + final void copyImage(RenderedImage ri, int srcX, int srcY, + byte[] image, int dstX, int dstY, + boolean usedByTexture, int depth, + int copywidth, int copyheight, + int imageFormat, int imageBytesPerPixel) { + + if (ri instanceof BufferedImage) { + copyImage((BufferedImage)ri, srcX, srcY, image, dstX, dstY, + usedByTexture, depth, copywidth, copyheight, + imageFormat, imageBytesPerPixel); + return; + } + + + int w, h, i, j, m, n; + int dstBegin; + Object pixel = null; + java.awt.image.Raster ras; + int lineBytes = width * imageBytesPerPixel; // nbytes per line in + // dst image buffer + int sign; // -1 for going down + int dstLineBytes; // sign * lineBytes + int tileStart; // destination buffer offset + // at the next left most tile + + int offset; + + ColorModel cm = ri.getColorModel(); + + int xoff = ri.getTileGridXOffset(); // tile origin x offset + int yoff = ri.getTileGridYOffset(); // tile origin y offset + int minTileX = ri.getMinTileX(); // min tile x index + int minTileY = ri.getMinTileY(); // min tile y index + tilew = ri.getTileWidth(); // tile width in pixels + tileh = ri.getTileHeight(); // tile height in pixels + + + // determine the first tile of the image + + float mt; + + mt = (float)(srcX - xoff) / (float)tilew; + if (mt < 0) { + minTileX = (int)(mt - 1); + } else { + minTileX = (int)mt; + } + + mt = (float)(srcY - yoff) / (float)tileh; + if (mt < 0) { + minTileY = (int)(mt - 1); + } else { + minTileY = (int)mt; + } + + + // determine the pixel offset of the upper-left corner of the + // first tile + + int startXTile = minTileX * tilew + xoff; + int startYTile = minTileY * tileh + yoff; + + + // image dimension in the first tile + + int curw = (startXTile + tilew - srcX); + int curh = (startYTile + tileh - srcY); + + + // check if the to-be-copied region is less than the tile image + // if so, update the to-be-copied dimension of this tile + + if (curw > copywidth) { + curw = copywidth; + } + + if (curh > copyheight) { + curh = copyheight; + } + + + // save the to-be-copied width of the left most tile + + int startw = curw; + + + // temporary variable for dimension of the to-be-copied region + + int tmpw = copywidth; + int tmph = copyheight; + + + // offset of the first pixel of the tile to be copied; offset is + // relative to the upper left corner of the title + + int x = srcX - startXTile; + int y = srcY - startYTile; + + + // determine the number of tiles in each direction that the + // image spans + + numXTiles = (copywidth + x) / tilew; + numYTiles = (copyheight + y) / tileh; + + if (((float)(copywidth + x ) % (float)tilew) > 0) { + numXTiles += 1; + } + + if (((float)(copyheight + y ) % (float)tileh) > 0) { + numYTiles += 1; + } + +/* + System.out.println("-----------------------------------------------"); + System.out.println("minTileX= " + minTileX + " minTileY= " + minTileY); + System.out.println("numXTiles= " + numXTiles + " numYTiles= " + numYTiles); + System.out.println("tilew= " + tilew + " tileh= " + tileh); + System.out.println("xoff= " + xoff + " yoff= " + yoff); + System.out.println("startXTile= " + startXTile + " startYTile= " + startYTile); + System.out.println("srcX= " + srcX + " srcY= " + srcY); + System.out.println("copywidth= " + copywidth + " copyheight= " + copyheight); + + System.out.println("rminTileX= " + ri.getMinTileX() + " rminTileY= " + ri.getMinTileY()); + System.out.println("rnumXTiles= " + ri.getNumXTiles() + " rnumYTiles= " + ri.getNumYTiles()); +*/ + + if ((!yUp && usedByTexture) || + (yUp && !usedByTexture)) { + + // destination buffer offset + + tileStart = ((height - dstY - 1) * width + dstX) + * imageBytesPerPixel; + + sign = -1; + dstLineBytes = -lineBytes; + } else { + + // destination buffer offset + + tileStart = (dstY * width + dstX) * imageBytesPerPixel; + sign = 1; + dstLineBytes = lineBytes; + } + +/* + System.out.println("tileStart= " + tileStart + " dstLineBytes= " + dstLineBytes); + System.out.println("startw= " + startw); +*/ + + // allocate memory for a pixel + + ras = ri.getTile(minTileX,minTileY); + pixel = getDataElementBuffer(ras); + + if (formatMatches(imageFormat, ri)) { + byte[] src; + int srcOffset, dstOffset; + int tileLineBytes= tilew * imageBytesPerPixel; + int copyBytes; + + for (n = minTileY; n < minTileY+numYTiles; n++) { + + dstBegin = tileStart; // destination buffer offset + tmpw = copywidth; // reset the width to be copied + curw = startw; // reset the width to be copied of + // the left most tile + x = srcX - startXTile; // reset the starting x offset of + // the left most tile + + for (m = minTileX; m < minTileX+numXTiles; m++) { + + // retrieve the raster for the next tile + ras = ri.getTile(m,n); + src = ((DataBufferByte)ras.getDataBuffer()).getData(); + + srcOffset = (y * tilew + x) * imageBytesPerPixel; + dstOffset = dstBegin; + + copyBytes = curw * imageBytesPerPixel; + + //System.out.println("curh = "+curh+" curw = "+curw); + //System.out.println("x = "+x+" y = "+y); + + for (h = 0; h < curh; h++) { + System.arraycopy(src, srcOffset, image, dstOffset, + copyBytes); + srcOffset += tileLineBytes; + dstOffset += dstLineBytes; + } + + // advance the destination buffer offset + dstBegin += curw * imageBytesPerPixel; + + // move to the next tile in x direction + x = 0; + + // determine the width of copy region of the next tile + + tmpw -= curw; + if (tmpw < tilew) { + curw = tmpw; + } else { + curw = tilew; + } + } + + + // we are done copying an array of tiles in the x direction + // advance the tileStart offset + + tileStart += width * imageBytesPerPixel * curh * sign; + + + // move to the next set of tiles in y direction + y = 0; + + // determine the height of copy region for the next set + // of tiles + tmph -= curh; + if (tmph < tileh) { + curh = tmph; + } else { + curh = tileh; + } + } + return; + } + + switch(format) { + case ImageComponent.FORMAT_RGBA8: + case ImageComponent.FORMAT_RGB5_A1: + case ImageComponent.FORMAT_RGBA4: { + // System.out.println("Case 1: byReference = "+byReference); + for (n = minTileY; n < minTileY+numYTiles; n++) { + + dstBegin = tileStart; // destination buffer offset + tmpw = copywidth; // reset the width to be copied + curw = startw; // reset the width to be copied of + // the left most tile + x = srcX - startXTile; // reset the starting x offset of + // the left most tile + + for (m = minTileX; m < minTileX+numXTiles; m++) { + + // retrieve the raster for the next tile + ras = ri.getTile(m,n); + + j = dstBegin; + offset = 0; + + //System.out.println("curh = "+curh+" curw = "+curw); + //System.out.println("x = "+x+" y = "+y); + + for (h = y; h < (y + curh); h++) { + // System.out.println("j = "+j); + for (w = x; w < (x + curw); w++) { + ras.getDataElements(w, h, pixel); + image[j++] = (byte)cm.getRed(pixel); + image[j++] = (byte)cm.getGreen(pixel); + image[j++] = (byte)cm.getBlue(pixel); + image[j++] = (byte)cm.getAlpha(pixel); + } + offset += dstLineBytes; + j = dstBegin + offset; + } + + // advance the destination buffer offset + dstBegin += curw * imageBytesPerPixel; + + // move to the next tile in x direction + x = 0; + + // determine the width of copy region of the next tile + + tmpw -= curw; + if (tmpw < tilew) { + curw = tmpw; + } else { + curw = tilew; + } + } + + + // we are done copying an array of tiles in the x direction + // advance the tileStart offset + + tileStart += width * imageBytesPerPixel * curh * sign; + + + // move to the next set of tiles in y direction + y = 0; + + // determine the height of copy region for the next set + // of tiles + tmph -= curh; + if (tmph < tileh) { + curh = tmph; + } else { + curh = tileh; + } + } + } + break; + case ImageComponent.FORMAT_RGB8: + case ImageComponent.FORMAT_RGB5: + case ImageComponent.FORMAT_RGB4: + case ImageComponent.FORMAT_R3_G3_B2: { + for (n = minTileY; n < minTileY+numYTiles; n++) { + + dstBegin = tileStart; // destination buffer offset + tmpw = copywidth; // reset the width to be copied + curw = startw; // reset the width to be copied of + // the left most tile + x = srcX - startXTile; // reset the starting x offset of + // the left most tile + + for (m = minTileX; m < minTileX+numXTiles; m++) { + + // retrieve the raster for the next tile + ras = ri.getTile(m,n); + + j = dstBegin; + offset = 0; + + //System.out.println("curh = "+curh+" curw = "+curw); + //System.out.println("x = "+x+" y = "+y); + + for (h = y; h < (y + curh); h++) { + // System.out.println("j = "+j); + for (w = x; w < (x + curw); w++) { + ras.getDataElements(w, h, pixel); + image[j++] = (byte)cm.getRed(pixel); + image[j++] = (byte)cm.getGreen(pixel); + image[j++] = (byte)cm.getBlue(pixel); + image[j++] = (byte)255; + } + offset += dstLineBytes; + j = dstBegin + offset; + } + + // advance the destination buffer offset + dstBegin += curw * imageBytesPerPixel; + + // move to the next tile in x direction + x = 0; + + // determine the width of copy region of the next tile + + tmpw -= curw; + if (tmpw < tilew) { + curw = tmpw; + } else { + curw = tilew; + } + } + + + // we are done copying an array of tiles in the x direction + // advance the tileStart offset + + tileStart += width * imageBytesPerPixel * curh * sign; + + + // move to the next set of tiles in y direction + y = 0; + + // determine the height of copy region for the next set + // of tiles + tmph -= curh; + if (tmph < tileh) { + curh = tmph; + } else { + curh = tileh; + } + } + } + break; + case ImageComponent.FORMAT_LUM8_ALPHA8: + case ImageComponent.FORMAT_LUM4_ALPHA4: { + for (n = minTileY; n < minTileY+numYTiles; n++) { + + dstBegin = tileStart; // destination buffer offset + tmpw = copywidth; // reset the width to be copied + curw = startw; // reset the width to be copied of + // the left most tile + x = srcX - startXTile; // reset the starting x offset of + // the left most tile + + for (m = minTileX; m < minTileX+numXTiles; m++) { + + // retrieve the raster for the next tile + ras = ri.getTile(m,n); + + j = dstBegin; + offset = 0; + + //System.out.println("curh = "+curh+" curw = "+curw); + //System.out.println("x = "+x+" y = "+y); + + for (h = y; h < (y + curh); h++) { + // System.out.println("j = "+j); + for (w = x; w < (x + curw); w++) { + ras.getDataElements(w, h, pixel); + image[j++] = (byte)cm.getRed(pixel); + image[j++] = (byte)cm.getAlpha(pixel); + } + offset += dstLineBytes; + j = dstBegin + offset; + } + + // advance the destination buffer offset + dstBegin += curw * imageBytesPerPixel; + + // move to the next tile in x direction + x = 0; + + // determine the width of copy region of the next tile + + tmpw -= curw; + if (tmpw < tilew) { + curw = tmpw; + } else { + curw = tilew; + } + } + + + // we are done copying an array of tiles in the x direction + // advance the tileStart offset + + tileStart += width * imageBytesPerPixel * curh * sign; + + + // move to the next set of tiles in y direction + y = 0; + + // determine the height of copy region for the next set + // of tiles + tmph -= curh; + if (tmph < tileh) { + curh = tmph; + } else { + curh = tileh; + } + } + } + break; + case ImageComponent.FORMAT_CHANNEL8: { + for (n = minTileY; n < minTileY+numYTiles; n++) { + + dstBegin = tileStart; // destination buffer offset + tmpw = copywidth; // reset the width to be copied + curw = startw; // reset the width to be copied of + // the left most tile + x = srcX - startXTile; // reset the starting x offset of + // the left most tile + + for (m = minTileX; m < minTileX+numXTiles; m++) { + + // retrieve the raster for the next tile + ras = ri.getTile(m,n); + + j = dstBegin; + offset = 0; + + //System.out.println("curh = "+curh+" curw = "+curw); + //System.out.println("x = "+x+" y = "+y); + + for (h = y; h < (y + curh); h++) { + // System.out.println("j = "+j); + for (w = x; w < (x + curw); w++) { + ras.getDataElements(w, h, pixel); + image[j++] = (byte)cm.getRed(pixel); + } + offset += dstLineBytes; + j = dstBegin + offset; + } + + // advance the destination buffer offset + dstBegin += curw * imageBytesPerPixel; + + // move to the next tile in x direction + x = 0; + + // determine the width of copy region of the next tile + + tmpw -= curw; + if (tmpw < tilew) { + curw = tmpw; + } else { + curw = tilew; + } + } + + + // we are done copying an array of tiles in the x direction + // advance the tileStart offset + + tileStart += width * imageBytesPerPixel * curh * sign; + + + // move to the next set of tiles in y direction + y = 0; + + // determine the height of copy region for the next set + // of tiles + tmph -= curh; + if (tmph < tileh) { + curh = tmph; + } else { + curh = tileh; + } + } + } + break; + default: + break; + } + } + + + /** + * Copy entire image data from Buffered Image to + * ImageComponent's internal representation + */ + final void copyImage(BufferedImage bi, byte[] image, + boolean usedByTexture, int depth, + int imageFormat, int imageBytesPerPixel) { + copyImage(bi, 0, 0, image, 0, 0, usedByTexture, depth, + width, height, imageFormat, imageBytesPerPixel); + } + + /** + * Copy specified region of image data from Buffered Image to + * ImageComponent's internal representation + */ + final void copyImage(BufferedImage bi, int srcX, int srcY, + byte[] image, int dstX, int dstY, boolean usedByTexture, + int depth, int copywidth, int copyheight, + int imageFormat, int imageBytesPerPixel) { + + int w, h, i, j; + int rowBegin, // src begin row index + srcBegin, // src begin offset + dstBegin, // dst begin offset + rowInc, // row increment + // -1 --- ydown + // 1 --- yup + row; + Object pixel = null; + + rowBegin = srcY; + rowInc = 1; + + int dstBytesPerRow = width * imageBytesPerPixel; // bytes per row + // in dst image + + if ((!yUp && usedByTexture) || (yUp && !usedByTexture)) { + dstBegin = (depth * width * height + + (height - dstY - 1) * width + dstX) * + imageBytesPerPixel; + + dstBytesPerRow = - 1 * dstBytesPerRow; + + } else { + dstBegin = (dstY * width + dstX) * imageBytesPerPixel; + } + + // if the image format matches the format of the incoming + // buffered image, then do a straight copy, else do the + // format conversion while copying the data + + if (formatMatches(imageFormat, bi)) { + byte[] byteData = + ((DataBufferByte)bi.getRaster().getDataBuffer()).getData(); + int copyBytes = copywidth * imageBytesPerPixel; + int scanline = width * imageBytesPerPixel; + + srcBegin = (rowBegin * width + srcX) * imageBytesPerPixel; + for (h = 0; h < copyheight; h++) { + System.arraycopy(byteData, srcBegin, image, dstBegin, copyBytes); + dstBegin += dstBytesPerRow; + srcBegin += scanline; + } + } else { + + int biType = bi.getType(); + if ((biType == BufferedImage.TYPE_INT_ARGB || + biType == BufferedImage.TYPE_INT_RGB) && + (format == ImageComponent.FORMAT_RGBA8 || + format == ImageComponent.FORMAT_RGB8)) { + + // optimized cases + + int[] intData = + ((DataBufferInt)bi.getRaster().getDataBuffer()).getData(); + int rowOffset = rowInc * width; + int intPixel; + + srcBegin = rowBegin * width + srcX; + + if (biType == BufferedImage.TYPE_INT_ARGB && + format == ImageComponent.FORMAT_RGBA8) { + for (h = 0; h < copyheight; h++) { + i = srcBegin; + j = dstBegin; + for (w = 0; w < copywidth; w++, i++) { + intPixel = intData[i]; + image[j++] = (byte)((intPixel >> 16) & 0xff); + image[j++] = (byte)((intPixel >> 8) & 0xff); + image[j++] = (byte)(intPixel & 0xff); + image[j++] = (byte)((intPixel >> 24) & 0xff); + } + srcBegin += rowOffset; + dstBegin += dstBytesPerRow; + } + } else { // format == ImageComponent.FORMAT_RGB8 + for (h = 0; h < copyheight; h++) { + i = srcBegin; + j = dstBegin; + for (w = 0; w < copywidth; w++, i++) { + intPixel = intData[i]; + image[j++] = (byte)((intPixel >> 16) & 0xff); + image[j++] = (byte)((intPixel >> 8) & 0xff); + image[j++] = (byte)(intPixel & 0xff); + image[j++] = (byte)255; + } + srcBegin += rowOffset; + dstBegin += dstBytesPerRow; + } + } + + } else if ((biType == BufferedImage.TYPE_BYTE_GRAY) && + (format == ImageComponent.FORMAT_CHANNEL8)) { + + byte[] byteData = + ((DataBufferByte)bi.getRaster().getDataBuffer()).getData(); + int rowOffset = rowInc * width; + + j = dstBegin; + srcBegin = rowBegin * width + srcX; + + for (h = 0; h < copyheight; + h++, j += width, srcBegin += rowOffset) { + System.arraycopy(byteData, srcBegin, image, j, copywidth); + } + } else { + // non-optimized cases + + WritableRaster ras = bi.getRaster(); + ColorModel cm = bi.getColorModel(); + pixel = getDataElementBuffer(ras); + + switch(format) { + case ImageComponent.FORMAT_RGBA8: + case ImageComponent.FORMAT_RGB5_A1: + case ImageComponent.FORMAT_RGBA4: { + for (row = rowBegin, h = 0; + h < copyheight; h++, row += rowInc) { + j = dstBegin; + for (w = srcX; w < (copywidth + srcX); w++) { + ras.getDataElements(w, row, pixel); + image[j++] = (byte)cm.getRed(pixel); + image[j++] = (byte)cm.getGreen(pixel); + image[j++] = (byte)cm.getBlue(pixel); + image[j++] = (byte)cm.getAlpha(pixel); + } + dstBegin += dstBytesPerRow; + } + } + break; + + case ImageComponent.FORMAT_RGB8: + case ImageComponent.FORMAT_RGB5: + case ImageComponent.FORMAT_RGB4: + case ImageComponent.FORMAT_R3_G3_B2: { + for (row = rowBegin, h = 0; + h < copyheight; h++, row += rowInc) { + j = dstBegin; + for (w = srcX; w < (copywidth + srcX); w++) { + ras.getDataElements(w, row, pixel); + image[j++] = (byte)cm.getRed(pixel); + image[j++] = (byte)cm.getGreen(pixel); + image[j++] = (byte)cm.getBlue(pixel); + image[j++] = (byte)255; + } + dstBegin += dstBytesPerRow; + } + } + break; + + case ImageComponent.FORMAT_LUM8_ALPHA8: + case ImageComponent.FORMAT_LUM4_ALPHA4: { + for (row = rowBegin, h = 0; + h < copyheight; h++, row += rowInc) { + j = dstBegin; + for (w = srcX; w < (copywidth + srcX); w++) { + ras.getDataElements(w, row, pixel); + image[j++] = (byte)cm.getRed(pixel); + image[j++] = (byte)cm.getAlpha(pixel); + } + dstBegin += dstBytesPerRow; + } + } + break; + + case ImageComponent.FORMAT_CHANNEL8: { + for (row = rowBegin, h = 0; + h < copyheight; h++, row += rowInc) { + j = dstBegin; + for (w = srcX; w < (copywidth + srcX); w++) { + ras.getDataElements(w, row, pixel); + image[j++] = (byte)cm.getRed(pixel); + } + dstBegin += dstBytesPerRow; + } + } + break; + } + } + } + } + + + + + final int getBytesStored(int f) { + int val = 0; + switch(f) { + case BYTE_RGBA: + val = 4; + break; + case BYTE_ABGR: + val = 4; + break; + case BYTE_GRAY: + val = 1; + break; + case USHORT_GRAY: + val = 2; + break; + case BYTE_LA: + val = 2; + break; + case BYTE_BGR:; + val = 3; + break; + case BYTE_RGB:; + val = 3; + break; + } + return val; + } + + + boolean is4ByteRGBAOr3ByteRGB(RenderedImage ri) { + boolean value = false; + int i; + int biType = getImageType(ri); + if (biType != BufferedImage.TYPE_CUSTOM) + return false; + ColorModel cm = ri.getColorModel(); + ColorSpace cs = cm.getColorSpace(); + SampleModel sm = ri.getSampleModel(); + boolean isAlphaPre = cm.isAlphaPremultiplied(); + int csType = cs.getType(); + if ( csType == ColorSpace.TYPE_RGB) { + int numBands = sm.getNumBands(); + if (sm.getDataType() == DataBuffer.TYPE_BYTE) { + if (cm instanceof ComponentColorModel && + sm instanceof PixelInterleavedSampleModel) { + PixelInterleavedSampleModel csm = + (PixelInterleavedSampleModel) sm; + int[] offs = csm.getBandOffsets(); + ComponentColorModel ccm = (ComponentColorModel)cm; + int[] nBits = ccm.getComponentSize(); + boolean is8Bit = true; + for (i=0; i < numBands; i++) { + if (nBits[i] != 8) { + is8Bit = false; + break; + } + } + if (is8Bit && + offs[0] == 0 && + offs[1] == 1 && + offs[2] == 2) { + if (numBands == 3) { + if (format == ImageComponent.FORMAT_RGB) + value = true; + } + else if (offs[3] == 3 && !isAlphaPre) { + if (format == ImageComponent.FORMAT_RGBA) + value = true; + } + } + } + } + } + return value; + } + + final int getImageType(RenderedImage ri) { + int imageType = BufferedImage.TYPE_CUSTOM; + int i; + + if (ri instanceof BufferedImage) { + return ((BufferedImage)ri).getType(); + } + ColorModel cm = ri.getColorModel(); + ColorSpace cs = cm.getColorSpace(); + SampleModel sm = ri.getSampleModel(); + int csType = cs.getType(); + boolean isAlphaPre = cm.isAlphaPremultiplied(); + if ( csType != ColorSpace.TYPE_RGB) { + if (csType == ColorSpace.TYPE_GRAY && + cm instanceof ComponentColorModel) { + if (sm.getDataType() == DataBuffer.TYPE_BYTE) { + imageType = BufferedImage.TYPE_BYTE_GRAY; + } else if (sm.getDataType() == DataBuffer.TYPE_USHORT) { + imageType = BufferedImage.TYPE_USHORT_GRAY; + } + } + } + // RGB , only interested in BYTE ABGR and BGR for now + // all others will be copied to a buffered image + else { + int numBands = sm.getNumBands(); + if (sm.getDataType() == DataBuffer.TYPE_BYTE) { + if (cm instanceof ComponentColorModel && + sm instanceof PixelInterleavedSampleModel) { + PixelInterleavedSampleModel csm = + (PixelInterleavedSampleModel) sm; + int[] offs = csm.getBandOffsets(); + ComponentColorModel ccm = (ComponentColorModel)cm; + int[] nBits = ccm.getComponentSize(); + boolean is8Bit = true; + for (i=0; i < numBands; i++) { + if (nBits[i] != 8) { + is8Bit = false; + break; + } + } + if (is8Bit && + offs[0] == numBands-1 && + offs[1] == numBands-2 && + offs[2] == numBands-3) { + if (numBands == 3) { + imageType = BufferedImage.TYPE_3BYTE_BGR; + } + else if (offs[3] == 0) { + imageType = (isAlphaPre + ? BufferedImage.TYPE_4BYTE_ABGR_PRE + : BufferedImage.TYPE_4BYTE_ABGR); + } + } + } + } + } + return imageType; + } + + + + + /** + * Retrieves the bufferedImage at the specified depth level + */ + final void retrieveBufferedImage(int depth) { + + // create BufferedImage if one doesn't exist + if (bImage[depth] == null) { + if (format == ImageComponent.FORMAT_RGBA || + format == ImageComponent.FORMAT_RGBA4 || + format == ImageComponent.FORMAT_RGB5_A1 || + format == ImageComponent.FORMAT_LUM4_ALPHA4 || + format == ImageComponent.FORMAT_LUM8_ALPHA8) { + bImage[depth] = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + } + else + bImage[depth] = new BufferedImage(width, height, + BufferedImage.TYPE_INT_RGB); + } + + if (usedByTexture || !usedByRaster) { + copyToBufferedImage(imageYup, depth, true); + } else { + copyToBufferedImage(imageYdown[0], depth, false); + } + imageDirty[depth] = false; + + } + + /** + * Copy Image from RGBA to the user defined bufferedImage + */ + final void copyBufferedImageWithFormatConversion(boolean usedByTexture, int depth) { + int w, h, i, j; + int dstBegin, dstInc, dstIndex, dstIndexInc; + // Note that if a copy has been made, then its always a bufferedImage + // and not a renderedImage + BufferedImage bi = (BufferedImage)bImage[depth]; + int biType = bi.getType(); + byte[] buf; + + // convert from Ydown to Yup for texture + if (!yUp) { + if (usedByTexture == true) { + dstInc = -1 * width; + dstBegin = (height - 1) * width; + dstIndex = height -1; + dstIndexInc = -1; + buf = imageYup; + } else { + dstInc = width; + dstBegin = 0; + dstIndex = 0; + dstIndexInc = 1; + buf = imageYdown[0]; + } + } + else { + if (usedByTexture == true) { + dstInc = width; + dstBegin = 0; + dstIndex = 0; + dstIndexInc = 1; + buf = imageYup; + } + else { + dstInc = -1 * width; + dstBegin = (height - 1) * width; + dstIndex = height -1; + dstIndexInc = -1; + buf = imageYdown[0]; + } + } + + switch (biType) { + case BufferedImage.TYPE_INT_ARGB: + int[] intData = + ((DataBufferInt)bi.getRaster().getDataBuffer()).getData(); + // Multiply by 4 to get the byte incr and start point + j = 0; + for(h = 0; h < height; h++, dstBegin += dstInc) { + i = dstBegin; + for (w = 0; w < width; w++, j+=4, i++) { + intData[i] = (((buf[j+3] &0xff) << 24) | // a + ((buf[j] &0xff) << 16) | // r + ((buf[j+1] &0xff) << 8) | // g + (buf[j+2] & 0xff)); // b + + + } + } + break; + + case BufferedImage.TYPE_INT_RGB: + intData = + ((DataBufferInt)bi.getRaster().getDataBuffer()).getData(); + // Multiply by 4 to get the byte incr and start point + j = 0; + for(h = 0; h < height; h++, dstBegin += dstInc) { + i = dstBegin; + for (w = 0; w < width; w++, j+=4, i++) { + intData[i] = (0xff000000 | // a + ((buf[j] &0xff) << 16) | // r + ((buf[j+1] &0xff) << 8) | // g + (buf[j+2] & 0xff)); // b + + + } + } + break; + + case BufferedImage.TYPE_4BYTE_ABGR: + byte[] byteData = + ((DataBufferByte)bi.getRaster().getDataBuffer()).getData(); + // Multiply by 4 to get the byte incr and start point + j = 0; + for(h = 0; h < height; h++, dstBegin += (dstInc << 2)) { + i = dstBegin; + for (w = 0; w < width; w++, j+=4) { + + byteData[i++] = buf[j+3]; // a + byteData[i++] = buf[j+2]; // b + byteData[i++] = buf[j+1];// g + byteData[i++] = buf[j]; // r + } + } + break; + case BufferedImage.TYPE_INT_BGR: + intData = + ((DataBufferInt)bi.getRaster().getDataBuffer()).getData(); + // Multiply by 4 to get the byte incr and start point + j = 0; + + for(h = 0; h < height; h++, dstBegin += dstInc) { + i = dstBegin; + for (w = 0; w < width; w++, j+=4, i++) { + intData[i] = (0xff000000 | // a + ((buf[j] &0xff) ) | // r + ((buf[j+1] &0xff) << 8) | // g + (buf[j+2] & 0xff)<< 16); // b + + + } + } + break; + case BufferedImage.TYPE_BYTE_GRAY: + byteData = + ((DataBufferByte)bi.getRaster().getDataBuffer()).getData(); + j = 0; + for( h = 0; h < height; h++, dstBegin += dstInc) { + System.arraycopy(byteData, dstBegin, buf, j, width); + j += width; + } + break; + case BufferedImage.TYPE_USHORT_GRAY: + int pixel; + j = 0; + short[] shortData = + ((DataBufferShort)bi.getRaster().getDataBuffer()).getData(); + // Multiply by 4 to get the byte incr and start point + for(h = 0; h < height; h++, dstBegin+= dstInc) { + i = dstBegin; + for (w = 0; w < width; w++, i++, j++) { + shortData[i] = (short)buf[j]; + } + } + break; + + default: + j = 0; + for( h = 0; h < height; h++, dstIndex += dstIndexInc) { + i = dstIndex; + for (w = 0; w < width; w++, j+=4) { + pixel = (((buf[j+3] &0xff) << 24) | // a + ((buf[j] &0xff) << 16) | // r + ((buf[j+1] &0xff) << 8) | // g + (buf[j+2] & 0xff)); // b + bi.setRGB(w, i, pixel); + + } + } + break; + } + + } + + /** + * Copy image data from ImageComponent's internal representation + * to Buffered Image + */ + final void copyToBufferedImage(byte[] buf, int depth, + boolean usedByTexture) { + + int w, h, i, j; + int dstBegin, dstInc, srcBegin; + + + // convert from Ydown to Yup for texture + if (!yUp) { + if (usedByTexture == true) { + srcBegin = depth * width * height * bytesPerYupPixelStored; + dstInc = -1 * width; + dstBegin = (height - 1) * width; + } else { + srcBegin = 0; + dstInc = width; + dstBegin = 0; + } + } + else { + if (usedByTexture == true) { + srcBegin = 0; + dstInc = width; + dstBegin = 0; + } + else { + srcBegin = depth * width * height * bytesPerYdownPixelStored; + dstInc = -1 * width; + dstBegin = (height - 1) * width; + } + } + + // Note that if a copy has been made, then its always a bufferedImage + // and not a renderedImage + int[] intData = ((DataBufferInt) + ((BufferedImage)bImage[depth]).getRaster().getDataBuffer()).getData(); + + switch(format) { + case ImageComponent.FORMAT_RGBA8: + case ImageComponent.FORMAT_RGB5_A1: + case ImageComponent.FORMAT_RGBA4: + + for (j = srcBegin, h = 0; h < height; h++, dstBegin += dstInc) { + i = dstBegin; + for (w = 0; w < width; w++, j+=4, i++) { + intData[i] = ((buf[j+3] & 0xff) << 24) | + ((buf[j] & 0xff) << 16) | + ((buf[j+1] & 0xff) << 8) | + (buf[j+2] & 0xff); + } + } + break; + + + case ImageComponent.FORMAT_RGB8: + case ImageComponent.FORMAT_RGB5: + case ImageComponent.FORMAT_RGB4: + case ImageComponent.FORMAT_R3_G3_B2: + for (j = srcBegin, h = 0; h < height; h++, dstBegin += dstInc) { + i = dstBegin; + for (w = 0; w < width; w++, j+=4, i++) { + intData[i] = ((buf[j] & 0xff) << 16) | + ((buf[j+1] & 0xff) << 8) | + (buf[j+2] & 0xff); + } + } + break; + + case ImageComponent.FORMAT_LUM8_ALPHA8: + case ImageComponent.FORMAT_LUM4_ALPHA4: + for (j = srcBegin, h = 0; h < height; h++, dstBegin += dstInc) { + i = dstBegin; + for (w = 0; w < width; w++, j+=2, i++) { + intData[i] = ((buf[j+1] & 0xff) << 24) | + ((buf[j] & 0xff) << 16); + } + } + break; + + case ImageComponent.FORMAT_CHANNEL8: + for (j = srcBegin, h = 0; h < height; h++, dstBegin += dstInc) { + i = dstBegin; + for (w = 0; w < width; w++, j++, i++) { + intData[i] = ((buf[j] & 0xff) << 16); + } + } + break; + } + } + + Object getData(DataBuffer buffer) { + Object data = null; + switch (buffer.getDataType()) { + case DataBuffer.TYPE_BYTE: + data = ((DataBufferByte)buffer).getData(); + break; + case DataBuffer.TYPE_INT: + data = ((DataBufferInt)buffer).getData(); + break; + case DataBuffer.TYPE_SHORT: + data = ((DataBufferShort)buffer).getData(); + break; + } + return data; + } + + final void setByReference(boolean byReference) { + this.byReference = byReference; + } + + final boolean isByReference() { + return byReference; + } + + final void setYUp( boolean yUp) { + this.yUp = yUp; + } + + final boolean isYUp() { + return yUp; + } + + // Add a user to the userList + synchronized void addUser(NodeComponentRetained node) { + userList.add(node); + } + + // Add a user to the userList + synchronized void removeUser(NodeComponentRetained node) { + int i = userList.indexOf(node); + if (i >= 0) { + userList.remove(i); + } + } + + /** + * ImageComponent object doesn't really have mirror object. + * But it's using the updateMirrorObject interface to propagate + * the changes to the users + */ + synchronized void updateMirrorObject(int component, Object value) { + + //System.out.println("ImageComponent.updateMirrorObject"); + + Object user; + + if (((component & IMAGE_CHANGED) != 0) || + ((component & SUBIMAGE_CHANGED) != 0)) { + synchronized(userList) { + for (int i = userList.size()-1; i >=0; i--) { + user = userList.get(i); + if (user != null) { + if (user instanceof TextureRetained) { + ((TextureRetained)user).notifyImageComponentImageChanged(this, (ImageComponentUpdateInfo)value); + } else if (user instanceof RasterRetained) { + ((RasterRetained)user).notifyImageComponentImageChanged(this, (ImageComponentUpdateInfo)value); + } else if (user instanceof BackgroundRetained) { + ((BackgroundRetained)user).notifyImageComponentImageChanged(this, (ImageComponentUpdateInfo)value); + } + } + } + } + + // return the subimage update info to the free list + if (value != null) { + VirtualUniverse.mc.addFreeImageUpdateInfo( + (ImageComponentUpdateInfo)value); + } + } + } + + final void sendMessage(int attrMask, Object attr) { + + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES | + J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.IMAGE_COMPONENT_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + } + + void handleFrequencyChange(int bit) { + if (bit == ImageComponent.ALLOW_IMAGE_WRITE) { + setFrequencyChangeMask(ImageComponent.ALLOW_IMAGE_WRITE, 0x1); + } + } + + static Object getDataElementBuffer(java.awt.image.Raster ras) { + int nc = ras.getNumDataElements(); + + switch (ras.getTransferType()) { + case DataBuffer.TYPE_INT: + return new int[nc]; + case DataBuffer.TYPE_BYTE: + return new byte[nc]; + case DataBuffer.TYPE_USHORT: + case DataBuffer.TYPE_SHORT: + return new short[nc]; + case DataBuffer.TYPE_FLOAT: + return new float[nc]; + case DataBuffer.TYPE_DOUBLE: + return new double[nc]; + } + // Should not happen + return null; + } +} diff --git a/src/classes/share/javax/media/j3d/ImageComponentUpdateInfo.java b/src/classes/share/javax/media/j3d/ImageComponentUpdateInfo.java new file mode 100644 index 0000000..e58caa1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ImageComponentUpdateInfo.java @@ -0,0 +1,30 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Image Component update information for the users + */ +class ImageComponentUpdateInfo extends Object { + + int x = 0; + int y = 0; + int z = 0; + int width = 0; + int height = 0; + int updateMask = 0; // which resources need to be updated + // canvas or renderer + boolean entireImage = false;// true if the entire image needs to be updated + // then none of the dimension info is to be + // applied. +} diff --git a/src/classes/share/javax/media/j3d/IndexedGeometryArray.java b/src/classes/share/javax/media/j3d/IndexedGeometryArray.java new file mode 100644 index 0000000..8020849 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedGeometryArray.java @@ -0,0 +1,883 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * The IndexedGeometryArray object contains separate integer arrays + * that index into the arrays of positional coordinates, colors, + * normals, and texture coordinates. These index arrays specify how + * vertices are connected to form geometry primitives. This class is + * extended to create the various indexed primitive types (e.g., + * lines, triangle strips, etc.). + */ + +public abstract class IndexedGeometryArray extends GeometryArray { + + // non-public, no parameter constructor + IndexedGeometryArray() {} + + /** + * Specifies that this IndexedGeometryArray allows reading the array of + * coordinate indices. + */ + public static final int + ALLOW_COORDINATE_INDEX_READ = CapabilityBits.INDEXED_GEOMETRY_ARRAY_ALLOW_COORDINATE_INDEX_READ; + + /** + * Specifies that this IndexedGeometryArray allows writing the array of + * coordinate indices. + */ + public static final int + ALLOW_COORDINATE_INDEX_WRITE = CapabilityBits.INDEXED_GEOMETRY_ARRAY_ALLOW_COORDINATE_INDEX_WRITE; + + /** + * Specifies that this IndexedGeometryArray allows reading the array of + * color indices. + */ + public static final int + ALLOW_COLOR_INDEX_READ = CapabilityBits.INDEXED_GEOMETRY_ARRAY_ALLOW_COLOR_INDEX_READ; + + /** + * Specifies that this IndexedGeometryArray allows writing the array of + * color indices. + */ + public static final int + ALLOW_COLOR_INDEX_WRITE = CapabilityBits.INDEXED_GEOMETRY_ARRAY_ALLOW_COLOR_INDEX_WRITE; + + /** + * Specifies that this IndexedGeometryArray allows reading the array of + * normal indices. + */ + public static final int + ALLOW_NORMAL_INDEX_READ = CapabilityBits.INDEXED_GEOMETRY_ARRAY_ALLOW_NORMAL_INDEX_READ; + + /** + * Specifies that this IndexedGeometryArray allows writing the array of + * normal indices. + */ + public static final int + ALLOW_NORMAL_INDEX_WRITE = CapabilityBits.INDEXED_GEOMETRY_ARRAY_ALLOW_NORMAL_INDEX_WRITE; + + /** + * Specifies that this IndexedGeometryArray allows reading the array of + * texture coordinate indices. + */ + public static final int + ALLOW_TEXCOORD_INDEX_READ = CapabilityBits.INDEXED_GEOMETRY_ARRAY_ALLOW_TEXCOORD_INDEX_READ; + + /** + * Specifies that this IndexedGeometryArray allows writing the array of + * texture coordinate indices. + */ + public static final int + ALLOW_TEXCOORD_INDEX_WRITE = CapabilityBits.INDEXED_GEOMETRY_ARRAY_ALLOW_TEXCOORD_INDEX_WRITE; + + /** + * Constructs an empty IndexedGeometryArray object with the specified + * number of vertices, vertex format, and number of indices. + * Defaults are used for all other parameters. The default values + * are as follows: + * + * <ul> + * validIndexCount : indexCount<br> + * initialIndexIndex : 0<br> + * all index array values : 0<br> + * </ul> + * + * @param vertexCount the number of vertex elements in this + * IndexedGeometryArray + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or + * TEXTURE_COORDINATE_4, to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + */ + public IndexedGeometryArray(int vertexCount, + int vertexFormat, + int indexCount) { + super(vertexCount, vertexFormat); + ((IndexedGeometryArrayRetained)this.retained).createIndexedGeometryArrayData(indexCount); + } + + /** + * Constructs an empty IndexedGeometryArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and number of indices. + * Defaults are used for all other parameters. + * + * @param vertexCount the number of vertex elements in this + * IndexedGeometryArray<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D , 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * + * @since Java 3D 1.2 + */ + public IndexedGeometryArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount) { + super(vertexCount, vertexFormat, texCoordSetCount, texCoordSetMap); + ((IndexedGeometryArrayRetained)this.retained).createIndexedGeometryArrayData(indexCount); + } + + /** + * Gets number of indices for this IndexedGeometryArray. + * @return indexCount the number of indices + */ + public int getIndexCount(){ + if (isLiveOrCompiled()) + if(!this.getCapability(GeometryArray.ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray0")); + + return ((IndexedGeometryArrayRetained)this.retained).getIndexCount(); + } + + /** + * Sets the valid index count for this IndexedGeometryArray object. + * This count specifies the number of indexed vertices actually used + * in rendering or other operations such as picking and collision. + * This attribute is initialized to <code>indexCount</code>. + * + * @param validIndexCount the new valid index count. + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @exception IllegalArgumentException if either of the following is true: + * <ul> + * <code>validIndexCount < 0</code>, or<br> + * <code>initialIndexIndex + validIndexCount > indexCount</code><br> + * </ul> + * + * @exception ArrayIndexOutOfBoundsException if any element in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the index array associated with any of the enabled vertex + * components (coord, color, normal, texcoord) is out of range. + * An element is out of range if it is less than 0 or is greater + * than or equal to the number of vertices actually defined for + * the particular component's array. + * + * @since Java 3D 1.3 + */ + public void setValidIndexCount(int validIndexCount) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray16")); + + ((IndexedGeometryArrayRetained)this.retained).setValidIndexCount(validIndexCount); + } + + /** + * Gets the valid index count for this IndexedGeometryArray + * object. For geometry strip primitives (subclasses of + * IndexedGeometryStripArray), the valid index count is defined + * to be the sum of the stripIndexCounts array. + * + * @return the current valid index count + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getValidIndexCount() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray17")); + + return ((IndexedGeometryArrayRetained)this.retained).getValidIndexCount(); + } + + /** + * Sets the initial index index for this IndexedGeometryArray object. + * This index specifies the first index within this indexed geometry + * array that is actually used in rendering or other operations + * such as picking and collision. This attribute is initialized + * to 0. + * + * @param initialIndexIndex the new initial index index. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * @exception IllegalArgumentException if either of the following is true: + * <ul> + * <code>initialIndexIndex < 0</code>, or<br> + * <code>initialIndexIndex + validIndexCount > indexCount</code><br> + * </ul> + * + * @exception ArrayIndexOutOfBoundsException if any element in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * in the index array associated with any of the enabled vertex + * components (coord, color, normal, texcoord) is out of range. + * An element is out of range if it is less than 0 or is greater + * than or equal to the number of vertices actually defined for + * the particular component's array. + * + * @since Java 3D 1.3 + */ + public void setInitialIndexIndex(int initialIndexIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray18")); + + if (initialIndexIndex < 0) + throw new IllegalArgumentException(J3dI18N.getString("IndexedGeometryArray20")); + + + ((IndexedGeometryArrayRetained)this.retained).setInitialIndexIndex(initialIndexIndex); + } + + /** + * Gets the initial index index for this IndexedGeometryArray object. + * @return the current initial index index + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this object is part of a live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getInitialIndexIndex() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray19")); + + return ((IndexedGeometryArrayRetained)this.retained).getInitialIndexIndex(); + + } + + /** + * This method is not supported for indexed geometry arrays. + * Indexed primitives use an array of indices to determine how + * to access the vertex array. + * The initialIndexIndex attribute can be used to set the starting + * index within the index arrays. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setInitialVertexIndex(int initialVertexIndex) { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported for indexed geometry arrays. + * Indexed primitives use an array of indices to determine how + * to access the vertex array. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setInitialCoordIndex(int initialCoordIndex) { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported for indexed geometry arrays. + * Indexed primitives use an array of indices to determine how + * to access the vertex array. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setInitialColorIndex(int initialColorIndex) { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported for indexed geometry arrays. + * Indexed primitives use an array of indices to determine how + * to access the vertex array. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setInitialNormalIndex(int initialNormalIndex) { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported for indexed geometry arrays. + * Indexed primitives use an array of indices to determine how + * to access the vertex array. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setInitialTexCoordIndex(int texCoordSet, + int initialTexCoordIndex) { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported for indexed geometry arrays. + * Indexed primitives use an array of indices to determine how + * to access the vertex array. + * The validIndexCount attribute can be used to set the number of + * valid indexed vertices rendered. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setValidVertexCount(int validVertexCount) { + throw new UnsupportedOperationException(); + } + + + /** + * Sets the coordinate index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @param coordinateIndex the new coordinate index + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if index is less than 0 + * or is greater than or equal to indexCount + * + * @exception ArrayIndexOutOfBoundsException if index is in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * and the specified coordinateIndex is out of range. The + * coordinateIndex is out of range if it is less than 0 or is + * greater than or equal to the number of vertices actually + * defined for the coordinate array. + */ + public void setCoordinateIndex(int index, int coordinateIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_INDEX_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray1")); + + ((IndexedGeometryArrayRetained)this.retained).setCoordinateIndex(index, coordinateIndex); + } + + /** + * Sets the coordinate indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param coordinateIndices an array of coordinate indices + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if index is less than 0 + * or is greater than or equal to indexCount + * + * @exception ArrayIndexOutOfBoundsException if any element of the + * coordinateIndices array whose destination position is in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * is out of range. An element is out of range if it is less than 0 + * or is greater than or equal to the number of vertices actually + * defined for the coordinate array. + */ + public void setCoordinateIndices(int index, int coordinateIndices[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_INDEX_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray1")); + + ((IndexedGeometryArrayRetained)this.retained).setCoordinateIndices(index, coordinateIndices); + } + + /** + * Sets the color index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @param colorIndex the new color index + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if index is less than 0 + * or is greater than or equal to indexCount + * + * @exception ArrayIndexOutOfBoundsException if index is in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * and the specified colorIndex is out of range. The + * colorIndex is out of range if it is less than 0 or is + * greater than or equal to the number of vertices actually + * defined for the color array. + */ + public void setColorIndex(int index, int colorIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_INDEX_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray3")); + + ((IndexedGeometryArrayRetained)this.retained).setColorIndex(index, colorIndex); + } + + /** + * Sets the color indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param colorIndices an array of color indices + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if index is less than 0 + * or is greater than or equal to indexCount + * + * @exception ArrayIndexOutOfBoundsException if any element of the + * colorIndices array whose destination position is in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * is out of range. An element is out of range if it is less than 0 + * or is greater than or equal to the number of vertices actually + * defined for the color array. + */ + public void setColorIndices(int index, int colorIndices[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_INDEX_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray3")); + + ((IndexedGeometryArrayRetained)this.retained).setColorIndices(index, colorIndices); + } + + /** + * Sets the normal index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @param normalIndex the new normal index + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if index is less than 0 + * or is greater than or equal to indexCount + * + * @exception ArrayIndexOutOfBoundsException if index is in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * and the specified normalIndex is out of range. The + * normalIndex is out of range if it is less than 0 or is + * greater than or equal to the number of vertices actually + * defined for the normal array. + */ + public void setNormalIndex(int index, int normalIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_INDEX_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray5")); + + ((IndexedGeometryArrayRetained)this.retained).setNormalIndex(index, normalIndex); + } + + /** + * Sets the normal indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param normalIndices an array of normal indices + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if index is less than 0 + * or is greater than or equal to indexCount + * + * @exception ArrayIndexOutOfBoundsException if any element of the + * normalIndices array whose destination position is in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * is out of range. An element is out of range if it is less than 0 + * or is greater than or equal to the number of vertices actually + * defined for the normal array. + */ + public void setNormalIndices(int index, int normalIndices[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_INDEX_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray5")); + + ((IndexedGeometryArrayRetained)this.retained).setNormalIndices(index, normalIndices); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinateIndex(int texCoordSet, ...)</code> + */ + public void setTextureCoordinateIndex(int index, int texCoordIndex) { + setTextureCoordinateIndex(0, index, texCoordIndex); + } + + /** + * Sets the texture coordinate index associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index the vertex index + * @param texCoordIndex the new texture coordinate index + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if neither of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception ArrayIndexOutOfBoundsException if index is in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * and the specified texCoordIndex is out of range. The + * texCoordIndex is out of range if it is less than 0 or is + * greater than or equal to the number of vertices actually + * defined for the texture coordinate array. + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinateIndex(int texCoordSet, + int index, + int texCoordIndex) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_INDEX_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray7")); + + ((IndexedGeometryArrayRetained)this.retained).setTextureCoordinateIndex(texCoordSet, index, texCoordIndex); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setTextureCoordinateIndices(int texCoordSet, ...)</code> + */ + public void setTextureCoordinateIndices(int index, int texCoordIndices[]) { + setTextureCoordinateIndices(0, index, texCoordIndices); + } + + /** + * Sets the texture coordinate indices associated with the vertices + * starting at the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index the vertex index + * @param texCoordIndices an array of texture coordinate indices + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if neither of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @exception ArrayIndexOutOfBoundsException if any element of the + * texCoordIndices array whose destination position is in the range + * <code>[initialIndexIndex, initialIndexIndex+validIndexCount-1]</code> + * is out of range. An element is out of range if it is less than 0 + * or is greater than or equal to the number of vertices actually + * defined for the texture coordinate array. + * + * @since Java 3D 1.2 + */ + public void setTextureCoordinateIndices(int texCoordSet, + int index, + int texCoordIndices[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_INDEX_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray7")); + + ((IndexedGeometryArrayRetained)this.retained).setTextureCoordinateIndices(texCoordSet, index, texCoordIndices); + } + + /** + * Retrieves the coordinate index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @return the coordinate index + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getCoordinateIndex(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_INDEX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray9")); + + return ((IndexedGeometryArrayRetained)this.retained).getCoordinateIndex(index); + } + + /** + * Retrieves the coordinate indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param coordinateIndices array that will receive the coordinate indices + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getCoordinateIndices(int index, int coordinateIndices[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_INDEX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray9")); + + ((IndexedGeometryArrayRetained)this.retained).getCoordinateIndices(index, coordinateIndices); + } + + /** + * Retrieves the color index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @return the color index + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getColorIndex(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_INDEX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray11")); + + return ((IndexedGeometryArrayRetained)this.retained).getColorIndex(index); + } + + /** + * Retrieves the color indices associated with the vertices starting at + * the specified index for this object. The color indicies are + * copied into the specified array. The array must be large enough + * to hold all of the indices. + * @param index the vertex index + * @param colorIndices array that will receive the color indices + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getColorIndices(int index, int colorIndices[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_INDEX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray11")); + + ((IndexedGeometryArrayRetained)this.retained).getColorIndices(index, colorIndices); + } + + /** + * Retrieves the normal index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @return the normal index + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getNormalIndex(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_INDEX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray13")); + + return ((IndexedGeometryArrayRetained)this.retained).getNormalIndex(index); + } + + /** + * Retrieves the normal indices associated with the vertices starting at + * the specified index for this object. The normal indicies are + * copied into the specified array. The array must be large enough + * to hold all of the normal indicies. + * + * @param index the vertex index + * @param normalIndices array that will receive the normal indices + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getNormalIndices(int index, int normalIndices[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_INDEX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray13")); + + ((IndexedGeometryArrayRetained)this.retained).getNormalIndices(index, normalIndices); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getTextureCoordinateIndex(int texCoordSet, ...)</code> + */ + public int getTextureCoordinateIndex(int index) { + return (getTextureCoordinateIndex(0, index)); + } + + /** + * Retrieves the texture coordinate index associated with the vertex at + * the specified index in the specified texture coordinate set + * for this object. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index the vertex index + * + * @return the texture coordinate index + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if neither of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @since Java 3D 1.2 + */ + public int getTextureCoordinateIndex(int texCoordSet, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_INDEX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray15")); + + return ((IndexedGeometryArrayRetained)this.retained).getTextureCoordinateIndex(texCoordSet, index); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getTextureCoordinateIndices(int texCoordSet, ...)</code> + */ + public void getTextureCoordinateIndices(int index, int texCoordIndices[]) { + getTextureCoordinateIndices(0, index, texCoordIndices); + } + + + /** + * Retrieves the texture coordinate indices associated with the vertices + * starting at the specified index in the specified texture coordinate set + * for this object. The texture + * coordinate indices are copied into the specified array. The array + * must be large enough to hold all of the indices. + * + * @param texCoordSet texture coordinate set in this geometry array + * @param index the vertex index + * @param texCoordIndices array that will receive the texture coordinate + * indices + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception ArrayIndexOutOfBoundsException if neither of the + * <code>TEXTURE_COORDINATE</code> bits are set in the + * <code>vertexFormat</code> or if the index or + * texCoordSet is out of range. + * + * @since Java 3D 1.2 + */ + public void getTextureCoordinateIndices(int texCoordSet, + int index, + int texCoordIndices[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COORDINATE_INDEX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryArray15")); + + ((IndexedGeometryArrayRetained)this.retained).getTextureCoordinateIndices(texCoordSet, index, texCoordIndices); + } + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + // vertexFormat, vertexCount and indexCount are copied in + // subclass when constructor + // public IndexedGeometryArray(int vertexCount, int vertexFormat, + // int indexCount) + // is used in cloneNodeComponent() + IndexedGeometryArrayRetained ga = + (IndexedGeometryArrayRetained) originalNodeComponent.retained; + IndexedGeometryArrayRetained rt = + (IndexedGeometryArrayRetained) retained; + + int vformat = ga.getVertexFormat(); + int buffer[] = new int[ga.getIndexCount()]; + + if ((vformat & GeometryArray.COORDINATES) != 0) { + ga.getCoordinateIndices(0, buffer); + rt.setCoordinateIndices(0, buffer); + } + + if ((vformat & GeometryArray.NORMALS) != 0) { + ga.getNormalIndices(0, buffer); + rt.setNormalIndices(0, buffer); + } + + if ((vformat & GeometryArray.COLOR) != 0) { + ga.getColorIndices(0, buffer); + rt.setColorIndices(0, buffer); + } + + if ((vformat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < ga.texCoordSetCount; i++) { + ga.getTextureCoordinateIndices(i, 0, buffer); + rt.setTextureCoordinateIndices(i, 0, buffer); + } + } + } + +} diff --git a/src/classes/share/javax/media/j3d/IndexedGeometryArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedGeometryArrayRetained.java new file mode 100644 index 0000000..632e4ec --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedGeometryArrayRetained.java @@ -0,0 +1,1609 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Vector; +import java.util.ArrayList; +import com.sun.j3d.internal.ByteBufferWrapper; +import com.sun.j3d.internal.BufferWrapper; +import com.sun.j3d.internal.FloatBufferWrapper; +import com.sun.j3d.internal.DoubleBufferWrapper; + + +/** + * The IndexedGeometryArray object contains arrays of positional coordinates, + * colors, normals and/or texture coordinates that describe + * point, line, or surface geometry. It is extended to create + * the various primitive types (e.g., lines, triangle_strips, etc.) + */ + +abstract class IndexedGeometryArrayRetained extends GeometryArrayRetained { + + // arrays to save indices for coord, color, normal, texcoord + int indexCoord[], indexColor[], indexNormal[]; + Object indexTexCoord[]; + + int indexCount; + + int initialIndexIndex = 0; + int validIndexCount = 0; + + // Following variables are only used in compile mode + int[] compileIndexCount; + int[] compileIndexOffset; + + int maxCoordIndex = 0; + int maxColorIndex = 0; + int maxNormalIndex = 0; + int[] maxTexCoordIndices = null; + + void createIndexedGeometryArrayData(int indexCount) { + this.indexCount = indexCount; + this.validIndexCount = indexCount; + + if((this.vertexFormat & GeometryArray.COORDINATES) != 0) + this.indexCoord = new int[indexCount]; + + if((this.vertexFormat & GeometryArray.NORMALS) != 0) + this.indexNormal = new int[indexCount]; + + if((this.vertexFormat & GeometryArray.COLOR) != 0) + this.indexColor = new int[indexCount]; + + if((this.vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + this.indexTexCoord = new Object[this.texCoordSetCount]; + for (int i = 0; i < this.texCoordSetCount; i++) { + this.indexTexCoord[i] = new int[indexCount]; + } + maxTexCoordIndices = new int[texCoordSetCount]; + } + } + + + Object cloneNonIndexedGeometry() { + GeometryArrayRetained obj = null; + int vOffset; + + switch (this.geoType) { + case GEO_TYPE_INDEXED_LINE_SET: + obj = new LineArrayRetained(); + break; + case GEO_TYPE_INDEXED_POINT_SET: + obj = new PointArrayRetained(); + break; + case GEO_TYPE_INDEXED_QUAD_SET: + obj = new QuadArrayRetained(); + break; + case GEO_TYPE_INDEXED_TRI_SET: + obj = new TriangleArrayRetained(); + break; + } + obj.createGeometryArrayData(validIndexCount, (vertexFormat & ~(GeometryArray.BY_REFERENCE|GeometryArray.INTERLEAVED|GeometryArray.USE_NIO_BUFFER)), + texCoordSetCount, texCoordSetMap); + obj.cloneSourceArray = this; + obj.unIndexify(this); + + return (Object)obj; + } + + void execute(long ctx, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha) { + throw new RuntimeException(J3dI18N.getString("IndexedGeometryArrayRetained0")); + } + + + /** + * Gets current number of indices + * @return indexCount + */ + int getIndexCount(){ + return indexCount; + } + + void doErrorCheck(int newMax) { + doCoordCheck(newMax); + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0) { + if ((vertexFormat & GeometryArray.COLOR) != 0) { + doColorCheck(newMax); + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + doTexCoordCheck(newMax, i); + } + } + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + doNormalCheck(newMax); + } + } + } + + void doCoordCheck(int newMax) { + // Check to make sure that the array length defined by the user is ateast maxCoordIndex long + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + if (newMax >= vertexCount) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } + else { + if(( vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0) { + switch ((vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case PF: + if(floatBufferRefCoords != null && 3 * newMax >= floatBufferRefCoords.limit() ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + break; + case PD: + if(doubleBufferRefCoords != null && 3 * newMax >= doubleBufferRefCoords.limit() ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + break; + } + } + else { + if(interleavedFloatBufferImpl != null && stride * newMax >= interleavedFloatBufferImpl.limit() ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } + } else { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0) { + switch ((vertexType & VERTEX_DEFINED)) { + case PF: + if (floatRefCoords != null && (3 * newMax >= floatRefCoords.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + break; + case PD: + if (doubleRefCoords != null && (3 * newMax >= doubleRefCoords.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + + break; + case P3F: + if (p3fRefCoords != null && (newMax >= p3fRefCoords.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + break; + case P3D: + if (p3dRefCoords != null && (newMax >= p3dRefCoords.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + break; + default: + break; + } + } + else { + if (interLeavedVertexData != null && (stride * newMax >= interLeavedVertexData.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray23")); + } + } + } + } + + } + + void doColorCheck(int newMax) { + // If the new Value is greater than the old value, make sure there is array length + // to support the change + // Check to make sure that the array length defined by the user is ateast maxCoordIndex long + if ((vertexFormat & GeometryArray.COLOR) == 0) + return; + + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + if (newMax >= vertexCount) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } + else { + int multiplier = getColorStride(); + + if(( vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0) { + switch ((vertexType & COLOR_DEFINED)) { + case CF: + if (floatBufferRefColors != null && multiplier * newMax >= floatBufferRefColors.limit()) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + break; + case CUB: + if (byteBufferRefColors != null && multiplier * newMax >= byteBufferRefColors.limit()) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + break; + } + } + else { + if(interleavedFloatBufferImpl != null && + stride * newMax >= interleavedFloatBufferImpl.limit()) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } + } else { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0) { + switch ((vertexType & COLOR_DEFINED)) { + case CF: + if (floatRefColors != null && (multiplier * newMax >= floatRefColors.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + break; + case CUB: + if (byteRefColors != null && (multiplier * newMax >= byteRefColors.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + + break; + case C3F: + if (c3fRefColors != null && (newMax >= c3fRefColors.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + break; + case C4F: + if (c4fRefColors != null && (newMax >= c4fRefColors.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + break; + case C3UB: + if (c3bRefColors != null && (newMax >= c3bRefColors.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + break; + case C4UB: + if (c4bRefColors != null && (newMax >= c4bRefColors.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + break; + default: + break; + } + } else { + if (interLeavedVertexData != null && (stride * newMax >= interLeavedVertexData.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray24")); + } + } + } + } + + } + + + void doNormalCheck(int newMax) { + if ((vertexFormat & GeometryArray.NORMALS) == 0) + return; + + // Check to make sure that the array length defined by the user is ateast maxCoordIndex long + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + if (newMax >= vertexCount) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + } + else { + if(( vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0) { + switch ((vertexType & GeometryArrayRetained.NORMAL_DEFINED)) { + case NF: + if(floatBufferRefNormals != null && 3 * newMax >= floatBufferRefNormals.limit() ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + break; + } + } + else { + if(interleavedFloatBufferImpl != null && stride * newMax >= interleavedFloatBufferImpl.limit() ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + } + } else { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0) { + switch ((vertexType & NORMAL_DEFINED)) { + case NF: + if (floatRefNormals != null && (3 * newMax >= floatRefNormals.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + break; + case N3F: + if (v3fRefNormals != null && (newMax >= v3fRefNormals.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + + break; + default: + break; + } + } + else { + if (interLeavedVertexData != null && (stride * newMax >= interLeavedVertexData.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray26")); + } + } + } + } + + } + + + + void doTexCoordCheck(int newMax, int texCoordSet) { + + // Check to make sure that the array length defined by the user is ateast maxCoordIndex long + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) == 0) + return; + + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + if (newMax >= vertexCount) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + } + else { + int multiplier = getTexStride(); + + if(( vertexFormat & GeometryArray.USE_NIO_BUFFER) != 0) { + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0) { + switch ((vertexType & GeometryArrayRetained.TEXCOORD_DEFINED)) { + case TF: + FloatBufferWrapper texBuffer; + texBuffer = (FloatBufferWrapper)(((J3DBuffer) refTexCoordsBuffer[texCoordSet]).getBufferImpl()); + if(refTexCoords[texCoordSet] != null && multiplier * newMax >= texBuffer.limit()) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + break; + } + } + else { + if(interleavedFloatBufferImpl != null && stride * newMax >= interleavedFloatBufferImpl.limit() ) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + } + } else { + + if ((vertexFormat & GeometryArray.INTERLEAVED) == 0) { + switch ((vertexType & TEXCOORD_DEFINED)) { + case TF: + if (refTexCoords[texCoordSet] != null && (multiplier * newMax >= ((float[])refTexCoords[texCoordSet]).length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + break; + case T2F: + if (refTexCoords[texCoordSet] != null && (newMax >= ((TexCoord2f[])refTexCoords[texCoordSet]).length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + + break; + case T3F: + if (refTexCoords[texCoordSet] != null && (newMax >= ((TexCoord3f[])refTexCoords[texCoordSet]).length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + break; + default: + break; + } + } + else { + if (interLeavedVertexData != null && (stride * newMax >= interLeavedVertexData.length)) { + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray25")); + } + } + } + } + + } + + + /** + * Sets the coordinate index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @param coordinateIndex the new coordinate index + */ + final void setCoordinateIndex(int index, int coordinateIndex) { + int newMax; + newMax = doIndexCheck(index, maxCoordIndex, indexCoord, coordinateIndex); + if (newMax > maxCoordIndex) { + doErrorCheck(newMax); + } + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0) { + if ((vertexFormat & GeometryArray.COLOR) != 0) { + maxColorIndex = newMax; + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + maxTexCoordIndices[i] = newMax; + } + } + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + maxNormalIndex = newMax; + } + } + + geomLock.getLock(); + dirtyFlag |= INDEX_CHANGED; + this.indexCoord[index] = coordinateIndex; + maxCoordIndex = newMax; + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(true); + } + } + + int doIndexCheck(int index, int maxIndex, int[] indices, int dataValue) { + int newMax = maxIndex; + if (index < initialIndexIndex) + return newMax; + + if (index >= (initialIndexIndex+validIndexCount)) + return newMax; + + if (dataValue < 0) { + // Throw an exception, since index is negative + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray27")); + + } + + if (newMax == indices[index]) { + if (dataValue >= newMax) { + newMax = dataValue; + } + // Go thru the entire list and look for the max + else { + for (int i = 0; i < indices.length; i++) { + if (indices[i] > newMax) { + newMax = indices[i]; + } + } + } + } + else if (dataValue > newMax) { + newMax = dataValue; + } + return newMax; + } + + int doIndicesCheck(int index, int maxIndex, int[] indices, int[] newIndices) { + int newMax = maxIndex; + boolean computeNewMax = false; + int i, j, num = newIndices.length; + boolean maxReset = false; + for (j = 0; j < num; j++) { + if ((index+j) < initialIndexIndex) + continue; + + if ((index+j) >= (initialIndexIndex+validIndexCount)) + continue; + if (newIndices[j] < 0) { + // Throw an exception, since index is negative + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("IndexedGeometryArray27")); + + } + if (indices[index+j] == maxIndex) { + if (newIndices[j] >= newMax) { + newMax = newIndices[j]; + computeNewMax = false; + maxReset = true; + } + // Go thru the entire list and look for the max + // If in the new list there is no value that is >= + // to the old maximum + else if (!maxReset){ + computeNewMax = true; + } + } + else if (newIndices[j] >= newMax) { + newMax = newIndices[j]; + computeNewMax = false; + maxReset = true; + } + } + if (computeNewMax) { + for (i = 0; i < indices.length; i++) { + if (indices[i] > newMax) { + newMax = indices[i]; + } + } + } + return newMax; + } + + + /** + * Sets the coordinate indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param coordinateIndices an array of coordinate indices + */ + final void setCoordinateIndices(int index, int coordinateIndices[]) { + int newMax; + int i, j, num = coordinateIndices.length; + newMax = doIndicesCheck(index, maxCoordIndex, indexCoord, coordinateIndices); + if (newMax > maxCoordIndex) { + doErrorCheck(newMax); + } + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0) { + if ((vertexFormat & GeometryArray.COLOR) != 0) { + maxColorIndex = newMax; + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (i = 0; i < texCoordSetCount; i++) { + maxTexCoordIndices[i] = newMax; + } + } + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + maxNormalIndex = newMax; + } + } + + geomLock.getLock(); + dirtyFlag |= INDEX_CHANGED; + maxCoordIndex = newMax; + for (i=0, j = index; i < num;i++, j++) { + this.indexCoord[j] = coordinateIndices[i]; + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(true); + } + } + + /** + * Sets the color index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @param colorIndex the new color index + */ + final void setColorIndex(int index, int colorIndex) { + int newMax = maxColorIndex; + + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + newMax = doIndexCheck(index, maxColorIndex, indexColor, colorIndex); + if (newMax > maxColorIndex) { + doColorCheck(newMax); + } + geomLock.getLock(); + // No need to set INDEX_CHANGED since IndexBuffer + // is used only when USE_COORD_INDEX_ONLY specified. + // In this case only coordinate index array is + // considered. + this.indexColor[index] = colorIndex; + maxColorIndex = newMax; + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + else { + maxColorIndex = maxCoordIndex; + this.indexColor[index] = colorIndex; + } + + } + + /** + * Sets the color indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param colorIndices an array of color indices + */ + final void setColorIndices(int index, int colorIndices[]) { + int i, j, num = colorIndices.length; + int newMax; + + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + newMax = doIndicesCheck(index, maxColorIndex, indexColor, colorIndices); + if (newMax > maxColorIndex) { + doColorCheck(newMax); + } + geomLock.getLock(); + maxColorIndex = newMax; + for (i=0, j = index; i < num;i++, j++) { + this.indexColor[j] = colorIndices[i]; + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + else { + maxColorIndex = maxCoordIndex; + for (i=0, j = index; i < num;i++, j++) { + this.indexColor[j] = colorIndices[i]; + } + } + + } + + /** + * Sets the normal index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @param normalIndex the new normal index + */ + final void setNormalIndex(int index, int normalIndex) { + int newMax; + + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + newMax = doIndexCheck(index, maxNormalIndex, indexNormal, normalIndex); + if (newMax > maxNormalIndex) { + doNormalCheck(newMax); + } + geomLock.getLock(); + maxNormalIndex = newMax; + this.indexNormal[index] = normalIndex; + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + else { + maxNormalIndex = maxCoordIndex; + this.indexNormal[index] = normalIndex; + } + + } + + /** + * Sets the normal indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param normalIndices an array of normal indices + */ + final void setNormalIndices(int index, int normalIndices[]) { + int i, j, num = normalIndices.length; + int newMax; + + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + newMax = doIndicesCheck(index, maxNormalIndex, indexNormal, normalIndices); + if (newMax > maxNormalIndex) { + doNormalCheck(newMax); + } + geomLock.getLock(); + for (i=0, j = index; i < num;i++, j++) { + this.indexNormal[j] = normalIndices[i]; + } + maxNormalIndex = newMax; + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + else { + maxNormalIndex = maxCoordIndex; + for (i=0, j = index; i < num;i++, j++) { + this.indexNormal[j] = normalIndices[i]; + } + } + + } + + /** + * Sets the texture coordinate index associated with the vertex at + * the specified index for this object. + * @param texCoordSet the texture coordinate set + * @param index the vertex index + * @param texCoordIndex the new texture coordinate index + */ + final void setTextureCoordinateIndex(int texCoordSet, int index, int texCoordIndex) { + int newMax; + int [] indices = (int[])this.indexTexCoord[texCoordSet]; + + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + newMax = doIndexCheck(index, maxTexCoordIndices[texCoordSet],indices, texCoordIndex); + if (newMax > maxTexCoordIndices[texCoordSet]) { + doTexCoordCheck(newMax, texCoordSet); + } + geomLock.getLock(); + maxTexCoordIndices[texCoordSet] = newMax; + indices[index] = texCoordIndex; + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + } + else { + maxTexCoordIndices[texCoordSet] = maxCoordIndex; + indices[index] = texCoordIndex; + } + + + } + + /** + * Sets the texture coordinate indices associated with the vertices + * starting at the specified index for this object. + * @param texCoordSet the texture coordinate set + * @param index the vertex index + * @param texCoordIndices an array of texture coordinate indices + */ + final void setTextureCoordinateIndices(int texCoordSet, int index, int texCoordIndices[]) { + int i, j, num = texCoordIndices.length; + int [] indices = (int[])this.indexTexCoord[texCoordSet]; + + int newMax; + + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + newMax = doIndicesCheck(index, maxTexCoordIndices[texCoordSet], indices, texCoordIndices); + if (newMax > maxTexCoordIndices[texCoordSet]) { + doTexCoordCheck(newMax, texCoordSet); + } + geomLock.getLock(); + maxTexCoordIndices[texCoordSet] = newMax; + for (i=0, j = index; i < num;i++, j++) { + indices[j] = texCoordIndices[i]; + } + geomLock.unLock(); + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(false); + } + + } + else { + maxTexCoordIndices[texCoordSet] = maxCoordIndex; + for (i=0, j = index; i < num;i++, j++) { + indices[j] = texCoordIndices[i]; + } + } + + } + + /** + * Retrieves the coordinate index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @return the coordinate index + */ + final int getCoordinateIndex(int index) { + return this.indexCoord[index]; + } + + /** + * Retrieves the coordinate indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param coordinateIndices array that will receive the coordinate indices + */ + final void getCoordinateIndices(int index, int coordinateIndices[]) { + int i, j, num = coordinateIndices.length; + + for (i=0, j = index;i < num;i++, j++) + { + coordinateIndices[i] = this.indexCoord[j]; + } + } + + /** + * Retrieves the color index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @return the color index + */ + final int getColorIndex(int index) { + return this.indexColor[index]; + } + + /** + * Retrieves the color indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param colorIndices array that will receive the color indices + */ + final void getColorIndices(int index, int colorIndices[]) { + int i, j, num = colorIndices.length; + + for (i=0, j = index;i < num;i++, j++) + { + colorIndices[i] = this.indexColor[j]; + } + } + + /** + * Retrieves the normal index associated with the vertex at + * the specified index for this object. + * @param index the vertex index + * @return the normal index + */ + final int getNormalIndex(int index) { + return this.indexNormal[index]; + } + + /** + * Retrieves the normal indices associated with the vertices starting at + * the specified index for this object. + * @param index the vertex index + * @param normalIndices array that will receive the normal indices + */ + final void getNormalIndices(int index, int normalIndices[]) { + int i, j, num = normalIndices.length; + + for (i=0, j = index;i < num;i++, j++) + { + normalIndices[i] = this.indexNormal[j]; + } + } + + /** + * Retrieves the texture coordinate index associated with the vertex at + * the specified index for this object. + * @param texCoordSet the texture coordinate set + * @param index the vertex index + * @return the texture coordinate index + */ + final int getTextureCoordinateIndex(int texCoordSet, int index) { + return ((int[])this.indexTexCoord[texCoordSet])[index]; + } + + /** + * Retrieves the texture coordinate indices associated with the vertices + * starting at the specified index for this object. + * @param texCoordSet the texture coordinate set + * @param index the vertex index + * @param texCoordIndices array that will receive the texture coordinate indices + */ + final void getTextureCoordinateIndices(int texCoordSet, int index, int texCoordIndices[]) { + int i, j, num = texCoordIndices.length; + int [] indices = (int[])this.indexTexCoord[texCoordSet]; + + for (i=0, j = index;i < num;i++, j++) + { + texCoordIndices[i] = indices[j]; + } + } + + // used for GeometryArrays + native void executeIndexedGeometry(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, + boolean useAlpha, + boolean multiScreen, + boolean ignoreVertexColors, + int initialIndexIndex, + int indexCount, + int vertexCount, int vformat, + int texCoordSetCount, int texCoordSetMap[], + int texCoordSetMapLen, + int[] texCoordSetOffset, + int numActiveTexUnitState, + int[] texUnitStateMap, + float[] varray, float[] cdata, + int texUnitIndex, int cdirty, + int[] indexCoord); + + // used for interleaved, by reference, nio buffer + native void executeIndexedGeometryBuffer(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, + boolean useAlpha, + boolean multiScreen, + boolean ignoreVertexColors, + int initialIndexIndex, + int indexCount, + int vertexCount, int vformat, + int texCoordSetCount, int texCoordSetMap[], + int texCoordSetMapLen, + int[] texCoordSetOffset, + int numActiveTexUnitState, + int[] texUnitStateMap, + Object varray, float[] cdata, + int texUnitIndex, int cdirty, + int[] indexCoord); + + + + native void executeIndexedGeometryVA(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, + boolean multiScreen, + boolean ignoreVertexColors, + int initialIndexIndex, + int validIndexCount, + int vertexCount, + int vformat, + int vdefined, + float[] vfcoords, double[] vdcoords, + float[] cfdata, byte[] cbdata, + float[] ndata, + int pass, int texcoordmaplength, + int[] texcoordoffset, + int numActiveTexUnitState, int[] texunitstatemap, + int texstride, Object[] texCoords, + int cdirty, + int[] indexCoord); + + // non interleaved, by reference, nio buffer + native void executeIndexedGeometryVABuffer(long ctx, + GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, + boolean multiScreen, + boolean ignoreVertexColors, + int initialIndexIndex, + int validIndexCount, + int vertexCount, + int vformat, + int vdefined, + Object vcoords, + Object cdataBuffer, + float[] cfdata, byte[] cbdata, + Object normal, + int pass, int texcoordmaplength, + int[] texcoordoffset, + int numActiveTexUnitState, int[] texunitstatemap, + int texstride, Object[] texCoords, + int cdirty, + int[] indexCoord); + + // used for IndexedGeometry + native void buildIndexedGeometry(long ctx, GeometryArrayRetained geo, int geo_type, + boolean isNonUniformScale, boolean updateAlpha, + float alpha, + boolean ignoreVertexColors, + int initialIndexIndex, + int validIndexCount, + int vertexCount, + int vformat, + int texCoordSetCount, int texCoordSetMap[], + int texCoordSetMapLen, + int[] texCoordSetMapOffset, + double[] xform, double[] nxform, + float[] varray, int[] indexCoord); + + + void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha, + boolean multiScreen, int screen, + boolean ignoreVertexColors, int pass) { + int cdirty; + boolean useAlpha = false; + Object[] retVal; + if (mirrorGeometry != null) { + mirrorGeometry.execute(cv, ra, isNonUniformScale, updateAlpha, alpha, + multiScreen, screen, + ignoreVertexColors, pass); + return; + } + //By reference with java array + if ((vertexFormat & GeometryArray.USE_NIO_BUFFER) == 0) { + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + float[] vdata; + // System.out.println("by-copy"); + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + // update the alpha values + retVal = updateAlphaInVertexData(cv, screen, alpha); + useAlpha = (retVal[0] == Boolean.TRUE); + vdata = (float[])retVal[1]; + + // D3D only + if (alpha != lastScreenAlpha) { + // handle multiple screen case + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + vdata = vertexData; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + // geomLock is get in MasterControl when + // RenderBin render the geometry. So it is safe + // just to set the dirty flag here + dirtyFlag = 0; + } + + executeIndexedGeometry(cv.ctx, this, geoType, isNonUniformScale, + useAlpha, + multiScreen, + ignoreVertexColors, + initialIndexIndex, + validIndexCount, + // Vertex Count is maxCoordIndex + 1 + maxCoordIndex + 1, + ((vertexFormat & GeometryArray.COLOR) != 0)?(vertexFormat|GeometryArray.COLOR_4):vertexFormat, + texCoordSetCount, texCoordSetMap, + (texCoordSetMap == null) ? 0 : texCoordSetMap.length, + texCoordSetMapOffset, + cv.numActiveTexUnit, cv.texUnitStateMap, + vdata, null, + pass, cdirty, indexCoord); + + + } // end of non by reference + else if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + if(interLeavedVertexData == null) + return; + + float[] cdata = null; + + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + // update the alpha values + retVal = updateAlphaInInterLeavedData(cv, screen, alpha); + useAlpha = (retVal[0] == Boolean.TRUE); + cdata = (float[])retVal[1]; + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + dirtyFlag = 0; + } + + executeIndexedGeometry(cv.ctx, this, geoType, isNonUniformScale, + useAlpha, + multiScreen, + ignoreVertexColors, + initialIndexIndex, + validIndexCount, + maxCoordIndex + 1, + vertexFormat, + texCoordSetCount, texCoordSetMap, + (texCoordSetMap == null) ? 0 : texCoordSetMap.length, + texCoordSetMapOffset, + cv.numActiveTexUnit, cv.texUnitStateMap, + interLeavedVertexData, cdata, + pass, cdirty, indexCoord); + } //end of interleaved + else { + // Check if a vertexformat is set, but the array is null + // if yes, don't draw anything + if ((vertexType == 0) || + ((vertexType & VERTEX_DEFINED) == 0) || + (((vertexFormat & GeometryArray.COLOR) != 0) && + (vertexType & COLOR_DEFINED) == 0) || + (((vertexFormat & GeometryArray.NORMALS) != 0) && + (vertexType & NORMAL_DEFINED) == 0) || + (((vertexFormat& GeometryArray.TEXTURE_COORDINATE) != 0) && + (vertexType & TEXCOORD_DEFINED) == 0)) { + return; + } else { + byte[] cbdata = null; + float[] cfdata = null; + + if ((vertexType & (CF | C3F | C4F )) != 0) { + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + cfdata = updateAlphaInFloatRefColors(cv, + screen, alpha); + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + cfdata = mirrorFloatRefColors[0]; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + + } + dirtyFlag = 0; + } + } else if ((vertexType & (CUB| C3UB | C4UB)) != 0) { + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + cbdata = updateAlphaInByteRefColors( + cv, screen, alpha); + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + cbdata = mirrorUnsignedByteRefColors[0]; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + dirtyFlag = 0; + } + } else { + cdirty = dirtyFlag; + } + + int vdefined = 0; + if((vertexType & (PF | P3F)) != 0) + vdefined |= COORD_FLOAT; + if((vertexType & (PD | P3D)) != 0) + vdefined |= COORD_DOUBLE; + if((vertexType & (CF | C3F | C4F)) != 0) + vdefined |= COLOR_FLOAT; + if((vertexType & (CUB| C3UB | C4UB)) != 0) + vdefined |= COLOR_BYTE; + if((vertexType & NORMAL_DEFINED) != 0) + vdefined |= NORMAL_FLOAT; + if((vertexType & TEXCOORD_DEFINED) != 0) + vdefined |= TEXCOORD_FLOAT; + + executeIndexedGeometryVA(cv.ctx, this, geoType, isNonUniformScale, + multiScreen, + ignoreVertexColors, + initialIndexIndex, + validIndexCount, + maxCoordIndex + 1, + (vertexFormat | c4fAllocated), + vdefined, + mirrorFloatRefCoords, mirrorDoubleRefCoords, + cfdata, cbdata, + mirrorFloatRefNormals, + pass, + ((texCoordSetMap == null) ? 0:texCoordSetMap.length), + texCoordSetMap, + cv.numActiveTexUnit, + cv.texUnitStateMap, + texCoordStride, + mirrorRefTexCoords, cdirty, indexCoord); + + } + } // end of non interleaved and by reference + }//end of non io buffer + + else { + if ((vertexFormat & GeometryArray.INTERLEAVED) != 0) { + if( interleavedFloatBufferImpl == null) + return; + + float[] cdata = null; + + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + // update the alpha values + retVal = updateAlphaInInterLeavedData(cv, screen, alpha); + useAlpha = (retVal[0] == Boolean.TRUE); + cdata = (float[])retVal[1]; + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + dirtyFlag = 0; + } + + executeIndexedGeometryBuffer(cv.ctx, this, geoType, isNonUniformScale, + useAlpha, + multiScreen, + ignoreVertexColors, + initialIndexIndex, + validIndexCount, + maxCoordIndex + 1, + vertexFormat, + texCoordSetCount, texCoordSetMap, + (texCoordSetMap == null) ? 0 : texCoordSetMap.length, + texCoordSetMapOffset, + cv.numActiveTexUnit, cv.texUnitStateMap, + interleavedFloatBufferImpl.getBufferAsObject(), cdata, + pass, cdirty, indexCoord); + } //end of interleaved + else { + // Check if a vertexformat is set, but the array is null + // if yes, don't draw anything + if ((vertexType == 0) || + ((vertexType & VERTEX_DEFINED) == 0) || + (((vertexFormat & GeometryArray.COLOR) != 0) && + (vertexType & COLOR_DEFINED) == 0) || + (((vertexFormat & GeometryArray.NORMALS) != 0) && + (vertexType & NORMAL_DEFINED) == 0) || + (((vertexFormat& GeometryArray.TEXTURE_COORDINATE) != 0) && + (vertexType & TEXCOORD_DEFINED) == 0)) { + return; + } else { + byte[] cbdata = null; + float[] cfdata = null; + + if ((vertexType & CF ) != 0) { + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + cfdata = updateAlphaInFloatRefColors(cv, + screen, alpha); + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + // TODO: handle transparency case + //cfdata = null; + cfdata = mirrorFloatRefColors[0]; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + + } + dirtyFlag = 0; + } + } else if ((vertexType & CUB ) != 0) { + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + cbdata = updateAlphaInByteRefColors( + cv, screen, alpha); + if (alpha != lastScreenAlpha) { + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + // TODO: handle transparency case + // cbdata = null; + cbdata = mirrorUnsignedByteRefColors[0]; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + dirtyFlag = 0; + } + } else { + cdirty = dirtyFlag; + } + + Object vcoord = null, cdataBuffer=null, normal=null; + + int vdefined = 0; + if((vertexType & PF) != 0) { + vdefined |= COORD_FLOAT; + vcoord = floatBufferRefCoords.getBufferAsObject(); + } else if((vertexType & PD ) != 0) { + vdefined |= COORD_DOUBLE; + vcoord = doubleBufferRefCoords.getBufferAsObject(); + } + if((vertexType & CF ) != 0) { + vdefined |= COLOR_FLOAT; + cdataBuffer = floatBufferRefColors.getBufferAsObject(); + } else if((vertexType & CUB) != 0) { + vdefined |= COLOR_BYTE; + cdataBuffer = byteBufferRefColors.getBufferAsObject(); + } + + if((vertexType & NORMAL_DEFINED) != 0) { + vdefined |= NORMAL_FLOAT; + normal = floatBufferRefNormals.getBufferAsObject(); + } + + if((vertexType & TEXCOORD_DEFINED) != 0) + vdefined |= TEXCOORD_FLOAT; + + executeIndexedGeometryVABuffer(cv.ctx, this, geoType, isNonUniformScale, + multiScreen, + ignoreVertexColors, + initialIndexIndex, + validIndexCount, + maxCoordIndex + 1, + (vertexFormat | c4fAllocated), + vdefined, + vcoord, + cdataBuffer, + cfdata, cbdata, + normal, + pass, + ((texCoordSetMap == null) ? 0:texCoordSetMap.length), + texCoordSetMap, + cv.numActiveTexUnit, + cv.texUnitStateMap, + texCoordStride, + refTexCoords, cdirty, indexCoord); + + } + } // end of non interleaved and by reference + } // end of nio buffer + } + + void buildGA(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha, boolean ignoreVertexColors, + Transform3D xform, Transform3D nxform) { + int cdirty; + boolean useAlpha = false; + Object[] retVal; + if (mirrorGeometry != null) { + ((GeometryArrayRetained)mirrorGeometry).buildGA(cv, ra, isNonUniformScale, updateAlpha, alpha, + ignoreVertexColors, xform, nxform); + } + else { + + if ((vertexFormat & GeometryArray.BY_REFERENCE) == 0) { + float[] vdata; + // System.out.println("by-copy"); + synchronized (this) { + cdirty = dirtyFlag; + if (updateAlpha && !ignoreVertexColors) { + // update the alpha values + retVal = updateAlphaInVertexData(cv, cv.screen.screen, alpha); + useAlpha = (retVal[0] == Boolean.TRUE); + vdata = (float[])retVal[1]; + + // D3D only + if (alpha != lastScreenAlpha) { + // handle multiple screen case + lastScreenAlpha = alpha; + cdirty |= COLOR_CHANGED; + } + } else { + vdata = vertexData; + // if transparency switch between on/off + if (lastScreenAlpha != -1) { + lastScreenAlpha = -1; + cdirty |= COLOR_CHANGED; + } + } + // geomLock is get in MasterControl when + // RenderBin render the geometry. So it is safe + // just to set the dirty flag here + dirtyFlag = 0; + } + + buildIndexedGeometry(cv.ctx, this, geoType, isNonUniformScale, + updateAlpha, alpha, ignoreVertexColors, + initialIndexIndex, + validIndexCount, + maxCoordIndex + 1, + vertexFormat, + texCoordSetCount, texCoordSetMap, + (texCoordSetMap == null) ? 0 : texCoordSetMap.length, + texCoordSetMapOffset, + (xform == null) ? null : xform.mat, + (nxform == null) ? null : nxform.mat, + vdata, indexCoord); + } + } + } + + void mergeGeometryArrays(ArrayList list) { + int numMerge = list.size(); + int[] texCoord = null; + indexCount = 0; + for (int i=0; i < numMerge; i++) { + IndexedGeometryArrayRetained geo= (IndexedGeometryArrayRetained)list.get(i); + indexCount += geo.validIndexCount; + } + validIndexCount = indexCount; + initialIndexIndex = 0; + compileIndexCount = new int[numMerge]; + compileIndexOffset = new int[numMerge]; + indexCoord = new int[indexCount]; + if ((vertexFormat & GeometryArray.COLOR) != 0) + indexColor = new int[indexCount]; + if ((vertexFormat & GeometryArray.NORMALS) != 0) + indexNormal = new int[indexCount]; + // We only merge if texCoordSetCount = 1 + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + indexTexCoord = new Object[1]; + indexTexCoord[0] = new int[indexCount]; + texCoord = (int[])indexTexCoord[0]; + } + int curDataOffset = 0; + int curIndexOffset = 0; + for (int i = 0; i < numMerge; i++) { + IndexedGeometryArrayRetained geo= (IndexedGeometryArrayRetained)list.get(i); + int curIndexCount = geo.validIndexCount; + compileIndexCount[i] = curIndexCount; + // Copy all the indices + for (int j = 0; j < curIndexCount; j++) { + indexCoord[j+curIndexOffset] = geo.indexCoord[j+geo.initialIndexIndex]+curDataOffset; + if ((vertexFormat & GeometryArray.COLOR) != 0) + indexColor[j+curIndexOffset] = geo.indexColor[j+geo.initialIndexIndex]+curDataOffset; + if ((vertexFormat & GeometryArray.NORMALS) != 0) + indexNormal[j+curIndexOffset] = geo.indexNormal[j+geo.initialIndexIndex]+curDataOffset; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) + texCoord[j+curIndexOffset] = ((int[])geo.indexTexCoord[0])[j+geo.initialIndexIndex]+curDataOffset; + } + maxCoordIndex = geo.maxCoordIndex +curDataOffset; + compileIndexOffset[i] = curIndexOffset; + curDataOffset += geo.vertexCount; + curIndexOffset += curIndexCount; + } + // reset the max Values + + // call the super to merge the vertex data + super.mergeGeometryArrays(list); + } + + + boolean isWriteStatic() { + + if (!super.isWriteStatic() || + source.getCapability(IndexedGeometryArray.ALLOW_COORDINATE_INDEX_WRITE ) || + source.getCapability(IndexedGeometryArray.ALLOW_COLOR_INDEX_WRITE) || + source.getCapability(IndexedGeometryArray.ALLOW_NORMAL_INDEX_WRITE) || + source.getCapability(IndexedGeometryArray.ALLOW_TEXCOORD_INDEX_WRITE)) + return false; + + return true; + } + + /** + * Gets current number of indices + * @return indexCount + */ + int getIndexCount(int id){ + return compileIndexCount[id]; + } + + int computeMaxIndex(int initial, int count, int[] indices) { + int maxIndex = 0; + for (int i = initial; i < (initial+count); i++) { + if (indices[i] > maxIndex) { + maxIndex = indices[i]; + } + } + return maxIndex; + + } + + void setValidIndexCount(int validIndexCount) { + if (validIndexCount < 0) { + throw new IllegalArgumentException(J3dI18N.getString("IndexedGeometryArray21")); + } + if ((initialIndexIndex + validIndexCount) > indexCount) { + throw new IllegalArgumentException(J3dI18N.getString("IndexedGeometryArray22")); + } + int newCoordMax =0; + int newColorIndex=0; + int newNormalIndex=0; + int newTexCoordIndex[]=null; + + newCoordMax = computeMaxIndex(initialIndexIndex, validIndexCount,indexCoord ); + doErrorCheck(newCoordMax); + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + if ((vertexFormat & GeometryArray.COLOR) != 0) { + newColorIndex = computeMaxIndex(initialIndexIndex, validIndexCount, indexColor); + doColorCheck(newColorIndex); + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + newTexCoordIndex = new int[texCoordSetCount]; + for (int i = 0; i < texCoordSetCount; i++) { + newTexCoordIndex[i] = computeMaxIndex(initialIndexIndex,validIndexCount, + (int[])indexTexCoord[i]); + doTexCoordCheck(newTexCoordIndex[i], i); + } + } + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + newNormalIndex = computeMaxIndex(initialIndexIndex, validIndexCount, indexNormal); + doNormalCheck(newNormalIndex); + } + } + + geomLock.getLock(); + this.validIndexCount = validIndexCount; + maxCoordIndex = newCoordMax; + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + maxColorIndex = newColorIndex; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + maxTexCoordIndices[i] = newTexCoordIndex[i]; + } + } + maxNormalIndex = newNormalIndex; + } + else { + maxColorIndex = maxCoordIndex; + maxNormalIndex = maxCoordIndex; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + maxTexCoordIndices[i] = maxCoordIndex; + } + } + } + geomLock.unLock(); + // bbox is computed for the entries list. + // so, send as false + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(true); + } + + } + + void setInitialIndexIndex(int initialIndexIndex) { + if ((initialIndexIndex + validIndexCount) > indexCount) { + throw new IllegalArgumentException(J3dI18N.getString("IndexedGeometryArray22")); + } + int newCoordMax =0; + int newColorIndex=0; + int newNormalIndex=0; + int newTexCoordIndex[]=null; + + newCoordMax = computeMaxIndex(initialIndexIndex, validIndexCount, indexCoord); + doErrorCheck(newCoordMax); + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + if ((vertexFormat & GeometryArray.COLOR) != 0) { + newColorIndex = computeMaxIndex(initialIndexIndex, validIndexCount, indexColor); + doColorCheck(newColorIndex); + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + newTexCoordIndex = new int[texCoordSetCount]; + for (int i = 0; i < texCoordSetCount; i++) { + newTexCoordIndex[i] = computeMaxIndex(initialIndexIndex,validIndexCount, + (int[])indexTexCoord[i]); + doTexCoordCheck(newTexCoordIndex[i], i); + } + } + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + newNormalIndex = computeMaxIndex(initialIndexIndex, validIndexCount, indexNormal); + doNormalCheck(newNormalIndex); + } + } + + geomLock.getLock(); + dirtyFlag |= INDEX_CHANGED; + this.initialIndexIndex = initialIndexIndex; + maxCoordIndex = newCoordMax; + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + maxColorIndex = newColorIndex; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + maxTexCoordIndices[i] = newTexCoordIndex[i]; + } + } + maxNormalIndex = newNormalIndex; + } + else { + maxColorIndex = maxCoordIndex; + maxNormalIndex = maxCoordIndex; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (int i = 0; i < texCoordSetCount; i++) { + maxTexCoordIndices[i] = maxCoordIndex; + } + } + } + geomLock.unLock(); + // bbox is computed for the entries list. + // so, send as false + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(true); + } + } + + int getInitialIndexIndex() { + return initialIndexIndex; + } + + int getValidIndexCount() { + return validIndexCount; + } + void handleFrequencyChange(int bit) { + if ((bit == IndexedGeometryArray.ALLOW_COORDINATE_INDEX_WRITE) || + (((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) && + ((vertexFormat & GeometryArray.COLOR) != 0) && + bit == IndexedGeometryArray.ALLOW_COLOR_INDEX_WRITE) || + (((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) && + ((vertexFormat & GeometryArray.NORMALS) != 0) && + bit == IndexedGeometryArray.ALLOW_NORMAL_INDEX_WRITE) || + (((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0)&& + ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0)&& + bit == IndexedGeometryArray.ALLOW_TEXCOORD_INDEX_WRITE)) { + setFrequencyChangeMask(bit, 0x1); + } + else { + super.handleFrequencyChange(bit); + } + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedGeometryStripArray.java b/src/classes/share/javax/media/j3d/IndexedGeometryStripArray.java new file mode 100644 index 0000000..a3b6e0f --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedGeometryStripArray.java @@ -0,0 +1,210 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The IndexedGeometryStripArray object is an abstract class that is extended for + * a set of IndexedGeometryArray strip primitives. These include LINE_STRIP, + * TRIANGLE_STRIP, and TRIANGLE_FAN. + */ + +public abstract class IndexedGeometryStripArray extends IndexedGeometryArray { + + // non-public, no parameter constructor + IndexedGeometryStripArray() {} + + /** + * Constructs an empty IndexedGeometryStripArray object with the specified + * number of vertices, vertex format, number of indices, and + * array of per-strip index counts. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * The length of this array is the number of separate strips. + * The sum of the elements in this array defines the total number + * of valid indexed vertices that are rendered (validIndexCount). + * + * @exception IllegalArgumentException if + * <code>validIndexCount > indexCount</code> + */ + public IndexedGeometryStripArray(int vertexCount, + int vertexFormat, + int indexCount, + int[] stripIndexCounts) { + + super(vertexCount, vertexFormat, indexCount); + ((IndexedGeometryStripArrayRetained)this.retained). + setStripIndexCounts(stripIndexCounts); + } + + /** + * Constructs an empty IndexedGeometryStripArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, number of indices, and + * array of per-strip index counts. + * + * @param vertexCount the number of vertex elements in this object<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered.<p> + * + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * The length of this array is the number of separate strips. + * The sum of the elements in this array defines the total number + * of valid indexed vertices that are rendered (validIndexCount). + * + * @exception IllegalArgumentException if + * <code>validIndexCount > indexCount</code> + * + * @since Java 3D 1.2 + */ + public IndexedGeometryStripArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount, + int[] stripIndexCounts) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + indexCount); + ((IndexedGeometryStripArrayRetained)this.retained). + setStripIndexCounts(stripIndexCounts); + } + + /** + * Get number of strips in the GeometryStripArray + * @return numStrips number of strips + */ + public int getNumStrips(){ + if (isLiveOrCompiled()) + if(!this.getCapability(GeometryArray.ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryStripArray0")); + + return ((IndexedGeometryStripArrayRetained)this.retained).getNumStrips(); + } + + /** + * Sets the array of strip index counts. The length of this + * array is the number of separate strips. The elements in this + * array specify the number of indices for each separate strip. + * The sum of the elements in this array defines the total number + * of valid indexed vertices that are rendered (validIndexCount). + * + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * + * @exception IllegalArgumentException if + * <code>initialIndexIndex + validIndexCount > indexCount</code> + * + * @since Java 3D 1.3 + */ + public void setStripIndexCounts(int[] stripIndexCounts) { + if (isLiveOrCompiled()) + if(!this.getCapability(GeometryArray.ALLOW_COUNT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryStripArray2")); + + ((IndexedGeometryStripArrayRetained)this.retained).setStripIndexCounts(stripIndexCounts); + + } + + /** + * Gets a list of indexCounts for each strip. The list is + * copied into the specified array. The array must be + * large enough to hold all of the ints. + * @param stripIndexCounts an array that will receive indexCounts + */ + public void getStripIndexCounts(int[] stripIndexCounts) { + if (isLiveOrCompiled()) + if(!this.getCapability(GeometryArray.ALLOW_COUNT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("IndexedGeometryStripArray1")); + + ((IndexedGeometryStripArrayRetained)this.retained). + getStripIndexCounts(stripIndexCounts); + } + + /** + * This method is not supported for indexed geometry strip arrays. + * The sum of the elements in the strip index counts array defines + * the valid index count. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setValidIndexCount(int validIndexCount) { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/classes/share/javax/media/j3d/IndexedGeometryStripArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedGeometryStripArrayRetained.java new file mode 100644 index 0000000..5246cf8 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedGeometryStripArrayRetained.java @@ -0,0 +1,207 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.Vector; +import java.util.ArrayList; + +/** + * The IndexedGeometryStripArray object is an abstract class that is extended for + * a set of IndexedGeometryArray strip primitives. These include LINE_STRIP, + * TRIANGLE_STRIP, and TRIANGLE_FAN. + */ + +abstract class IndexedGeometryStripArrayRetained extends IndexedGeometryArrayRetained { + + // Array of per-strip vertex counts + int stripIndexCounts[]; + + // Following variables are only used in compile mode + int[] compileStripICOffset; + int[] compileIndexLength; + + /** + * Set stripIndexCount data into local array + */ + void setStripIndexCounts(int stripIndexCounts[]){ + + int i, num = stripIndexCounts.length, total = 0; + + + + + + for (i=0; i < num; i++) { + total += stripIndexCounts[i]; + if (this instanceof IndexedLineStripArrayRetained) { + if (stripIndexCounts[i] < 2) { + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineStripArrayRetained1")); + } + } + else if (this instanceof IndexedTriangleStripArrayRetained) { + if (stripIndexCounts[i] < 3) { + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleStripArrayRetained1")); + } + } + else if (this instanceof IndexedTriangleFanArrayRetained) { + if (stripIndexCounts[i] < 3) { + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleFanArrayRetained1")); + } + } + } + + // Sum of all stripIndexCounts MUST be same as indexCount + if ((initialIndexIndex + total) > indexCount) + throw new IllegalArgumentException(J3dI18N.getString("IndexedGeometryStripArrayRetained0")); + int newCoordMax =0; + int newColorIndex=0; + int newNormalIndex=0; + int newTexCoordIndex[]=null; + + newCoordMax = computeMaxIndex(initialIndexIndex, total, indexCoord); + doErrorCheck(newCoordMax); + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + if ((vertexFormat & GeometryArray.COLOR) != 0) { + newColorIndex = computeMaxIndex(initialIndexIndex, total, indexColor); + doColorCheck(newColorIndex); + } + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + newTexCoordIndex = new int[texCoordSetCount]; + for (i = 0; i < texCoordSetCount; i++) { + newTexCoordIndex[i] = computeMaxIndex(initialIndexIndex,total, + (int[])indexTexCoord[i]); + doTexCoordCheck(newTexCoordIndex[i], i); + } + } + if ((vertexFormat & GeometryArray.NORMALS) != 0) { + newNormalIndex = computeMaxIndex(initialIndexIndex, total, indexNormal); + doNormalCheck(newNormalIndex); + } + } + + geomLock.getLock(); + validIndexCount = total; + this.stripIndexCounts = new int[num]; + for (i=0;i < num;i++) + { + this.stripIndexCounts[i] = stripIndexCounts[i]; + } + maxCoordIndex = newCoordMax; + if ((vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) == 0) { + maxColorIndex = newColorIndex; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (i = 0; i < texCoordSetCount; i++) { + maxTexCoordIndices[i] = newTexCoordIndex[i]; + } + } + maxNormalIndex = newNormalIndex; + } + else { + maxColorIndex = maxCoordIndex; + maxNormalIndex = maxCoordIndex; + if ((vertexFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (i = 0; i < texCoordSetCount; i++) { + maxTexCoordIndices[i] = maxCoordIndex; + } + } + } + geomLock.unLock(); + // bbox is computed for the entries list. + // so, send as false + if (!inUpdater && source != null && source.isLive()) { + sendDataChangedMessage(true); + } + + } + + Object cloneNonIndexedGeometry() { + GeometryStripArrayRetained obj = null; + int i; + switch (this.geoType) { + case GEO_TYPE_INDEXED_LINE_STRIP_SET: + obj = new LineStripArrayRetained(); + break; + case GEO_TYPE_INDEXED_TRI_FAN_SET: + obj = new TriangleFanArrayRetained(); + break; + case GEO_TYPE_INDEXED_TRI_STRIP_SET: + obj = new TriangleStripArrayRetained(); + break; + } + obj.createGeometryArrayData(validIndexCount, (vertexFormat & ~(GeometryArray.BY_REFERENCE|GeometryArray.INTERLEAVED|GeometryArray.USE_NIO_BUFFER)), texCoordSetCount, texCoordSetMap); + obj.unIndexify(this); + obj.setStripVertexCounts(stripIndexCounts); + + return (Object)obj; + } + + + /** + * Get number of strips in the GeometryStripArray + * @return numStrips number of strips + */ + int getNumStrips(){ + return stripIndexCounts.length; + } + + /** + * Get a list of vertexCounts for each strip + * @param stripIndexCounts an array that will receive vertexCounts + */ + void getStripIndexCounts(int stripIndexCounts[]){ + for (int i=stripIndexCounts.length-1;i >= 0; i--) { + stripIndexCounts[i] = this.stripIndexCounts[i]; + } + } + + void mergeGeometryArrays(ArrayList list) { + int numMerge = list.size(); + int numCount = 0; + int i, j; + + for (i = 0; i < numMerge; i++) { + IndexedGeometryStripArrayRetained geo = (IndexedGeometryStripArrayRetained) list.get(i); + numCount += geo.stripIndexCounts.length; + } + + stripIndexCounts = new int[numCount]; + compileIndexLength = new int[numCount]; + compileStripICOffset = new int[numMerge]; + int curICOffset = 0; + for (i = 0; i < numMerge; i++) { + IndexedGeometryStripArrayRetained geo = (IndexedGeometryStripArrayRetained) list.get(i); + compileStripICOffset[i] = curICOffset; + compileIndexLength[i] = geo.stripIndexCounts.length; + System.arraycopy(geo.stripIndexCounts, 0, stripIndexCounts, + curICOffset, geo.stripIndexCounts.length); + curICOffset += geo.stripIndexCounts.length; + } + super.mergeGeometryArrays(list); + + } + int getNumStrips(int id){ + return compileIndexLength[id]; + } + + /** + * Get a list of vertexCounts for each strip + * @param stripIndexCounts an array that will receive vertexCounts + */ + void getStripIndexCounts(int id, int stripIndexCounts[]){ + int count = compileIndexLength[id]; + int coffset = compileStripICOffset[id]; + for (int i=0;i < count; i++) { + stripIndexCounts[i] = this.stripIndexCounts[coffset+1]; + } + } + +} diff --git a/src/classes/share/javax/media/j3d/IndexedLineArray.java b/src/classes/share/javax/media/j3d/IndexedLineArray.java new file mode 100644 index 0000000..e9723aa --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedLineArray.java @@ -0,0 +1,170 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The IndexedLineArray object draws the array of vertices as individual + * line segments. Each pair of vertices defines a line to be drawn. + */ + +public class IndexedLineArray extends IndexedGeometryArray { + /** + * Package scoped default constructor. + */ + IndexedLineArray() { + } + + /** + * Constructs an empty IndexedLineArray object with the specified + * number of vertices, vertex format, and number of indices. + * @param vertexCount the number of vertex elements in this object + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 2, or indexCount is <i>not</i> + * a multiple of 2 + */ + public IndexedLineArray(int vertexCount, int vertexFormat, int indexCount) { + super(vertexCount,vertexFormat, indexCount); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineArray0")); + + if (indexCount < 2 || ((indexCount%2) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineArray1")); + } + + /** + * Constructs an empty IndexedLineArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and number of indices. + * + * @param vertexCount the number of vertex elements in this object<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3 or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 2, or indexCount is <i>not</i> + * a multiple of 2 + * + * @since Java 3D 1.2 + */ + public IndexedLineArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + indexCount); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineArray0")); + + if (indexCount < 2 || ((indexCount%2) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineArray1")); + } + + /** + * Creates the retained mode IndexedLineArrayRetained object that this + * IndexedLineArray object will point to. + */ + void createRetained() { + this.retained = new IndexedLineArrayRetained(); + this.retained.setSource(this); + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + IndexedLineArrayRetained rt = (IndexedLineArrayRetained) retained; + int texSetCount = rt.getTexCoordSetCount(); + IndexedLineArray l; + if (texSetCount == 0) { + l = new IndexedLineArray(rt.getVertexCount(), + rt.getVertexFormat(), + rt.getIndexCount()); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + l = new IndexedLineArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + rt.getIndexCount()); + } + l.duplicateNodeComponent(this); + return l; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedLineArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedLineArrayRetained.java new file mode 100644 index 0000000..b613e54 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedLineArrayRetained.java @@ -0,0 +1,335 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The IndexedLineArray object draws the array of vertices as individual + * line segments. Each pair of vertices defines a line to be drawn. + */ + +class IndexedLineArrayRetained extends IndexedGeometryArrayRetained { + + IndexedLineArrayRetained() { + this.geoType = GEO_TYPE_INDEXED_LINE_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[2]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + + if (intersectLineAndRay(pnts[0], pnts[1], pickRay.origin, + pickRay.direction, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + Vector3d dir = + new Vector3d(pickSegment.end.x - pickSegment.start.x, + pickSegment.end.y - pickSegment.start.y, + pickSegment.end.z - pickSegment.start.z); + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectLineAndRay(pnts[0], pnts[1], + pickSegment.start, + dir, sdist, iPnt) && + (sdist[0] <= 1.0)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectBoundingPolytope(pnts, bpolytope, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + + } + + boolean intersect(Point3d[] pnts) { + Point3d[] points = new Point3d[2]; + Vector3d dir; + double dist[] = new double[1]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + points[0] = new Point3d(); + points[1] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle/Quad , common case first + case 4: + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + if (intersectSegment(pnts, points[0], points[1], dist, + null)) { + return true; + } + } + break; + case 2: // Line + dir = new Vector3d(); + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + dir.x = points[1].x - points[0].x; + dir.y = points[1].y - points[0].y; + dir.z = points[1].z - points[0].z; + if (intersectLineAndRay(pnts[0], pnts[1], points[0], + dir, dist, null) && + (dist[0] <= 1.0)) { + return true; + } + } + break; + case 1: // Point + dir = new Vector3d(); + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + dir.x = points[1].x - points[0].x; + dir.y = points[1].y - points[0].y; + dir.z = points[1].z - points[0].z; + if (intersectPntAndRay(pnts[0], points[0], dir, dist) && + (dist[0] <= 1.0)) { + return true; + } + } + break; + } + return false; + } + + + boolean intersect(Transform3D thisToOtherVworld, + GeometryRetained geom) { + + Point3d[] pnts = new Point3d[2]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + thisToOtherVworld.transform(pnts[0]); + thisToOtherVworld.transform(pnts[1]); + if (geom.intersect(pnts)) { + return true; + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + Point3d[] pnts = new Point3d[2]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while(i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while(i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, null, null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while(i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectBoundingPolytope(pnts, bpolytope, null, null)) { + return true; + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + int getClassType() { + return LINE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedLineStripArray.java b/src/classes/share/javax/media/j3d/IndexedLineStripArray.java new file mode 100644 index 0000000..52760a8 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedLineStripArray.java @@ -0,0 +1,197 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The IndexedLineStripArray object draws an array of vertices as a set of + * connected line strips. An array of per-strip index counts specifies + * where the separate strips appear in the indexed vertex array. + * For every strip in the set, each vertex, beginning with + * the second vertex in the array, defines a line segment to be drawn + * from the previous vertex to the current vertex. + */ + +public class IndexedLineStripArray extends IndexedGeometryStripArray { + + /** + * Package scoped default constructor. + */ + IndexedLineStripArray() { + } + + /** + * Constructs an empty IndexedLineStripArray object with the specified + * number of vertices, vertex format, number of indices, and + * array of per-strip index counts. + * @param vertexCount the number of vertex elements in this object + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * The length of this array is the number of separate strips. + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 2, + * or any element in the stripIndexCounts array is less than 2 + */ + public IndexedLineStripArray(int vertexCount, + int vertexFormat, + int indexCount, + int stripIndexCounts[]) { + + super(vertexCount, vertexFormat, indexCount, stripIndexCounts); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineStripArray0")); + + if (indexCount < 2 ) + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineStripArray1")); + } + + /** + * Constructs an empty IndexedLineStripArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, number of indices, and + * array of per-strip index counts. + * + * @param vertexCount the number of vertex elements in this object<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered.<p> + * + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 2, + * or any element in the stripIndexCounts array is less than 2 + * + * @since Java 3D 1.2 + */ + public IndexedLineStripArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount, + int stripIndexCounts[]) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + indexCount, stripIndexCounts); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineStripArray0")); + + if (indexCount < 2 ) + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineStripArray1")); + } + + /** + * Creates the retained mode IndexedLineStripArrayRetained object that this + * IndexedLineStripArray object will point to. + */ + void createRetained() { + this.retained = new IndexedLineStripArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + IndexedLineStripArrayRetained rt = + (IndexedLineStripArrayRetained) retained; + int stripIndexCounts[] = new int[rt.getNumStrips()]; + rt.getStripIndexCounts(stripIndexCounts); + int texSetCount = rt.getTexCoordSetCount(); + IndexedLineStripArray l; + + if (texSetCount == 0) { + l = new IndexedLineStripArray(rt.getVertexCount(), + rt.getVertexFormat(), + rt.getIndexCount(), + stripIndexCounts); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + l = new IndexedLineStripArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + rt.getIndexCount(), + stripIndexCounts); + + } + l.duplicateNodeComponent(this); + return l; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedLineStripArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedLineStripArrayRetained.java new file mode 100644 index 0000000..846c959 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedLineStripArrayRetained.java @@ -0,0 +1,412 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.*; +import java.lang.Math; + +/** + * The IndexedLineStripArray object draws an array of vertices as a set of + * connected line strips. An array of per-strip vertex counts specifies + * where the separate strips appear in the vertex array. + * For every strip in the set, each vertex, beginning with + * the second vertex in the array, defines a line segment to be drawn + * from the previous vertex to the current vertex. + */ + +class IndexedLineStripArrayRetained extends IndexedGeometryStripArrayRetained { + + IndexedLineStripArrayRetained() { + geoType = GEO_TYPE_INDEXED_LINE_STRIP_SET; + } + + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[2]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int scount, j, i = 0; + int count = 0; + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + if (intersectLineAndRay(pnts[0], pnts[1], pickRay.origin, + pickRay.direction, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + Vector3d dir = + new Vector3d(pickSegment.end.x - pickSegment.start.x, + pickSegment.end.y - pickSegment.start.y, + pickSegment.end.z - pickSegment.start.z); + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + if (intersectLineAndRay(pnts[0], pnts[1], + pickSegment.start, + dir, sdist, iPnt) && + (sdist[0] <= 1.0)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + + if (intersectBoundingSphere(pnts, bsphere, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + + if (intersectBoundingPolytope(pnts, bpolytope, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("IndexedLineStripArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + + } + + // intersect pnts[] with every triangle in this object + boolean intersect(Point3d[] pnts) { + int i = 0; + int j, count=0; + int scount; + Point3d[] points = new Point3d[2]; + double dist[] = new double[1]; + Vector3d dir; + + points[0] = new Point3d(); + points[1] = new Point3d(); + + switch (pnts.length) { + case 3: + case 4: // Triangle, Quad + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + scount = stripIndexCounts[i++]; + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], points[1]); + if (intersectSegment(pnts, points[0], points[1], + dist, null)) { + return true; + } + points[0].set(points[1]); + } + } + break; + case 2: // line + dir = new Vector3d(); + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + scount = stripIndexCounts[i++]; + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], points[1]); + dir.x = points[1].x - points[0].x; + dir.y = points[1].y - points[0].y; + dir.z = points[1].z - points[0].z; + if (intersectLineAndRay(pnts[0], pnts[1], + points[0], dir, dist, null) + && (dist[0] <= 1.0)) { + return true; + } + points[0].set(points[1]); + } + } + break; + case 1: // point + dir = new Vector3d(); + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + scount = stripIndexCounts[i++]; + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], points[1]); + dir.x = points[1].x - points[0].x; + dir.y = points[1].y - points[0].y; + dir.z = points[1].z - points[0].z; + if (intersectPntAndRay(pnts[0], points[0], dir, + dist) && + (dist[0] <= 1.0)) { + return true; + } + points[0].set(points[1]); + } + } + break; + } + + return false; + } + + boolean intersect(Transform3D thisToOtherVworld, + GeometryRetained geom) { + int i = 0; + int j, count=0; + Point3d[] pnts = new Point3d[2]; + int scount; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + thisToOtherVworld.transform(pnts[0]); + scount = stripIndexCounts[i++]; + + for (j = 1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + thisToOtherVworld.transform(pnts[1]); + if (geom.intersect( pnts)) { + return true; + } + pnts[0].set(pnts[1]); + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + int i = 0; + int j, count=0; + Point3d[] pnts = new Point3d[2]; + int scount; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + } + + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + scount = stripIndexCounts[i++]; + for (j=1; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[1]); + if (intersectBoundingPolytope(pnts, bpolytope, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + int getClassType() { + return LINE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedObject.java b/src/classes/share/javax/media/j3d/IndexedObject.java new file mode 100644 index 0000000..712d5d5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedObject.java @@ -0,0 +1,54 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class used for IndexedUnorderedList + */ + +abstract class IndexedObject extends Object { + + /** + * A 2D array listIdx[3][len] is used. + * The entry listIdx[0][], listIdx[0][1] is used for each VirtualUniverse. + * The entry listIdx[2][0] is used for index to which one to use. + * + * This is used to handle the case the Node Object move from + * one VirtualUniverse A to another VirtualUniverse B. + * It is possible that another Structures in B may get the add + * message first before the Structures in A get the remove + * message to clear the entry. This cause MT problem. So a + * 2D array is used to resolve it. + */ + int[][] listIdx; + + abstract VirtualUniverse getVirtualUniverse(); + + synchronized int getIdxUsed(VirtualUniverse u) { + int idx = listIdx[2][0]; + if (u == getVirtualUniverse()) { + return idx; + } + return (idx == 0 ? 1 : 0); + } + + void incIdxUsed() { + if (listIdx[2][0] == 0) { + listIdx[2][0] = 1; + } else { + listIdx[2][0] = 0; + } + } +} + + diff --git a/src/classes/share/javax/media/j3d/IndexedPointArray.java b/src/classes/share/javax/media/j3d/IndexedPointArray.java new file mode 100644 index 0000000..2703595 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedPointArray.java @@ -0,0 +1,170 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The IndexedPointArray object draws the array of vertices as + * individual points. + */ + +public class IndexedPointArray extends IndexedGeometryArray { + + /** + * Package scoped default constructor. + */ + IndexedPointArray() { + } + + /** + * Constructs an empty IndexedPointArray object with the specified + * number of vertices, vertex format, and number of indices. + * @param vertexCount the number of vertex elements in this object + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * @exception IllegalArgumentException if vertexCount is less than 1 + * or indexCount is less than 1 + */ + public IndexedPointArray(int vertexCount, int vertexFormat, int indexCount) { + super(vertexCount,vertexFormat, indexCount); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedPointArray0")); + + if (indexCount < 1 ) + throw new IllegalArgumentException(J3dI18N.getString("IndexedPointArray1")); + } + + /** + * Constructs an empty IndexedPointArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and number of indices. + * + * @param vertexCount the number of vertex elements in this object<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3 or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * + * @exception IllegalArgumentException if vertexCount is less than 1 + * or indexCount is less than 1 + * + * @since Java 3D 1.2 + */ + public IndexedPointArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + indexCount); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedPointArray0")); + + if (indexCount < 1 ) + throw new IllegalArgumentException(J3dI18N.getString("IndexedPointArray1")); + } + + /** + * Creates the retained mode IndexedPointArrayRetained object that this + * IndexedPointArray object will point to. + */ + void createRetained() { + this.retained = new IndexedPointArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + IndexedPointArrayRetained rt = (IndexedPointArrayRetained) retained; + int texSetCount = rt.getTexCoordSetCount(); + IndexedPointArray p; + if (texSetCount == 0) { + p = new IndexedPointArray(rt.getVertexCount(), + rt.getVertexFormat(), + rt.getIndexCount()); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + p = new IndexedPointArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + rt.getIndexCount()); + } + p.duplicateNodeComponent(this); + return p; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedPointArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedPointArrayRetained.java new file mode 100644 index 0000000..14b0710 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedPointArrayRetained.java @@ -0,0 +1,247 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The IndexedPointArray object draws the array of vertices as individual points. + */ + +class IndexedPointArrayRetained extends IndexedGeometryArrayRetained { + + IndexedPointArrayRetained() { + this.geoType = GEO_TYPE_INDEXED_POINT_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + Point3d pnt = new Point3d(); + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnt); + if (intersectPntAndRay(pnt, pickRay.origin, + pickRay.direction, sdist)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + Vector3d dir = + new Vector3d(pickSegment.end.x - pickSegment.start.x, + pickSegment.end.y - pickSegment.start.y, + pickSegment.end.z - pickSegment.start.z); + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnt); + if (intersectPntAndRay(pnt, pickSegment.start, + dir, sdist) && + (sdist[0] <= 1.0)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + + } + } + break; + case PickShape.PICKBOUNDINGBOX: + case PickShape.PICKBOUNDINGSPHERE: + case PickShape.PICKBOUNDINGPOLYTOPE: + Bounds bounds = ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnt); + if (bounds.intersect(pnt)) { + if (dist == null) { + return true; + } + sdist[0] = pickShape.distance(pnt); + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + } + } + + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnt); + + if (intersectCylinder(pnt, pickCylinder, sdist)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnt); + + if (intersectCone(pnt, pickCone, sdist)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("IndexedPointArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + } + + boolean intersect(Point3d[] pnts) { + Point3d point = new Point3d(); + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + switch (pnts.length) { + case 3: // Triangle + while (i < validVertexCount) { + getVertexData(indexCoord[i++], point); + if (intersectTriPnt(pnts[0], pnts[1], pnts[2], point)) { + return true; + } + } + break; + case 4: // Quad + while (i < validVertexCount) { + getVertexData(indexCoord[i++], point); + if (intersectTriPnt(pnts[0], pnts[1], pnts[2], point) || + intersectTriPnt(pnts[0], pnts[2], pnts[3], point)) { + return true; + } + } + break; + case 2: // Line + double dist[] = new double[1]; + Vector3d dir = new Vector3d(); + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], point); + dir.x = pnts[1].x - pnts[0].x; + dir.y = pnts[1].y - pnts[0].y; + dir.z = pnts[1].z - pnts[0].z; + if (intersectPntAndRay(point, pnts[0], dir, dist) && + (dist[0] <= 1.0)) { + return true; + } + } + break; + case 1: // Point + while (i < validVertexCount) { + getVertexData(indexCoord[i++], point); + if ((pnts[0].x == point.x) && + (pnts[0].y == point.y) && + (pnts[0].z == point.z)) { + return true; + } + } + break; + } + return false; + } + + boolean intersect(Transform3D thisToOtherVworld, + GeometryRetained geom) { + Point3d[] pnt = new Point3d[1]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnt[0] = new Point3d(); + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnt[0]); + thisToOtherVworld.transform(pnt[0]); + if (geom.intersect(pnt)) { + return true; + } + } + return false; + + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + Point3d pnt = new Point3d(); + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnt); + if (targetBound.intersect(pnt)) { + return true; + } + } + return false; + } + + int getClassType() { + return POINT_TYPE; + } +} + diff --git a/src/classes/share/javax/media/j3d/IndexedQuadArray.java b/src/classes/share/javax/media/j3d/IndexedQuadArray.java new file mode 100644 index 0000000..d4d1610 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedQuadArray.java @@ -0,0 +1,174 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The IndexedQuadArray object draws the array of vertices as individual + * quadrilaterals. Each group + * of four vertices defines a quadrilateral to be drawn. + */ + +public class IndexedQuadArray extends IndexedGeometryArray { + + /** + * Package scoped default constructor. + */ + IndexedQuadArray() { + } + + /** + * Constructs an empty IndexedQuadArray object with the specified + * number of vertices, vertex format, and number of indices. + * @param vertexCount the number of vertex elements in this object + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 4, or indexCount is <i>not</i> + * a multiple of 4 + */ + public IndexedQuadArray(int vertexCount, int vertexFormat, int indexCount) { + super(vertexCount,vertexFormat, indexCount); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedQuadArray0")); + + if (indexCount < 4 || ((indexCount%4) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("IndexedQuadArray1")); + } + + /** + * Constructs an empty IndexedQuadArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and number of indices. + * + * @param vertexCount the number of vertex elements in this object<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 4, or indexCount is <i>not</i> + * a multiple of 4 + * + * @since Java 3D 1.2 + */ + public IndexedQuadArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + indexCount); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedQuadArray0")); + + if (indexCount < 4 || ((indexCount%4) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("IndexedQuadArray1")); + } + + /** + * Creates the retained mode IndexedQuadArrayRetained object that this + * IndexedQuadArray object will point to. + */ + void createRetained() { + this.retained = new IndexedQuadArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + IndexedQuadArrayRetained rt = (IndexedQuadArrayRetained) retained; + int texSetCount = rt.getTexCoordSetCount(); + IndexedQuadArray q; + + if (texSetCount == 0) { + q = new IndexedQuadArray(rt.getVertexCount(), + rt.getVertexFormat(), + rt.getIndexCount()); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + q = new IndexedQuadArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + rt.getIndexCount()); + } + q.duplicateNodeComponent(this); + return q; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedQuadArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedQuadArrayRetained.java new file mode 100644 index 0000000..26d885e --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedQuadArrayRetained.java @@ -0,0 +1,387 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The IndexedQuadArray object draws the array of vertices as individual + * quadrilaterals. Each group + * of four vertices defines a quadrilateral to be drawn. + */ + +class IndexedQuadArrayRetained extends IndexedGeometryArrayRetained { + + IndexedQuadArrayRetained() { + this.geoType = GEO_TYPE_INDEXED_QUAD_SET; + } + + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[4]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + pnts[3] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + getVertexData(indexCoord[i++], pnts[3]); + if (intersectRay(pnts, pickRay, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + getVertexData(indexCoord[i++], pnts[3]); + if (intersectSegment(pnts, pickSegment.start, + pickSegment.end, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + getVertexData(indexCoord[i++], pnts[3]); + + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + getVertexData(indexCoord[i++], pnts[3]); + + if (intersectBoundingSphere(pnts, bsphere, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + getVertexData(indexCoord[i++], pnts[3]); + + if (intersectBoundingPolytope(pnts, bpolytope, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + getVertexData(indexCoord[i++], pnts[3]); + + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + getVertexData(indexCoord[i++], pnts[3]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("IndexedQuadArrayRetained0")); + default: + throw new RuntimeException("PickShape not supported for intersection "); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + + } + + + // intersect pnts[] with every quad in this object + boolean intersect(Point3d[] pnts) { + Point3d[] points = new Point3d[4]; + double dist[] = new double[1]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + points[3] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + getVertexData(indexCoord[i++], points[3]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[2], points[3], + pnts[0], pnts[1], pnts[2])) { + return true; + } + } + break; + case 4: // Quad + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + getVertexData(indexCoord[i++], points[3]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[2], pnts[3]) || + intersectTriTri(points[0], points[2], points[3], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[2], points[3], + pnts[0], pnts[2], pnts[3])) { + return true; + } + } + break; + case 2: // Line + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + getVertexData(indexCoord[i++], points[3]); + if (intersectSegment(points, pnts[0], pnts[1], dist, + null)) { + return true; + } + } + break; + case 1: // Point + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + getVertexData(indexCoord[i++], points[3]); + if (intersectTriPnt(points[0], points[1], points[2], + pnts[0]) || + intersectTriPnt(points[0], points[2], points[3], + pnts[0])) { + return true; + } + } + break; + } + return false; + } + + + boolean intersect(Transform3D thisToOtherVworld, + GeometryRetained geom) { + + Point3d[] points = new Point3d[4]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + points[3] = new Point3d(); + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + getVertexData(indexCoord[i++], points[3]); + thisToOtherVworld.transform(points[0]); + thisToOtherVworld.transform(points[1]); + thisToOtherVworld.transform(points[2]); + thisToOtherVworld.transform(points[3]); + if (geom.intersect(points)) { + return true; + } + } // for each quad + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + Point3d[] points = new Point3d[4]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + points[3] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + getVertexData(indexCoord[i++], points[3]); + if (intersectBoundingBox(points, box, null, null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + getVertexData(indexCoord[i++], points[3]); + if (intersectBoundingSphere(points, bsphere, null, + null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + while (i < validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + getVertexData(indexCoord[i++], points[3]); + if (intersectBoundingPolytope(points, bpolytope, null, null)) { + return true; + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + + int getClassType() { + return QUAD_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedTriangleArray.java b/src/classes/share/javax/media/j3d/IndexedTriangleArray.java new file mode 100644 index 0000000..ab4c8d5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedTriangleArray.java @@ -0,0 +1,175 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The IndexedTriangleArray object draws the array of vertices as individual + * triangles. Each group + * of three vertices defines a triangle to be drawn. + */ + +public class IndexedTriangleArray extends IndexedGeometryArray { + + /** + * Package scoped default constructor. + */ + IndexedTriangleArray() { + } + + /** + * Constructs an empty IndexedTriangleArray object with the specified + * number of vertices, vertex format, and number of indices. + * @param vertexCount the number of vertex elements in this object + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 3, or indexCount is <i>not</i> + * a multiple of 3 + */ + public IndexedTriangleArray(int vertexCount, int vertexFormat, + int indexCount) { + super(vertexCount,vertexFormat, indexCount); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleArray0")); + + if (indexCount < 3 || ((indexCount%3) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleArray1")); + } + + /** + * Constructs an empty IndexedTriangleArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and number of indices. + * + * @param vertexCount the number of vertex elements in this object<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 3, or indexCount is <i>not</i> + * a multiple of 3 + * + * @since Java 3D 1.2 + */ + public IndexedTriangleArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + indexCount); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleArray0")); + + if (indexCount < 3 || ((indexCount%3) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleArray1")); + } + + /** + * Creates the retained mode IndexedTriangleArrayRetained object that this + * IndexedTriangleArray object will point to. + */ + void createRetained() { + this.retained = new IndexedTriangleArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + IndexedTriangleArrayRetained rt = (IndexedTriangleArrayRetained) retained; + int texSetCount = rt.getTexCoordSetCount(); + IndexedTriangleArray t; + + if (texSetCount == 0) { + t = new IndexedTriangleArray(rt.getVertexCount(), + rt.getVertexFormat(), + rt.getIndexCount()); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + t = new IndexedTriangleArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + rt.getIndexCount()); + } + t.duplicateNodeComponent(this); + return t; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedTriangleArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedTriangleArrayRetained.java new file mode 100644 index 0000000..cd5ebe5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedTriangleArrayRetained.java @@ -0,0 +1,350 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The IndexedTriangleArray object draws the array of vertices as individual + * triangles. Each group + * of three vertices defines a triangle to be drawn. + */ + +class IndexedTriangleArrayRetained extends IndexedGeometryArrayRetained { + + IndexedTriangleArrayRetained() { + this.geoType = GEO_TYPE_INDEXED_TRI_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[3]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectRay(pnts, pickRay, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectSegment(pnts, pickSegment.start, + pickSegment.end, sdist, iPnt) + && (sdist[0] <= 1.0)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, + sdist,iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectCylinder(pnts, pickCylinder, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + } + + + // intersect pnts[] with every triangle in this object + boolean intersect(Point3d[] pnts) { + Point3d[] points = new Point3d[3]; + double dist[] = new double[1]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle + while (i<validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2])) { + return true; + } + } + break; + case 4: // Quad + while (i<validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[2], pnts[3])) { + return true; + } + } + break; + case 2: // Line + while (i<validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + if (intersectSegment(points, pnts[0], pnts[1], dist, + null)) { + return true; + } + } + break; + case 1: // Point + while (i<validVertexCount) { + getVertexData(indexCoord[i++], points[0]); + getVertexData(indexCoord[i++], points[1]); + getVertexData(indexCoord[i++], points[2]); + if (intersectTriPnt(points[0], points[1], points[2], + pnts[0])) { + return true; + } + } + break; + } + return false; + } + + + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) { + Point3d[] pnts = new Point3d[3]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + thisToOtherVworld.transform(pnts[0]); + thisToOtherVworld.transform(pnts[1]); + thisToOtherVworld.transform(pnts[2]); + if (geom.intersect(pnts)) { + return true; + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + Point3d[] pnts = new Point3d[3]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, null, + null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < validVertexCount) { + getVertexData(indexCoord[i++], pnts[0]); + getVertexData(indexCoord[i++], pnts[1]); + getVertexData(indexCoord[i++], pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, + null, null)) { + return true; + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + int getClassType() { + return TRIANGLE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedTriangleFanArray.java b/src/classes/share/javax/media/j3d/IndexedTriangleFanArray.java new file mode 100644 index 0000000..030cac7 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedTriangleFanArray.java @@ -0,0 +1,197 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The IndexedTriangleFanArray object draws an array of vertices as a set of + * connected triangle fans. An array of per-strip + * index counts specifies where the separate strips (fans) appear + * in the indexed vertex array. For every strip in the set, + * each vertex, beginning with the third vertex in the array, + * defines a triangle to be drawn using the current vertex, + * the previous vertex and the first vertex. This can be thought of + * as a collection of convex polygons. + */ + +public class IndexedTriangleFanArray extends IndexedGeometryStripArray { + + // non-public, no parameter constructor + IndexedTriangleFanArray() {} + + /** + * Constructs an empty IndexedTriangleFanArray object with the specified + * number of vertices, vertex format, number of indices, and + * array of per-strip index counts. + * @param vertexCount the number of vertex elements in this object + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * The length of this array is the number of separate strips. + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 3, + * or any element in the stripIndexCounts array is less than 3 + */ + public IndexedTriangleFanArray(int vertexCount, + int vertexFormat, + int indexCount, + int stripIndexCounts[]) { + + super(vertexCount, vertexFormat, indexCount, stripIndexCounts); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleFanArray0")); + + if (indexCount < 3 ) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleFanArray1")); + } + + /** + * Constructs an empty IndexedTriangleFanArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, number of indices, and + * array of per-strip index counts. + * + * @param vertexCount the number of vertex elements in this object<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3 or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered.<p> + * + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 3, + * or any element in the stripIndexCounts array is less than 3 + * + * @since Java 3D 1.2 + */ + public IndexedTriangleFanArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount, + int stripIndexCounts[]) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + indexCount, stripIndexCounts); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleFanArray0")); + + if (indexCount < 3 ) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleFanArray1")); + } + + /** + * Creates the retained mode IndexedTriangleFanArrayRetained object that this + * IndexedTriangleFanArray object will point to. + */ + void createRetained() { + this.retained = new IndexedTriangleFanArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + IndexedTriangleFanArrayRetained rt = + (IndexedTriangleFanArrayRetained) retained; + int stripIndexCounts[] = new int[rt.getNumStrips()]; + rt.getStripIndexCounts(stripIndexCounts); + int texSetCount = rt.getTexCoordSetCount(); + IndexedTriangleFanArray t; + + if (texSetCount == 0) { + t = new IndexedTriangleFanArray(rt.getVertexCount(), + rt.getVertexFormat(), + rt.getIndexCount(), + stripIndexCounts); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + t = new IndexedTriangleFanArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + rt.getIndexCount(), + stripIndexCounts); + } + + t.duplicateNodeComponent(this); + return t; + } + +} diff --git a/src/classes/share/javax/media/j3d/IndexedTriangleFanArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedTriangleFanArrayRetained.java new file mode 100644 index 0000000..484f5b5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedTriangleFanArrayRetained.java @@ -0,0 +1,415 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The IndexedTriangleFanArray object draws an array of vertices as a set of + * connected triangle fans. An array of per-strip + * vertex counts specifies where the separate strips (fans) appear + * in the vertex array. For every strip in the set, + * each vertex, beginning with the third vertex in the array, + * defines a triangle to be drawn using the current vertex, + * the previous vertex and the first vertex. This can be thought of + * as a collection of convex polygons. + */ + +class IndexedTriangleFanArrayRetained extends IndexedGeometryStripArrayRetained { + + IndexedTriangleFanArrayRetained(){ + geoType = GEO_TYPE_INDEXED_TRI_FAN_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[3]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = 0; + int j, scount, count = 0; + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectRay(pnts, pickRay, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(j++, pnts[2]); + if (intersectSegment(pnts, pickSegment.start, + pickSegment.end, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, + sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleFanArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + } + + // intersect pnts[] with every triangle in this object + boolean intersect(Point3d[] pnts) { + int j, end; + Point3d[] points = new Point3d[3]; + double dist[] = new double[1]; + int i = 0, scount, count = 0; + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + getVertexData(indexCoord[count++], points[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2])) { + return true; + } + points[1].set(points[2]); + } + } + break; + case 4: // Quad + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + getVertexData(indexCoord[count++], points[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[2], pnts[3])) { + return true; + } + points[1].set(points[2]); + } + } + break; + case 2: // Line + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + getVertexData(indexCoord[count++], points[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], points[2]); + if (intersectSegment(points, pnts[0], pnts[1], + dist, null)) { + return true; + } + points[1].set(points[2]); + } + } + break; + case 1: // Point + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + getVertexData(indexCoord[count++], points[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], points[2]); + if (intersectTriPnt(points[0], points[1], points[2], + pnts[0])) { + return true; + } + points[1].set(points[2]); + } + } + break; + } + return false; + } + + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) { + int i = 0, j, scount, count = 0; + Point3d[] pnts = new Point3d[3]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + thisToOtherVworld.transform(pnts[0]); + thisToOtherVworld.transform(pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[2]); + thisToOtherVworld.transform(pnts[2]); + if (geom.intersect(pnts)) { + return true; + } + pnts[1].set(pnts[2]); + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + int i = 0; + int j, scount, count = 0; + Point3d[] pnts = new Point3d[3]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[2]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, null, null)) { + return true; + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, null, null)) { + return true; + } + pnts[1].set(pnts[2]); + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + int getClassType() { + return TRIANGLE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedTriangleStripArray.java b/src/classes/share/javax/media/j3d/IndexedTriangleStripArray.java new file mode 100644 index 0000000..5957e99 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedTriangleStripArray.java @@ -0,0 +1,196 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The IndexedTriangleStripArray object draws an array of vertices as a set of + * connected triangle strips. An array of per-strip index counts specifies + * where the separate strips appear in the indexed vertex array. + * For every strip in the set, + * each vertex, beginning with the third vertex in the array, + * defines a triangle to be drawn using the current vertex and + * the two previous vertices. + */ + +public class IndexedTriangleStripArray extends IndexedGeometryStripArray { + + /** + * Package scoped default constructor + */ + IndexedTriangleStripArray() { + } + + /** + * Constructs an empty IndexedTriangleStripArray object with the specified + * number of vertices, vertex format, number of indices, and + * array of per-strip index counts. + * @param vertexCount the number of vertex elements in this object + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered. + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * The length of this array is the number of separate strips. + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 3, + * or any element in the stripIndexCounts array is less than 3 + */ + public IndexedTriangleStripArray(int vertexCount, + int vertexFormat, + int indexCount, + int stripIndexCounts[]) { + + super(vertexCount, vertexFormat, indexCount, stripIndexCounts); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleStripArray0")); + + if (indexCount < 3 ) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleStripArray1")); + } + + /** + * Constructs an empty IndexedTriangleStripArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, number of indices, and + * array of per-strip index counts. + * + * @param vertexCount the number of vertex elements in this object<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code> or + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param indexCount the number of indices in this object. This + * count is the maximum number of vertices that will be rendered.<p> + * + * @param stripIndexCounts array that specifies + * the count of the number of indices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 1, + * or indexCount is less than 3, + * or any element in the stripIndexCounts array is less than 3 + * + * @since Java 3D 1.2 + */ + public IndexedTriangleStripArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int indexCount, + int stripIndexCounts[]) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + indexCount, stripIndexCounts); + + if (vertexCount < 1) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleStripArray0")); + + if (indexCount < 3 ) + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleStripArray1")); + } + + /** + * Creates the retained mode IndexedTriangleStripArrayRetained object that this + * IndexedTriangleStripArray object will point to. + */ + void createRetained() { + this.retained = new IndexedTriangleStripArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + IndexedTriangleStripArrayRetained rt = + (IndexedTriangleStripArrayRetained) retained; + int stripIndexCounts[] = new int[rt.getNumStrips()]; + rt.getStripIndexCounts(stripIndexCounts); + int texSetCount = rt.getTexCoordSetCount(); + IndexedTriangleStripArray l; + if (texSetCount == 0) { + l = new IndexedTriangleStripArray(rt.getVertexCount(), + rt.getVertexFormat(), + rt.getIndexCount(), + stripIndexCounts); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + l = new IndexedTriangleStripArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + rt.getIndexCount(), + stripIndexCounts); + } + l.duplicateNodeComponent(this); + return l; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedTriangleStripArrayRetained.java b/src/classes/share/javax/media/j3d/IndexedTriangleStripArrayRetained.java new file mode 100644 index 0000000..493c5e9 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedTriangleStripArrayRetained.java @@ -0,0 +1,430 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The IndexedTriangleStripArray object draws an array of vertices as a set of + * connected triangle strips. An array of per-strip vertex counts specifies + * where the separate strips appear in the vertex array. + * For every strip in the set, + * each vertex, beginning with the third vertex in the array, + * defines a triangle to be drawn using the current vertex and + * the two previous vertices. + */ + +class IndexedTriangleStripArrayRetained extends IndexedGeometryStripArrayRetained { + + IndexedTriangleStripArrayRetained(){ + geoType = GEO_TYPE_INDEXED_TRI_STRIP_SET; + } + + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[3]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = 0; + int j, scount, count = 0; + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectRay(pnts, pickRay, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(j++, pnts[2]); + if (intersectSegment(pnts, pickSegment.start, + pickSegment.end, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, + sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + + for (j=2; j < scount; j++) { + getVertexData(count++, pnts[2]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("IndexedTriangleStripArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + } + + // intersect pnts[] with every triangle in this object + boolean intersect(Point3d[] pnts) { + int j, end; + Point3d[] points = new Point3d[3]; + double dist[] = new double[1]; + int i = 0, scount, count = 0; + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + getVertexData(indexCoord[count++], points[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2])) { + return true; + } + points[0].set(points[1]); + points[1].set(points[2]); + } + } + break; + case 4: // Quad + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + getVertexData(indexCoord[count++], points[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[2], pnts[3])) { + return true; + } + points[0].set(points[1]); + points[1].set(points[2]); + } + } + break; + case 2: // Line + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + getVertexData(indexCoord[count++], points[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], points[2]); + if (intersectSegment(points, pnts[0], pnts[1], + dist, null)) { + return true; + } + points[0].set(points[1]); + points[1].set(points[2]); + } + } + break; + case 1: // Point + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], points[0]); + getVertexData(indexCoord[count++], points[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], points[2]); + if (intersectTriPnt(points[0], points[1], points[2], + pnts[0])) { + return true; + } + points[0].set(points[1]); + points[1].set(points[2]); + } + } + break; + } + return false; + } + + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) { + int i = 0, j, scount, count = 0; + Point3d[] pnts = new Point3d[3]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + thisToOtherVworld.transform(pnts[0]); + thisToOtherVworld.transform(pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[2]); + thisToOtherVworld.transform(pnts[2]); + if (geom.intersect(pnts)) { + return true; + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + int i = 0; + int j, scount, count = 0; + Point3d[] pnts = new Point3d[3]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[2]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < stripIndexCounts.length) { + getVertexData(indexCoord[count++], pnts[0]); + getVertexData(indexCoord[count++], pnts[1]); + scount = stripIndexCounts[i++]; + for (j=2; j < scount; j++) { + getVertexData(indexCoord[count++], pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + int getClassType() { + return TRIANGLE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/IndexedUnorderSet.java b/src/classes/share/javax/media/j3d/IndexedUnorderSet.java new file mode 100644 index 0000000..01910af --- /dev/null +++ b/src/classes/share/javax/media/j3d/IndexedUnorderSet.java @@ -0,0 +1,598 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A strongly type indexed unorder set. + * All operations remove(IndexedObject, ListType), add(IndexedObject, ListType), + * contains(IndexedObject, ListType) etc. take O(1) time. + * The class is designed to optimize speed. So many reductance + * procedures call and range check as found in ArrayList are + * removed. + * + * <p> + * Use the following code to iterate through an array. + * + * <pre> + * IndexedUnorderSet IUset = + * new IndexedUnorderSet(YourClass.class, listType); + * // add element here + * + * YourClass[] arr = (YourClass []) IUset.toArray(); + * int size = IUset.arraySize(); + * for (int i=0; i < size; i++) { + * YourClass obj = arr[i]; + * .... + * } + * </pre> + * + * <p> + * Note: + * <ul> + * 1) The array return is a copied of internal array.<br> + * 2) Don't use arr.length , use IUset.arraySize();<br> + * 3) IndexedObject contains an array of listIndex, the number of + * array elements depends on the number of different types of + * IndexedUnorderSet that use it.<br> + * 4) No need to do casting for individual element as in ArrayList.<br> + * 5) IndexedUnorderSet is thread safe.<br> + * 6) Object implement this interface MUST initialize the index to -1.<br> + * </ul> + * + * <p> + * Limitation: + * <ul> + * 1) Order of IndexedObject in list is not important<br> + * 2) Can't modify the clone() copy.<br> + * 3) IndexedObject can't be null<br> + * </ul> + */ + +class IndexedUnorderSet implements Cloneable, java.io.Serializable { + + // TODO: set to false when release + final static boolean debug = false; + + /** + * The array buffer into which the elements of the ArrayList are stored. + * The capacity of the ArrayList is the length of this array buffer. + * + * It is non-private to enable compiler do inlining for get(), + * set(), remove() when -O flag turn on. + */ + transient IndexedObject elementData[]; + + /** + * Clone copy of elementData return by toArray(true); + */ + transient Object cloneData[]; + // size of the above clone objec. + transient int cloneSize; + + transient boolean isDirty = true; + + /** + * Component Type of individual array element entry + */ + Class componentType; + + /** + * The size of the ArrayList (the number of elements it contains). + * + * We make it non-private to enable compiler do inlining for + * getSize() when -O flag turn on. + */ + int size; + + int listType; + + // Current VirtualUniverse using this structure + VirtualUniverse univ; + + /** + * Constructs an empty list with the specified initial capacity. + * and the class data Type + * + * @param initialCapacity the initial capacity of the list. + * @param componentType class type of element in the list. + */ + IndexedUnorderSet(int initialCapacity, Class componentType, + int listType, VirtualUniverse univ) { + this.componentType = componentType; + this.elementData = (IndexedObject[])java.lang.reflect.Array.newInstance( + componentType, initialCapacity); + this.listType = listType; + this.univ = univ; + } + + /** + * Constructs an empty list. + * @param componentType class type of element in the list. + */ + IndexedUnorderSet(Class componentType, int listType, + VirtualUniverse univ) { + this(10, componentType, listType, univ); + } + + + /** + * Constructs an empty list with the specified initial capacity. + * + * @param initialCapacity the initial capacity of the list. + */ + IndexedUnorderSet(int initialCapacity, int listType, + VirtualUniverse univ) { + this(initialCapacity, IndexedObject.class, listType, univ); + } + + /** + * Constructs an empty list. + * @param listType default to Object. + */ + IndexedUnorderSet(int listType, VirtualUniverse univ) { + this(10, IndexedObject.class, listType, univ); + } + + /** + * Initialize all indexes to -1 + */ + final static void init(IndexedObject obj, int len) { + obj.listIdx = new int[3][]; + + obj.listIdx[0] = new int[len]; + obj.listIdx[1] = new int[len]; + obj.listIdx[2] = new int[1]; + + for (int i=0; i < len; i++) { + obj.listIdx[0][i] = -1; + obj.listIdx[1][i] = -1; + } + + // Just want to set both RenderMolecule idx + // and BehaviorRetained idx to 0 by default + // It is OK without the following lines + if (obj instanceof SceneGraphObjectRetained) { + // setlive() will change this back to 0 + obj.listIdx[2][0] = 1; + } else { + obj.listIdx[2][0] = 0; + } + } + + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list. + */ + final int size() { + return size; + } + + + /** + * Returns the size of entry use in toArray() number of elements + * in this list. + * + * @return the number of elements in this list. + */ + final int arraySize() { + return cloneSize; + } + + /** + * Tests if this list has no elements. + * + * @return <tt>true</tt> if this list has no elements; + * <tt>false</tt> otherwise. + */ + final boolean isEmpty() { + return size == 0; + } + + /** + * Returns <tt>true</tt> if this list contains the specified element. + * + * @param o element whose presence in this List is to be tested. + */ + synchronized final boolean contains(IndexedObject o) { + return (o.listIdx[o.getIdxUsed(univ)][listType] >= 0); + } + + + /** + * Searches for the last occurence of the given argument, testing + * for equality using the <tt>equals</tt> method. + * + * @param o an object. + * @return the index of the first occurrence of the argument in this + * list; returns <tt>-1</tt> if the object is not found. + * @see Object#equals(Object) + */ + synchronized final int indexOf(IndexedObject o) { + return o.listIdx[o.getIdxUsed(univ)][listType]; + } + + /** + * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The + * elements themselves are not copied.) + * + * @return a clone of this <tt>ArrayList</tt> instance. + */ + synchronized protected final Object clone() { + try { + IndexedUnorderSet v = (IndexedUnorderSet)super.clone(); + v.elementData = (IndexedObject[])java.lang.reflect.Array.newInstance( + componentType, size); + System.arraycopy(elementData, 0, v.elementData, 0, size); + isDirty = true; // can't use the old cloneData reference + return v; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + + /** + * Returns an array containing all of the elements in this list. + * The size of the array may longer than the actual size. Use + * arraySize() to retrieve the size. + * The array return is a copied of internal array. if copy + * is true. + * + * @return an array containing all of the elements in this list + */ + synchronized final Object[] toArray(boolean copy) { + if (copy) { + if (isDirty) { + if ((cloneData == null) || cloneData.length < size) { + cloneData = (Object[])java.lang.reflect.Array.newInstance( + componentType, size); + } + System.arraycopy(elementData, 0, cloneData, 0, size); + cloneSize = size; + isDirty = false; + } + return cloneData; + } else { + cloneSize = size; + return elementData; + } + + } + + /** + * Returns an array containing all of the elements in this list. + * The size of the array may longer than the actual size. Use + * arraySize() to retrieve the size. + * The array return is a copied of internal array. So another + * thread can continue add/delete the current list. However, + * it should be noticed that two call to toArray() may return + * the same copy. + * + * @return an array containing all of the elements in this list + */ + synchronized final Object[] toArray() { + return toArray(true); + } + + + /** + * Returns an array containing elements starting from startElement + * all of the elements in this list. A new array of exact size + * is always allocated. + * + * @param startElement starting element to copy + * + * @return an array containing elements starting from + * startElement, null if element not found. + * + */ + synchronized final Object[] toArray(IndexedObject startElement) { + int idx = indexOf(startElement); + if (idx < 0) { + return (Object[])java.lang.reflect.Array.newInstance(componentType, 0); + } + + int s = size - idx; + Object data[] = (Object[])java.lang.reflect.Array.newInstance(componentType, s); + System.arraycopy(elementData, idx, data, 0, s); + return data; + } + + /** + * Trims the capacity of this <tt>ArrayList</tt> instance to be the + * list's current size. An application can use this operation to minimize + * the storage of an <tt>ArrayList</tt> instance. + */ + synchronized final void trimToSize() { + if (elementData.length > size) { + Object oldData[] = elementData; + elementData = (IndexedObject[])java.lang.reflect.Array.newInstance( + componentType, + size); + System.arraycopy(oldData, 0, elementData, 0, size); + } + } + + + // Positional Access Operations + + /** + * Returns the element at the specified position in this list. + * + * @param index index of element to return. + * @return the element at the specified position in this list. + * @throws IndexOutOfBoundsException if index is out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final Object get(int index) { + return elementData[index]; + } + + /** + * Replaces the element at the specified position in this list with + * the specified element. + * + * @param index index of element to replace. + * @param o element to be stored at the specified position. + * @return the element previously at the specified position. + * @throws IndexOutOfBoundsException if index out of range + * <tt>(index < 0 || index >= size())</tt>. + */ + synchronized final void set(int index, IndexedObject o) { + IndexedObject oldElm = elementData[index]; + if (oldElm != null) { + oldElm.listIdx[oldElm.getIdxUsed(univ)][listType] = -1; + } + elementData[index] = o; + + int univIdx = o.getIdxUsed(univ); + + if (debug) { + if (o.listIdx[univIdx][listType] != -1) { + System.out.println("Illegal use of UnorderIndexedList idx in set " + + o.listIdx[univIdx][listType]); + Thread.dumpStack(); + } + } + + o.listIdx[univIdx][listType] = index; + isDirty = true; + } + + /** + * Appends the specified element to the end of this list. + * It is the user responsible to ensure that the element add is of + * the same type as array componentType. + * + * @param o element to be appended to this list. + */ + synchronized final void add(IndexedObject o) { + + if (elementData.length == size) { + IndexedObject oldData[] = elementData; + elementData = (IndexedObject[])java.lang.reflect.Array.newInstance( + componentType, + (size << 1)); + System.arraycopy(oldData, 0, elementData, 0, size); + + } + + int univIdx = o.getIdxUsed(univ); + + if (debug) { + int idx = o.listIdx[univIdx][listType]; + if (idx >= 0) { + if (elementData[idx] != o) { + System.out.println("Illegal use of UnorderIndexedList idx in add " + idx); + Thread.dumpStack(); + } + } + } + + int idx = size++; + elementData[idx] = o; + o.listIdx[univIdx][listType] = idx; + isDirty = true; + } + + + /** + * Removes the element at the specified position in this list. + * Replace the removed element by the last one. + * + * @param index the index of the element to removed. + * @throws IndexOutOfBoundsException if index out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final void remove(int index) { + IndexedObject elm = elementData[index]; + + int univIdx = elm.getIdxUsed(univ); + + if (debug) { + if (elm.listIdx[univIdx][listType] != index) { + System.out.println("Inconsistent idx in remove, expect " + index + " actual " + elm.listIdx[univIdx][listType]); + Thread.dumpStack(); + } + } + + elm.listIdx[univIdx][listType] = -1; + size--; + if (index != size) { + elm = elementData[size]; + elm.listIdx[univIdx][listType] = index; + elementData[index] = elm; + } + elementData[size] = null; + isDirty = true; + /* + if ((cloneData != null) && (index < cloneData.length)) { + cloneData[index] = null; // for gc + } + */ + } + + + /** + * Removes the element at the last position in this list. + * @return The element remove + * @throws IndexOutOfBoundsException if array is empty + */ + synchronized final Object removeLastElement() { + IndexedObject elm = elementData[--size]; + elementData[size] = null; + elm.listIdx[elm.getIdxUsed(univ)][listType] = -1; + isDirty = true; + /* + if ((cloneData != null) && (size < cloneData.length)) { + cloneData[size] = null; // for gc + } + */ + return elm; + } + + + /** + * Removes the specified element in this list. + * Replace the removed element by the last one. + * + * @param o the element to removed. + * @return true if object remove + * @throws IndexOutOfBoundsException if index out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final boolean remove(IndexedObject o) { + int univIdx = o.getIdxUsed(univ); + int idx = o.listIdx[univIdx][listType]; + + if (idx >= 0) { + if (debug) { + if (o != elementData[idx]) { + System.out.println(this + " Illegal use of UnorderIndexedList in remove expect " + o + " actual " + elementData[idx] + " idx = " + idx); + Thread.dumpStack(); + } + } + // Object in the container + size--; + if (idx != size) { + IndexedObject elm = elementData[size]; + elementData[idx] = elm; + elm.listIdx[elm.getIdxUsed(univ)][listType] = idx; + } + elementData[size] = null; + o.listIdx[univIdx][listType] = -1; + isDirty = true; + return true; + } + return false; + } + + + /** + * Removes all of the elements from this list. The list will + * be empty after this call returns. + */ + synchronized final void clear() { + IndexedObject o; + for (int i = size-1; i >= 0; i--) { + o = elementData[i]; + o.listIdx[o.getIdxUsed(univ)][listType] = -1; + elementData[i] = null; // Let gc do its work + } + size = 0; + isDirty = true; + } + + synchronized final void clearMirror() { + + if (cloneData != null) { + for (int i = cloneData.length-1; i >= 0; i--) { + // don't set index to -1 since the original + // copy is using this. + cloneData[i] = null; // Let gc do its work + } + } + cloneSize = 0; + isDirty = true; + } + + final Class getComponentType() { + return componentType; + } + + /* + synchronized public String toString() { + StringBuffer sb = new StringBuffer("Size = " + size + "\n["); + int len = size-1; + Object obj; + + for (int i=0; i < size; i++) { + obj = elementData[i]; + if (obj != null) { + sb.append(elementData[i].toString()); + } else { + sb.append("NULL"); + } + if (i != len) { + sb.append(", "); + } + } + sb.append("]\n"); + return sb.toString(); + } + */ + + + /** + * Save the state of the <tt>ArrayList</tt> instance to a stream (that + * is, serialize it). + * + * @serialData The length of the array backing the <tt>ArrayList</tt> + * instance is emitted (int), followed by all of its elements + * (each an <tt>Object</tt>) in the proper order. + */ + private synchronized void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException{ + // Write out element count, and any hidden stuff + s.defaultWriteObject(); + + // Write out array length + s.writeInt(elementData.length); + + // Write out all elements in the proper order. + for (int i=0; i<size; i++) + s.writeObject(elementData[i]); + + } + + /** + * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, + * deserialize it). + */ + private synchronized void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // Read in size, and any hidden stuff + s.defaultReadObject(); + + // Read in array length and allocate array + int arrayLength = s.readInt(); + elementData = (IndexedObject[])java.lang.reflect.Array.newInstance( + componentType, arrayLength); + + // Read in all elements in the proper order. + for (int i=0; i<size; i++) + elementData[i] = (IndexedObject) s.readObject(); + } +} diff --git a/src/classes/share/javax/media/j3d/InputDevice.java b/src/classes/share/javax/media/j3d/InputDevice.java new file mode 100644 index 0000000..117a515 --- /dev/null +++ b/src/classes/share/javax/media/j3d/InputDevice.java @@ -0,0 +1,153 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + + +/** + * InputDevice is the interface through which Java 3D and Java 3D + * application programs communicate with a device driver. All input + * devices that Java 3D uses must implement the InputDevice interface and + * be registered with Java 3D via a call to + * PhysicalEnvironment.addInputDevice(InputDevice). An input device + * transfers information to the Java 3D implementation and Java 3D + * applications by writing transform information to sensors that the + * device driver has created and manages. The driver can update its + * sensor information each time the pollAndProcessInput method is + * called. + */ + +public interface InputDevice { + + /** + * Signifies that the driver for a device is a blocking driver and that + * it should be scheduled for regular reads by Java 3D. A blocking driver + * is defined as a driver that can cause the thread accessing the driver + * (the Java 3D implementation thread calling the pollAndProcessInput + * method) to block while the data is being accessed from the driver. + */ + public static final int BLOCKING = 3; + + + /** + * Signifies that the driver for a device is a non-blocking driver and + * that it should be scheduled for regular reads by Java 3D. A + * non-blocking driver is defined as a driver that does not cause the + * calling thread to block while data is being retrieved from the + * driver. If no data is available from the device, pollAndProcessInput + * should return without updating the sensor read value. + */ + public static final int NON_BLOCKING = 4; + + /** + * Signifies that the Java 3D implementation should not schedule + * regular reads on the sensors of this device; the Java 3D + * implementation will only call pollAndProcessInput when one of the + * device's sensors' getRead methods is called. A DEMAND_DRIVEN driver + * must always provide the current value of the sensor on demand whenever + * pollAndProcessInput is called. This means that DEMAND_DRIVEN drivers + * are non-blocking by definition. + */ + public static final int DEMAND_DRIVEN = 5; + + + /** + * This method initializes the device. A device should be initialized + * before it is registered with Java 3D via the + * PhysicalEnvironment.addInputDevice(InputDevice) method call. + * @return return true for succesful initialization, false for failure + */ + public abstract boolean initialize(); + + /** + * This method sets the device's current position and orientation as the + * devices nominal position and orientation (establish its reference + * frame relative to the "Tracker base" reference frame). + */ + public abstract void setNominalPositionAndOrientation(); + + + /** + * This method causes the device's sensor readings to be updated by the + * device driver. For BLOCKING and NON_BLOCKING drivers, this method is + * called regularly and the Java 3D implementation can cache the sensor + * values. For DEMAND_DRIVEN drivers this method is called each time one + * of the Sensor.getRead methods is called, and is not otherwise called. + */ + public abstract void pollAndProcessInput(); + + /** + * This method will not be called by the Java 3D implementation and + * should be implemented as an empty method. + */ + public abstract void processStreamInput(); + + /** + * Code to process the clean up of the device and relinquish associated + * resources. This method should be called after the device has been + * unregistered from Java 3D via the + * PhysicalEnvironment.removeInputDevice(InputDevice) method call. + */ + public abstract void close(); + + /** + * This method retrieves the device's processing mode: one of BLOCKING, + * NON_BLOCKING, or DEMAND_DRIVEN. The Java 3D implementation calls + * this method when PhysicalEnvironment.addInputDevice(InputDevice) is + * called to register the device with Java 3D. If this method returns + * any value other than BLOCKING, NON_BLOCKING, or DEMAND_DRIVEN, + * addInputDevice will throw an IllegalArgumentException. + * @return Returns the devices processing mode, one of BLOCKING, + * NON_BLOCKING, or DEMAND_DRIVEN + */ + public abstract int getProcessingMode(); + + + /** + * This method sets the device's processing mode to one of: BLOCKING, + * NON_BLOCKING, or DEMAND_DRIVEN. Many drivers will be written to run + * in only one mode. Applications using such drivers should not attempt + * to set the processing mode. This method should throw an + * IllegalArgumentException if there is an attempt to set the processing + * mode to anything other than the aforementioned three values. + * + * <p> + * NOTE: this method should <i>not</i> be called after the input + * device has been added to a PhysicalEnvironment. The + * processingMode must remain constant while a device is attached + * to a PhysicalEnvironment. + * + * @param mode One of BLOCKING, NON_BLOCKING, or DEMAND_DRIVEN + */ + public abstract void setProcessingMode(int mode); + + + /** + * This method gets the number of sensors associated with the device. + * @return the device's sensor count. + */ + public int getSensorCount(); + + /** + * Gets the specified Sensor associated with the device. Each InputDevice + * implementation is responsible for creating and managing its own set of + * sensors. The sensor indices begin at zero and end at number of + * sensors minus one. Each sensor should have had + * Sensor.setDevice(InputDevice) set properly before addInputDevice + * is called. + * @param sensorIndex the sensor to retrieve + * @return Returns the specified sensor. + */ + public Sensor getSensor(int sensorIndex); + +} diff --git a/src/classes/share/javax/media/j3d/InputDeviceBlockingThread.java b/src/classes/share/javax/media/j3d/InputDeviceBlockingThread.java new file mode 100644 index 0000000..6bdff8a --- /dev/null +++ b/src/classes/share/javax/media/j3d/InputDeviceBlockingThread.java @@ -0,0 +1,99 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class InputDeviceBlockingThread extends Thread { + + // action flag for runMonitor + private static final int WAIT = 0; + private static final int NOTIFY = 1; + private static final int STOP = 2; + + // blocking device that this thread manages + private InputDevice device; + private boolean running = true; + private boolean waiting = false; + private volatile boolean stop = false; + private static int numInstances = 0; + private int instanceNum = -1; + + InputDeviceBlockingThread(ThreadGroup threadGroup, InputDevice device) { + super(threadGroup, ""); + setName("J3D-InputDeviceBlockingThread-" + getInstanceNum()); + this.device = device; + } + + private synchronized int newInstanceNum() { + return (++numInstances); + } + + private int getInstanceNum() { + if (instanceNum == -1) + instanceNum = newInstanceNum(); + return instanceNum; + } + + + public void run() { + // Since this thread is blocking, this thread should not be + // taking an inordinate amount of CPU time. Note that the + // yield() call should not be necessary (and may be ineffective), + // but we can't call MasterControl.threadYield() because it will + // sleep for at least a millisecond. + while (running) { + while (!stop) { + device.pollAndProcessInput(); + Thread.yield(); + } + runMonitor(WAIT); + } + } + + void sleep() { + stop = true; + } + + void restart() { + stop = false; + runMonitor(NOTIFY); + } + + void finish() { + stop = true; + while (!waiting) { + MasterControl.threadYield(); + } + + runMonitor(STOP); + } + + synchronized void runMonitor(int action) { + + switch (action) { + case WAIT: + try { + waiting = true; + wait(); + } catch (InterruptedException e) {} + waiting = false; + break; + case NOTIFY: + notify(); + break; + case STOP: + running = false; + notify(); + break; + } + } +} diff --git a/src/classes/share/javax/media/j3d/InputDeviceScheduler.java b/src/classes/share/javax/media/j3d/InputDeviceScheduler.java new file mode 100644 index 0000000..f438e37 --- /dev/null +++ b/src/classes/share/javax/media/j3d/InputDeviceScheduler.java @@ -0,0 +1,204 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +/** + * This thread manages all input device scheduling. It monitors and caches + * all device additions and removals. It spawns new threads for blocking + * devices, manages all non-blocking drivers itself, and tags the sensors + * of demand_driven devices. This implementation assume that + * processMode of InputDevice will not change after addInputDevice(). + * + */ + +class InputDeviceScheduler extends J3dThread { + + // list of devices that have been added with the phys env interface + ArrayList nonBlockingDevices = new ArrayList(1); + + // This condition holds blockingDevices.size() == threads.size() + ArrayList blockingDevices = new ArrayList(1); + ArrayList threads = new ArrayList(1); + + // This is used by MasterControl to keep track activeViewRef + PhysicalEnvironment physicalEnv; + + // store all inputDevices + Vector devices = new Vector(1); + + J3dThreadData threadData = new J3dThreadData(); + boolean active = false; + + // The time to sleep before next processAndProcess() is invoked + // for non-blocking input device + static int samplingTime = 5; + + // Some variables used to name threads correctly + private static int numInstances = 0; + private int instanceNum = -1; + + private synchronized int newInstanceNum() { + return (++numInstances); + } + + int getInstanceNum() { + if (instanceNum == -1) + instanceNum = newInstanceNum(); + return instanceNum; + } + + InputDeviceScheduler(ThreadGroup threadGroup, + PhysicalEnvironment physicalEnv) { + super(threadGroup); + setName("J3D-InputDeviceScheduler-" + getInstanceNum()); + threadData.threadType = J3dThread.INPUT_DEVICE_SCHEDULER; + threadData.thread = this; + this.physicalEnv = physicalEnv; + + synchronized (physicalEnv.devices) { + Enumeration elm = physicalEnv.devices.elements(); + while (elm.hasMoreElements()) { + addInputDevice((InputDevice) elm.nextElement()); + } + physicalEnv.inputsched = this; + } + + } + + + void addInputDevice(InputDevice device) { + + switch(device.getProcessingMode()) { + case InputDevice.BLOCKING: + InputDeviceBlockingThread thread = + VirtualUniverse.mc.getInputDeviceBlockingThread(device); + thread.start(); + synchronized (blockingDevices) { + threads.add(thread); + blockingDevices.add(device); + } + break; + case InputDevice.NON_BLOCKING: + synchronized (nonBlockingDevices) { + nonBlockingDevices.add(device); + if (active && (nonBlockingDevices.size() == 1)) { + VirtualUniverse.mc.addInputDeviceScheduler(this); + } + } + break; + default: // InputDevice.DEMAND_DRIVEN: + // tag the sensors + for (int i=device.getSensorCount()-1; i>=0; i--) { + device.getSensor(i).demand_driven = true; + } + break; + } + + } + + + void removeInputDevice(InputDevice device) { + + switch(device.getProcessingMode()) { + case InputDevice.BLOCKING: + // tell the thread to clean up and permanently block + synchronized (blockingDevices) { + int idx = blockingDevices.indexOf(device); + InputDeviceBlockingThread thread = + (InputDeviceBlockingThread) threads.remove(idx); + thread.finish(); + blockingDevices.remove(idx); + } + break; + case InputDevice.NON_BLOCKING: + // remove references that are in this thread + synchronized (nonBlockingDevices) { + nonBlockingDevices.remove(nonBlockingDevices.indexOf(device)); + if (active && (nonBlockingDevices.size() == 0)) { + VirtualUniverse.mc.removeInputDeviceScheduler(this); + } + } + break; + default: // InputDevice.DEMAND_DRIVEN: + // untag the sensors + for (int i=device.getSensorCount()-1; i>=0; i--) { + device.getSensor(i).demand_driven = false; + } + } + } + + // Add this thread to MC (Callback from MC thread) + void activate() { + if (!active) { + active = true; + + synchronized (nonBlockingDevices) { + if (nonBlockingDevices.size() > 0) { + VirtualUniverse.mc.addInputDeviceScheduler(this); + } + } + // run all spawn threads + synchronized (blockingDevices) { + for (int i=threads.size()-1; i >=0; i--) { + ((InputDeviceBlockingThread)threads.get(i)).restart(); + } + } + } + } + + // Remove this thread from MC (Callback from MC thread) + void deactivate() { + if (active) { + synchronized (nonBlockingDevices) { + if (nonBlockingDevices.size() > 0) { + VirtualUniverse.mc.removeInputDeviceScheduler(this); + } + } + + // stop all spawn threads + synchronized (blockingDevices) { + for (int i=threads.size()-1; i >=0; i--) { + ((InputDeviceBlockingThread)threads.get(i)).sleep(); + } + } + active = false; + } + } + + J3dThreadData getThreadData() { + return threadData; + } + + void doWork(long referenceTime) { + synchronized (nonBlockingDevices) { + for (int i = nonBlockingDevices.size()-1; i >=0; i--) { + ((InputDevice)nonBlockingDevices.get(i)).pollAndProcessInput(); + } + } + } + + void shutdown() { + // stop all spawn threads + for (int i=threads.size()-1; i >=0; i--) { + ((InputDeviceBlockingThread)threads.get(i)).finish(); + } + // for gc + threads.clear(); + blockingDevices.clear(); + nonBlockingDevices.clear(); + devices.clear(); + } + +} diff --git a/src/classes/share/javax/media/j3d/IntegerFreeList.java b/src/classes/share/javax/media/j3d/IntegerFreeList.java new file mode 100644 index 0000000..c739038 --- /dev/null +++ b/src/classes/share/javax/media/j3d/IntegerFreeList.java @@ -0,0 +1,40 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class IntegerFreeList extends MemoryFreeList { + + int count = 0; + + // default the initial count to 1 + IntegerFreeList() { + super("java.lang.Integer"); + } + + // sets up an initial count and an initial capacity for the freelist + IntegerFreeList(int initialCount, int initCapacity) { + super("java.lang.Integer", initCapacity); + count = initialCount; + } + + synchronized Object getObject() { + if (size > 0) return super.removeLastElement(); + else return new Integer(++count); + } + + public synchronized void clear() { + super.clear(); + count = 0; + } + +} diff --git a/src/classes/share/javax/media/j3d/Interpolator.java b/src/classes/share/javax/media/j3d/Interpolator.java new file mode 100644 index 0000000..fb97d0b --- /dev/null +++ b/src/classes/share/javax/media/j3d/Interpolator.java @@ -0,0 +1,126 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +import java.util.Vector; + +/** + * Interpolator is an abstract class that extends Behavior to provide + * common methods used by various interpolation subclasses. These + * include methods to convert a time value into an alpha value (A + * value in the range 0 to 1) and a method to initialize the behavior. + * Subclasses provide the methods that convert alpha values into + * values within that subclass' output range. + */ + +public abstract class Interpolator extends Behavior { + + // This interpolator's alpha generator + Alpha alpha; + + + /** + * Default WakeupCondition for all interpolators. The + * wakeupOn method of Behavior, which takes a WakeupCondition as + * the method parameter, will need to be called at the end + * of the processStimulus method of any class that subclasses + * Interpolator; this can be done with the following method call: + * wakeupOn(defaultWakeupCriterion). + */ + protected WakeupCriterion defaultWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0); + + + /** + * Constructs an Interpolator node with a null alpha value. + */ + public Interpolator() { + } + + + /** + * Constructs an Interpolator node with the specified alpha value. + * @param alpha the alpha object used by this interpolator. + * If it is null, then this interpolator will not run. + */ + public Interpolator(Alpha alpha){ + this.alpha = alpha; + } + + + /** + * Retrieves this interpolator's alpha object. + * @return this interpolator's alpha object + */ + public Alpha getAlpha() { + return this.alpha; + } + + + /** + * Set this interpolator's alpha to the specified alpha object. + * @param alpha the new alpha object. If set to null, + * then this interpolator will stop running. + */ + public void setAlpha(Alpha alpha) { + this.alpha = alpha; + VirtualUniverse.mc.sendRunMessage(J3dThread.RENDER_THREAD); + } + + + /** + * This is the default Interpolator behavior initialization routine. + * It schedules the behavior to awaken at the next frame. + */ + public void initialize() { + // Reset alpha + //alpha.setStartTime(System.currentTimeMillis()); + + // Insert wakeup condition into queue + wakeupOn(defaultWakeupCriterion); + } + + + /** + * Copies all Interpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + Interpolator it = (Interpolator) originalNode; + + Alpha a = it.getAlpha(); + if (a != null) { + setAlpha(a.cloneAlpha()); + } + } +} diff --git a/src/classes/share/javax/media/j3d/J3DBuffer.java b/src/classes/share/javax/media/j3d/J3DBuffer.java new file mode 100644 index 0000000..10c3aaf --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3DBuffer.java @@ -0,0 +1,243 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import com.sun.j3d.internal.ByteBufferWrapper; +import com.sun.j3d.internal.BufferWrapper; +import com.sun.j3d.internal.FloatBufferWrapper; +import com.sun.j3d.internal.DoubleBufferWrapper; +import com.sun.j3d.internal.ByteOrderWrapper; + +/** + * Java 3D wrapper class for java.nio.Buffer objects. + * When used to wrap a non-null NIO buffer object, this class will + * create a read-only view of the wrapped NIO buffer, and will call + * <code>rewind</code> on the read-only view, so that elements 0 + * through <code>buffer.limit()-1</code> will be available internally. + * + * <p> + * NOTE: Use of this class requires version 1.4 of the + * Java<sup><font size="-2">TM</font></sup> 2 Platform. + * Any attempt to access this class on an earlier version of the + * Java 2 Platform will fail with a NoClassDefFoundError. + * Refer to the documentation for version 1.4 of the Java 2 SDK + * for a description of the + * <code> + * <a href="http://java.sun.com/j2se/1.4/docs/api/java/nio/package-summary.html">java.nio</a> + * </code> package. + * + * @see GeometryArray#setCoordRefBuffer(J3DBuffer) + * @see GeometryArray#setColorRefBuffer(J3DBuffer) + * @see GeometryArray#setNormalRefBuffer(J3DBuffer) + * @see GeometryArray#setTexCoordRefBuffer(int,J3DBuffer) + * @see GeometryArray#setInterleavedVertexBuffer(J3DBuffer) + * @see CompressedGeometry#CompressedGeometry(CompressedGeometryHeader,J3DBuffer) + * + * @since Java 3D 1.3 + */ + +public class J3DBuffer { + static final int TYPE_NULL = 0; + static final int TYPE_UNKNOWN = 1; + static final int TYPE_BYTE = 2; + static final int TYPE_CHAR = 3; + static final int TYPE_SHORT = 4; + static final int TYPE_INT = 5; + static final int TYPE_LONG = 6; + static final int TYPE_FLOAT = 7; + static final int TYPE_DOUBLE = 8; + + static boolean unsupportedOperation = false; + + private java.nio.Buffer originalBuffer = null; + private BufferWrapper bufferImpl = null; + private int bufferType = TYPE_NULL; + + /** + * Constructs a J3DBuffer object and initializes it with + * a null NIO buffer object. The NIO buffer object + * must be set to a non-null value before using this J3DBuffer + * object in a Java 3D node component. + * + * @exception UnsupportedOperationException if the JVM does not + * support native access to direct NIO buffers + */ + public J3DBuffer() { + this(null); + } + + + /** + * Constructs a J3DBuffer object and initializes it with + * the specified NIO buffer object. + * + * @param buffer the NIO buffer wrapped by this J3DBuffer + * + * @exception UnsupportedOperationException if the JVM does not + * support native access to direct NIO buffers + * + * @exception IllegalArgumentException if the specified buffer is + * not a direct buffer, or if the byte order of the specified + * buffer does not match the native byte order of the underlying + * platform. + */ + public J3DBuffer(java.nio.Buffer buffer) { + if (unsupportedOperation) + throw new UnsupportedOperationException(J3dI18N.getString("J3DBuffer0")); + + setBuffer(buffer); + } + + + /** + * Sets the NIO buffer object in this J3DBuffer to + * the specified object. + * + * @param buffer the NIO buffer wrapped by this J3DBuffer + * + * @exception IllegalArgumentException if the specified buffer is + * not a direct buffer, or if the byte order of the specified + * buffer does not match the native byte order of the underlying + * platform. + */ + public void setBuffer(java.nio.Buffer buffer) { + int bType = TYPE_NULL; + boolean direct = false; + java.nio.ByteOrder order = java.nio.ByteOrder.BIG_ENDIAN; + + if (buffer == null) { + bType = TYPE_NULL; + } + else if (buffer instanceof java.nio.ByteBuffer) { + bType = TYPE_BYTE; + direct = ((java.nio.ByteBuffer)buffer).isDirect(); + order = ((java.nio.ByteBuffer)buffer).order(); + } + else if (buffer instanceof java.nio.CharBuffer) { + bType = TYPE_CHAR; + direct = ((java.nio.CharBuffer)buffer).isDirect(); + order = ((java.nio.CharBuffer)buffer).order(); + } + else if (buffer instanceof java.nio.ShortBuffer) { + bType = TYPE_SHORT; + direct = ((java.nio.ShortBuffer)buffer).isDirect(); + order = ((java.nio.ShortBuffer)buffer).order(); + } + else if (buffer instanceof java.nio.IntBuffer) { + bType = TYPE_INT; + direct = ((java.nio.IntBuffer)buffer).isDirect(); + order = ((java.nio.IntBuffer)buffer).order(); + } + else if (buffer instanceof java.nio.LongBuffer) { + bType = TYPE_LONG; + direct = ((java.nio.LongBuffer)buffer).isDirect(); + order = ((java.nio.LongBuffer)buffer).order(); + } + else if (buffer instanceof java.nio.FloatBuffer) { + bType = TYPE_FLOAT; + direct = ((java.nio.FloatBuffer)buffer).isDirect(); + order = ((java.nio.FloatBuffer)buffer).order(); + } + else if (buffer instanceof java.nio.DoubleBuffer) { + bType = TYPE_DOUBLE; + direct = ((java.nio.DoubleBuffer)buffer).isDirect(); + order = ((java.nio.DoubleBuffer)buffer).order(); + } + else { + bType = TYPE_UNKNOWN; + } + + // Verify that the buffer is direct and has the correct byte order + if (buffer != null) { + if (!direct) { + throw new IllegalArgumentException(J3dI18N.getString("J3DBuffer1")); + } + + if (order != java.nio.ByteOrder.nativeOrder()) { + throw new IllegalArgumentException(J3dI18N.getString("J3DBuffer2")); + } + } + + bufferType = bType; + originalBuffer = buffer; + + // Make a read-only view of the buffer if the type is one + // of the internally supported types: byte, float, or double + switch (bufferType) { + case TYPE_BYTE: + java.nio.ByteBuffer byteBuffer = + ((java.nio.ByteBuffer)buffer).asReadOnlyBuffer(); + byteBuffer.rewind(); + bufferImpl = new ByteBufferWrapper(byteBuffer); + break; + case TYPE_FLOAT: + java.nio.FloatBuffer floatBuffer = + ((java.nio.FloatBuffer)buffer).asReadOnlyBuffer(); + floatBuffer.rewind(); + bufferImpl = new FloatBufferWrapper(floatBuffer); + break; + case TYPE_DOUBLE: + java.nio.DoubleBuffer doubleBuffer = + ((java.nio.DoubleBuffer)buffer).asReadOnlyBuffer(); + doubleBuffer.rewind(); + bufferImpl = new DoubleBufferWrapper(doubleBuffer); + break; + default: + bufferImpl = null; + } + } + + + /** + * Retrieves the NIO buffer object from this J3DBuffer. + * + * @return the current NIO buffer wrapped by this J3DBuffer + */ + public java.nio.Buffer getBuffer() { + return originalBuffer; + } + + + int getBufferType() { + return bufferType; + } + + + BufferWrapper getBufferImpl() { + return bufferImpl; + } + + // Native method to verify that we can access a direct NIO buffer + // from native code + private native static long getNativeAddress(java.nio.Buffer buffer); + + private static boolean checkNativeBufferAccess(java.nio.Buffer buffer) { + // TODO: uncomment out the call to getNativeAddress and implement it + if (buffer == null /*|| getNativeAddress(buffer) == 0L*/) { + return false; + } + else { + return true; + } + } + + static { + // Allocate a direct byte buffer and verify that we can + // access the data pointer from native code + java.nio.ByteBuffer buffer = java.nio.ByteBuffer.allocateDirect(8); + + if (!checkNativeBufferAccess(buffer)) { + unsupportedOperation = true; + } + } +} diff --git a/src/classes/share/javax/media/j3d/J3DGraphics2D.java b/src/classes/share/javax/media/j3d/J3DGraphics2D.java new file mode 100644 index 0000000..61bf6ab --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3DGraphics2D.java @@ -0,0 +1,171 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.ImageObserver; + + +/** + * The J3DGraphics2D class extends Graphics2D to provide 2D rendering + * into a Canvas3D. It is an abstract base class that is further + * extended by a non-public Java 3D implementation class. This class + * allows Java 2D rendering to be mixed with Java 3D rendering in the + * same Canvas3D, subject to the same restrictions as imposed for 3D + * immediate-mode rendering: In mixed-mode rendering, all Java 2D + * requests must be done from one of the Canvas3D callback methods; in + * pure-immediate mode, the Java 3D renderer must be stopped for the + * Canvas3D being rendered into. + * + * <p> + * An application obtains a J3D 2D graphics context object from the + * Canvas3D object that the application wishes to render into by using + * the getGraphics2D method. A new J3DGraphics2D object is created if + * one does not already exist. + * + * <p> + * Note that the drawing methods in this class, including those + * inherited from Graphics2D, are not necessarily executed + * immediately. They may be buffered up for future execution. + * Applications must call the <code><a + * href="#flush(boolean)">flush</a>(boolean)</code> method to ensure + * that the rendering actually happens. The flush method is implicitly + * called in the following cases: + * + * <ul> + * <li>The <code>Canvas3D.swap</code> method calls + * <code>flush(true)</code></li> + * <li>The Java 3D renderer calls <code>flush(true)</code> prior to + * swapping the buffer for a double buffered on-screen Canvas3D</li> + * <li>The Java 3D renderer calls <code>flush(true)</code> prior to + * copying into the off-screen buffer of an off-screen Canvas3D</li> + * <li>The Java 3D renderer calls <code>flush(false)</code> after + * calling the preRender, renderField, postRender, and postSwap + * Canvas3D callback methods.</li> + * </ul> + * + * <p> + * A single-buffered, pure-immediate mode application must explicitly + * call flush to ensure that the graphics will be rendered to the + * Canvas3D. + * + * @see Canvas3D#getGraphics2D + * + * @since Java 3D 1.2 + */ + +public abstract class J3DGraphics2D extends Graphics2D { + + // Package scope contructor + J3DGraphics2D() { + } + + /** + * This method is not supported. The only way to obtain a + * J3DGraphics2D is from the associated Canvas3D. + * + * @exception UnsupportedOperationException this method is not supported + * + * @see Canvas3D#getGraphics2D + */ + public final Graphics create() { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported. The only way to obtain a + * J3DGraphics2D is from the associated Canvas3D. + * + * @exception UnsupportedOperationException this method is not supported + * + * @see Canvas3D#getGraphics2D + */ + public final Graphics create(int x, int y, int width, int height) { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported. Clearing a Canvas3D is done implicitly + * via a Background node in the scene graph or explicitly via the clear + * method in a 3D graphics context. + * + * @exception UnsupportedOperationException this method is not supported + * + * @see Background + * @see GraphicsContext3D#setBackground + * @see GraphicsContext3D#clear + */ + public final void setBackground(Color color) { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported. Clearing a Canvas3D is done implicitly + * via a Background node in the scene graph or explicitly via the clear + * method in a 3D graphics context. + * + * @exception UnsupportedOperationException this method is not supported + * + * @see Background + * @see GraphicsContext3D#getBackground + * @see GraphicsContext3D#clear + */ + public final Color getBackground() { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported. Clearing a Canvas3D is done implicitly + * via a Background node in the scene graph or explicitly via the clear + * method in a 3D graphics context. + * + * @exception UnsupportedOperationException this method is not supported + * + * @see Background + * @see GraphicsContext3D#setBackground + * @see GraphicsContext3D#clear + */ + public final void clearRect(int x, int y, int width, int height) { + throw new UnsupportedOperationException(); + } + + + /** + * Flushes all previously executed rendering operations to the + * drawing buffer for this 2D graphics object. + * + * @param wait flag indicating whether or not to wait for the + * rendering to be complete before returning from this call. + */ + public abstract void flush(boolean wait); + + /** + * Draws the specified image and flushes the buffer. This is + * functionally equivalent to calling <code>drawImage(...)</code> + * followed by <code>flush(false)</code>, but can avoid the cost + * of making an extra copy of the image in some cases. Anything + * previously drawn to this J3DGraphics2D will be flushed before + * the image is drawn. + * + * @param img The image to draw + * @param x The x location to draw at + * @param y The y location to draw at + * @param observer The ImageObserver + * + * @since Java 3D 1.3 + */ + public abstract void drawAndFlushImage(BufferedImage img, int x, int y, + ImageObserver observer); + +} diff --git a/src/classes/share/javax/media/j3d/J3DGraphics2DImpl.java b/src/classes/share/javax/media/j3d/J3DGraphics2DImpl.java new file mode 100644 index 0000000..c0e7425 --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3DGraphics2DImpl.java @@ -0,0 +1,1056 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.io.*; +import java.util.*; +import java.awt.*; +import java.awt.event.*; +import java.text.AttributedCharacterIterator; +import java.awt.RenderingHints.Key; +import java.awt.geom.*; +import java.awt.image.*; +import java.awt.image.renderable.RenderableImage; +import java.awt.font.*; + +/** + * Implementation class for J3DGraphics2D + */ + +final class J3DGraphics2DImpl extends J3DGraphics2D { + private Graphics2D offScreenGraphics2D; + private BufferedImage g3dImage = null; + private byte[] data = null; + private boolean isFlushed = true; + private Canvas3D canvas3d; + private int width, height; + private int texWidth, texHeight; + private int xmin, ymin, xmax, ymax; + private Object extentLock = new Object(); + private boolean abgr; + private boolean initTexMap = false; + private boolean strokeSet=false; + private Point2D.Float ptSrc = new Point2D.Float(); + private Point2D.Float ptDst1 = new Point2D.Float(); + private Point2D.Float ptDst2 = new Point2D.Float(); + private Color xOrModeColor = null; + private volatile boolean initCtx = false; + private boolean threadWaiting = false; + static final Color blackTransparent = new Color(0,0,0,0); + private boolean useDrawPixel = VirtualUniverse.mc.isJ3dG2dDrawPixel; + int objectId = -1; + + // Package scope contructor + J3DGraphics2DImpl(Canvas3D c) { + canvas3d = c; + + synchronized (VirtualUniverse.mc.contextCreationLock) { + if (c.ctx == 0) { + // create a dummy bufferImage + width = 1; + height = 1; + g3dImage = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + offScreenGraphics2D = g3dImage.createGraphics(); + } else { + init(); + } + } + + } + + // This is invoke from Renderer callback when the first + // time createContext() finish which set + // canvas3d.extensionSupported correctly. + void init() { + // if ABGR extension is supported, we want to use + // TYPE_4BYTE_ABGR to make a fast copy + if (!initCtx) { + abgr = ((canvas3d.extensionsSupported & Canvas3D.EXT_ABGR) != 0); + + width = canvas3d.getWidth(); + height = canvas3d.getHeight(); + initTexMap = false; + + if (width <= 0) { + width = 1; + } + if (height <= 0) { + height = 1; + } + + synchronized (extentLock) { + xmax = width; + ymax = height; + xmin = 0; + ymin = 0; + } + g3dImage = new BufferedImage(width, height, + (abgr ? BufferedImage.TYPE_4BYTE_ABGR: + BufferedImage.TYPE_INT_ARGB)); + offScreenGraphics2D = g3dImage.createGraphics(); + clearOffScreen(); + if (!abgr) { + data = new byte[width*height*4]; + } + + // should be the last flag to set + initCtx = true; + } + } + + /** + * Flushes all previously executed rendering operations to the + * drawing buffer for this 2D graphics object. + * + * @param wait flag indicating whether or not to wait for the + * rendering to be complete before returning from this call. + */ + public void flush(boolean waiting) { + + if (!isFlushed) { + // Composite g3dImage into Canvas3D + if (Thread.currentThread() == canvas3d.screen.renderer) { + if (!initCtx) { + return; + } + doFlush(); + } else { + if (!initCtx) { + if (waiting && + (canvas3d.pendingView != null) && + canvas3d.pendingView.activeStatus) { + // wait until Renderer init() this context + + while (!initCtx) { + MasterControl.threadYield(); + } + } else { + return; + } + } + // Behavior Scheduler or other threads + // TODO: may not be legal for behaviorScheduler + // May cause deadlock if it is in behaviorScheduler + // and we wait for Renderer to finish + boolean renderRun = (Thread.currentThread() != + canvas3d.view.universe.behaviorScheduler); + // This must put before sendRenderMessage() + threadWaiting = true; + sendRenderMessage(renderRun, GraphicsContext3D.FLUSH2D, null, + null, null); + if (waiting) { + // It is possible that thread got notify BEFORE + // the following runMonitor invoke. + runMonitor(J3dThread.WAIT); + } + } + isFlushed = true; + + } + } + + // copy the data into a byte buffer that will be passed to opengl + void doFlush() { + // clip to offscreen buffer size + if (canvas3d.ctx == 0) { + canvas3d.getGraphicsContext3D().doClear(); + } + + synchronized (extentLock) { + if (xmin < 0) { + xmin = 0; + } + if (xmax > width) { + xmax = width; + } + if (ymin < 0) { + ymin = 0; + } + if (ymax > height) { + ymax = height; + } + + if ((xmax - xmin > 0) && + (ymax - ymin > 0)) { + if (abgr) { + data = ((DataBufferByte)g3dImage.getRaster().getDataBuffer()).getData(); + } else { + copyImage(g3dImage, data, width, height, xmin, ymin, xmax, ymax); + } + copyDataToCanvas(0, 0, xmin, ymin, xmax, ymax, width, height); + } else { + + runMonitor(J3dThread.NOTIFY); + } + // this define an empty region + xmax = 0; + ymax = 0; + xmin = width; + ymin = height; + } + + } + + + // borrowed from ImageComponentRetained since ImageComponent2D + // seems to do stuff we don't need to + final void copyImage(BufferedImage bi, byte[] image, + int width, int height, + int x1, int y1, int x2, int y2) { + int biType = bi.getType(); + int w, h, i, j; + int row, rowBegin, rowInc, dstBegin; + + dstBegin = 0; + rowInc = 1; + rowBegin = 0; + + // convert format to RGBA for underlying OGL use + if ((biType == BufferedImage.TYPE_INT_ARGB) || + (biType == BufferedImage.TYPE_INT_RGB)) { + // optimized cases + rowBegin = y1; + + int colBegin = x1; + + int[] intData = + ((DataBufferInt)bi.getRaster().getDataBuffer()).getData(); + int rowOffset = rowInc * width; + int intPixel; + + rowBegin = rowBegin*width + colBegin; + dstBegin = rowBegin*4; + + if (biType == BufferedImage.TYPE_INT_ARGB) { + for (h = y1; h < y2; h++) { + i = rowBegin; + j = dstBegin; + for (w = x1; w < x2; w++, i++) { + intPixel = intData[i]; + image[j++] = (byte)((intPixel >> 16) & 0xff); + image[j++] = (byte)((intPixel >> 8) & 0xff); + image[j++] = (byte)(intPixel & 0xff); + image[j++] = (byte)((intPixel >> 24) & 0xff); + } + rowBegin += rowOffset; + dstBegin += (rowOffset*4); + } + } else { + for (h = y1; h < y2; h++) { + i = rowBegin; + j = dstBegin; + for (w = x1; w < x2; w++, i++) { + intPixel = intData[i]; + image[j++] = (byte)((intPixel >> 16) & 0xff); + image[j++] = (byte)((intPixel >> 8) & 0xff); + image[j++] = (byte)(intPixel & 0xff); + image[j++] = (byte)255; + } + rowBegin += rowOffset; + dstBegin += (rowOffset*4); + } + } + } else { + // non-optimized cases + WritableRaster ras = bi.getRaster(); + ColorModel cm = bi.getColorModel(); + Object pixel = ImageComponentRetained.getDataElementBuffer(ras); + + j = (y1*width + x1)*4; + for (h = y1; h < y2; h++) { + i = j; + for (w = x1; w < x2; w++) { + ras.getDataElements(w, h, pixel); + image[j++] = (byte)cm.getRed(pixel); + image[j++] = (byte)cm.getGreen(pixel); + image[j++] = (byte)cm.getBlue(pixel); + image[j++] = (byte)cm.getAlpha(pixel); + + } + j = i+ width*4; + } + } + } + + void sendRenderMessage(boolean renderRun, int command, + Object arg1, Object arg2, Object arg3) { + // send a message to the request renderer + J3dMessage renderMessage = VirtualUniverse.mc.getMessage(); + renderMessage.threads = J3dThread.RENDER_THREAD; + renderMessage.type = J3dMessage.RENDER_IMMEDIATE; + renderMessage.universe = null; + renderMessage.view = null; + renderMessage.args[0] = canvas3d; + renderMessage.args[1] = new Integer(command); + renderMessage.args[2] = arg1; + renderMessage.args[3] = arg2; + renderMessage.args[4] = arg3; + + while (!canvas3d.view.inRenderThreadData) { + // wait until the renderer thread data in added in + // MC:RenderThreadData array ready to receive message + MasterControl.threadYield(); + } + + canvas3d.screen.renderer.rendererStructure.addMessage(renderMessage); + + if (renderRun) { + // notify mc that there is work to do + VirtualUniverse.mc.sendRunMessage(canvas3d.view, J3dThread.RENDER_THREAD); + } else { + // notify mc that there is work for the request renderer + VirtualUniverse.mc.setWorkForRequestRenderer(); + } + } + + final void validate() { + validate(0, 0, width, height); + } + + void validate(float x1, float y1, float x2, float y2, + AffineTransform xform) { + float t; + + if (xform == null) { + validate(x1, y1, x2, y2); + } else { + ptSrc.x = x1; + ptSrc.y = y1; + xform.transform(ptSrc, ptDst1); + ptSrc.x = x2; + ptSrc.y = y2; + xform.transform(ptSrc, ptDst2); + + if (ptDst1.x > ptDst2.x) { + t = ptDst1.x; + ptDst1.x = ptDst2.x; + ptDst2.x = t; + } + if (ptDst1.y > ptDst2.y) { + t = ptDst1.y; + ptDst1.y = ptDst2.y; + ptDst2.y = t; + } + // take care of numerical error by adding 1 + validate(ptDst1.x-1, ptDst1.y-1, ptDst2.x+1, ptDst2.y+1); + } + } + + void validate(float x1, float y1, float x2, float y2) { + boolean doResize = false; + isFlushed = false; + + synchronized(canvas3d) { + if (initCtx && canvas3d.resizeGraphics2D) { + doResize = true; + canvas3d.resizeGraphics2D = false; + } + } + if (doResize) { + synchronized (VirtualUniverse.mc.contextCreationLock) { + Graphics2D oldOffScreenGraphics2D = offScreenGraphics2D; + initCtx = false; + init(); + copyGraphics2D(oldOffScreenGraphics2D); + } + } else { + AffineTransform tr = getTransform(); + ptSrc.x = x1; + ptSrc.y = y1; + tr.transform(ptSrc, ptDst1); + ptSrc.x = x2; + ptSrc.y = y2; + tr.transform(ptSrc, ptDst2); + + synchronized (extentLock) { + if (ptDst1.x < xmin) { + xmin = (int) ptDst1.x; + } + if (ptDst1.y < ymin) { + ymin = (int) ptDst1.y; + } + if (ptDst2.x > xmax) { + xmax = (int) ptDst2.x; + } + if (ptDst2.y > ymax) { + ymax = (int) ptDst2.y; + } + } + } + } + + void copyGraphics2D(Graphics2D oldg) { + // restore the original setting of Graphics2D when resizing the windows + setColor(oldg.getColor()); + setFont(oldg.getFont()); + setClip(oldg.getClip()); + setComposite(oldg.getComposite()); + setTransform(oldg.getTransform()); + setPaint(oldg.getPaint()); + setStroke(oldg.getStroke()); + if (xOrModeColor != null) { + setXORMode(xOrModeColor); + } + + } + + // Implementation of Graphics2D methods + public final void clip(Shape s) { + offScreenGraphics2D.clip(s); + } + + public FontMetrics getFontMetrics() { + return offScreenGraphics2D.getFontMetrics(); + } + + public Rectangle getClipBounds(Rectangle r) { + return offScreenGraphics2D.getClipBounds(r); + } + + public Rectangle getClipRect() { + return offScreenGraphics2D.getClipRect(); + } + + public String toString() { + return offScreenGraphics2D.toString(); + + } + + public final AffineTransform getTransform() { + return offScreenGraphics2D.getTransform(); + } + + public final Color getColor() { + return offScreenGraphics2D.getColor(); + } + + public final Composite getComposite() { + return offScreenGraphics2D.getComposite(); + } + + public final Font getFont() { + return offScreenGraphics2D.getFont(); + } + + public final FontMetrics getFontMetrics(Font f) { + return offScreenGraphics2D.getFontMetrics(f); + } + + public final FontRenderContext getFontRenderContext() { + return offScreenGraphics2D.getFontRenderContext(); + } + + public final GraphicsConfiguration getDeviceConfiguration() { + return offScreenGraphics2D.getDeviceConfiguration(); + } + + public final Object getRenderingHint(Key hintKey) { + return offScreenGraphics2D.getRenderingHint(hintKey); + } + + public final Paint getPaint() { + return offScreenGraphics2D.getPaint(); + } + + public final Rectangle getClipBounds() { + return offScreenGraphics2D.getClipBounds(); + } + + public final RenderingHints getRenderingHints() { + return offScreenGraphics2D.getRenderingHints(); + } + + public final Shape getClip() { + return offScreenGraphics2D.getClip(); + } + + public final Stroke getStroke() { + return offScreenGraphics2D.getStroke(); + } + + public final boolean drawImage(Image img, AffineTransform xform, + ImageObserver obs) { + + validate(0, 0, img.getWidth(obs), img.getHeight(obs), xform); + return offScreenGraphics2D.drawImage(img, xform, obs); + } + + public final void drawImage(BufferedImage img, BufferedImageOp op, + int x, int y) { + if (op != null) { + img = op.filter(img, null); + } + validate(x, y, x+img.getWidth(), y+img.getHeight()); + offScreenGraphics2D.drawImage(img, null, x, y); + } + + public final boolean drawImage(Image img, + int x, int y, + ImageObserver observer) { + + validate(x, y, + x + img.getWidth(observer), + y + img.getWidth(observer)); + return offScreenGraphics2D.drawImage(img, x, y, observer); + } + + public final boolean drawImage(Image img, int x, int y, + int width, int height, + ImageObserver observer) { + validate(x, y, x+width, y+height); + return offScreenGraphics2D.drawImage(img, x, y, width, height, + observer); + } + + public final boolean drawImage(Image img, int x, int y, + int width, int height, + Color bgcolor, + ImageObserver observer) { + validate(x, y, x+width, y+height); + return offScreenGraphics2D.drawImage(img, x, y, width, height, bgcolor, + observer); + } + + public final void drawImage(BufferedImage img, + int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + ImageObserver observer) { + validate(dx1, dy1, dx2, dy2); + offScreenGraphics2D.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, + sx2, sy2, observer); + } + + public final boolean drawImage(Image img, + int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + ImageObserver observer) { + validate(dx1, dy1, dx2, dy2); + return offScreenGraphics2D.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, + sx2, sy2, observer); + } + + public final boolean drawImage(Image img, + int dx1, int dy1, int dx2, int dy2, + int sx1, int sy1, int sx2, int sy2, + Color bgcolor, + ImageObserver observer) { + validate(dx1, dy1, dx2, dy2); + return offScreenGraphics2D.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, + sx2, sy2, bgcolor, observer); + } + + public final boolean drawImage(Image img, int x, int y, + Color bgcolor, + ImageObserver observer) { + validate(x, y, x+img.getWidth(observer), y+img.getHeight(observer)); + return offScreenGraphics2D.drawImage(img, x, y, bgcolor, observer); + } + + public final boolean hit(Rectangle rect, Shape s, boolean onStroke) { + return offScreenGraphics2D.hit(rect, s, onStroke); + } + + public final void addRenderingHints(Map hints) { + offScreenGraphics2D.addRenderingHints(hints); + } + + public final void clipRect(int x, int y, int width, int height) { + offScreenGraphics2D.clipRect(x, y, width, height); + } + + public final void copyArea(int x, int y, int width, int height, + int dx, int dy) { + validate(x+dx, y+dy, x+dx+width, y+dy+height); + offScreenGraphics2D.copyArea(x, y, width, height, dx, dy); + } + + public final void dispose() { + offScreenGraphics2D.dispose(); + } + + public final void draw(Shape s) { + Rectangle rect = s.getBounds(); + validate(rect.x, rect.y, + rect.x + rect.width, + rect.y + rect.height); + offScreenGraphics2D.draw(s); + } + + public final void drawArc(int x, int y, int width, int height, + int startAngle, int arcAngle) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawArc(x, y, width, height, startAngle, arcAngle); + } + + public final void drawGlyphVector(GlyphVector g, float x, float y) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawGlyphVector(g, x, y); + } + + public final void drawLine(int x1, int y1, int x2, int y2) { + int minx, miny, maxx, maxy; + if (!strokeSet) { + if (x1 > x2) { + minx = x2; + maxx = x1; + } else { + minx = x1; + maxx = x2; + } + if (y1 > y2) { + miny = y2; + maxy = y1; + } else { + miny = y1; + maxy = y2; + } + validate(minx, miny, maxx, maxy); + } else { + // TODO: call validate with bounding box of primitive + // TODO: Need to consider Stroke width + validate(); + } + offScreenGraphics2D.drawLine(x1, y1, x2, y2); + } + + public final void drawOval(int x, int y, int width, int height) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawOval(x, y, width, height); + } + + public final void drawPolygon(int xPoints[], int yPoints[], + int nPoints) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawPolygon(xPoints, yPoints, nPoints); + } + + public final void drawPolyline(int xPoints[], int yPoints[], + int nPoints) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawPolyline(xPoints, yPoints, nPoints); + } + + public final void drawRenderableImage(RenderableImage img, + AffineTransform xform) { + + validate(0, 0, img.getWidth(), img.getHeight(), xform); + offScreenGraphics2D.drawRenderableImage(img, xform); + } + + public final void drawRenderedImage(RenderedImage img, + AffineTransform xform) { + validate(0, 0, img.getWidth(), img.getHeight(), xform); + offScreenGraphics2D.drawRenderedImage(img, xform); + } + + public final void drawRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawRoundRect(x, y, width, height, arcWidth, + arcHeight); + } + + public final void drawString(AttributedCharacterIterator iterator, + int x, int y) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawString(iterator, x, y); + } + + public final void drawString(AttributedCharacterIterator iterator, + float x, float y) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawString(iterator, x, y); + } + + public final void drawString(String s, float x, float y) { + TextLayout layout = new TextLayout(s, getFont(), + getFontRenderContext()); + Rectangle2D bounds = layout.getBounds(); + float x1 = (float) bounds.getX(); + float y1 = (float) bounds.getY(); + validate(x1+x, y1+y, + x1 + x + (float) bounds.getWidth(), + y1 + y + (float) bounds.getHeight()); + offScreenGraphics2D.drawString(s, x, y); + + } + + public final void drawString(String s, int x, int y) { + drawString(s, (float) x, (float) y); + } + + public final void fill(Shape s) { + Rectangle rect = s.getBounds(); + validate(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height); + offScreenGraphics2D.fill(s); + } + + public final void fillArc(int x, int y, int width, int height, + int startAngle, int arcAngle) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.fillArc(x, y, width, height, startAngle, arcAngle); + } + + public final void fillOval(int x, int y, int width, int height) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.fillOval(x, y, width, height); + } + + public final void fillRoundRect(int x, int y, int width, int height, + int arcWidth, int arcHeight) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.fillRoundRect(x, y, width, height, arcWidth, + arcHeight); + } + + public final void rotate(double theta) { + offScreenGraphics2D.rotate(theta); + } + + public final void rotate(double theta, double x, double y) { + offScreenGraphics2D.rotate(theta, x, y); + } + + public final void scale(double sx, double sy) { + offScreenGraphics2D.scale(sx, sy); + } + + public final void setClip(Shape clip) { + offScreenGraphics2D.setClip(clip); + } + + + public final void setClip(int x, int y, int width, int height) { + offScreenGraphics2D.setClip(x, y, width, height); + } + + public final void setColor(Color c) { + offScreenGraphics2D.setColor(c); + } + + public final void setComposite(Composite comp) { + offScreenGraphics2D.setComposite(comp); + } + + public final void setFont(Font font) { + offScreenGraphics2D.setFont(font); + } + + public final void setPaint( Paint paint ) { + offScreenGraphics2D.setPaint(paint); + } + + public final void setPaintMode() { + xOrModeColor = null; + offScreenGraphics2D.setPaintMode(); + } + + public final void setRenderingHint(Key hintKey, Object hintValue) { + offScreenGraphics2D.setRenderingHint(hintKey, hintValue); + } + + public final void setRenderingHints(Map hints) { + offScreenGraphics2D.setRenderingHints(hints); + } + + public final void setStroke(Stroke s) { + strokeSet = (s != null); + offScreenGraphics2D.setStroke(s); + } + + public final void setTransform(AffineTransform Tx) { + offScreenGraphics2D.setTransform(Tx); + } + + public final void setXORMode(Color c1) { + xOrModeColor = c1; + offScreenGraphics2D.setXORMode(c1); + } + + public final void shear(double shx, double shy) { + offScreenGraphics2D.shear(shx, shy); + } + + public final void transform(AffineTransform Tx) { + offScreenGraphics2D.transform(Tx); + } + + public final void translate(double tx, double ty) { + offScreenGraphics2D.translate(tx, ty); + } + + public final void translate(int x, int y) { + offScreenGraphics2D.translate(x, y); + } + public boolean hitClip(int x, int y, int width, int height) { + return offScreenGraphics2D.hitClip(x, y, width, height); + } + + public void draw3DRect(int x, int y, int width, int height, + boolean raised) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.draw3DRect(x, y, width, height, raised); + } + + public void drawBytes(byte data[], int offset, int length, int x, int y) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawBytes(data, offset, length, x, y); + } + + public void drawChars(char data[], int offset, int length, int x, int y) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawChars(data, offset, length, x, y); + } + + public void drawPolygon(Polygon p) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.drawPolygon(p); + } + + public void drawRect(int x, int y, int width, int height) { + // TODO: call validate with bounding box of primitive + // TODO: need to consider Stroke width + validate(); + offScreenGraphics2D.drawRect(x, y, width, height); + } + + public void fill3DRect(int x, int y, int width, int height, + boolean raised) { + // TODO: call validate with bounding box of primitive + // TODO: need to consider Stroke width + validate(); + offScreenGraphics2D.fill3DRect(x, y, width, height, raised); + } + + public void fillPolygon(Polygon p) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.fillPolygon(p); + } + + public final void fillPolygon(int xPoints[], int yPoints[], + int nPoints) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.fillPolygon(xPoints, yPoints, nPoints); + } + + public final void fillRect(int x, int y, int width, int height) { + // TODO: call validate with bounding box of primitive + validate(); + offScreenGraphics2D.fillRect(x, y, width, height); + } + + public void finalize() { + if (objectId >= 0) { + VirtualUniverse.mc.freeTexture2DId(objectId); + } + offScreenGraphics2D.finalize(); + } + + public void drawAndFlushImage(BufferedImage img, int x, int y, + ImageObserver observer) { + + if (!(initCtx && abgr && + (img.getType() == BufferedImage.TYPE_4BYTE_ABGR))) { + drawImage(img, x, y, observer); + flush(false); + return; + } + + if (Thread.currentThread() == canvas3d.screen.renderer) { + doDrawAndFlushImage(img, x, y, observer); + } else { + // Behavior Scheduler or other threads + // TODO: may not be legal for behaviorScheduler + // May cause deadlock if it is in behaviorScheduler + // and we wait for Renderer to finish + boolean renderRun = (Thread.currentThread() != + canvas3d.view.universe.behaviorScheduler); + sendRenderMessage(renderRun, GraphicsContext3D.DRAWANDFLUSH2D, + img, new Point(x, y), observer); + } + } + + void doDrawAndFlushImage(BufferedImage img, int x, int y, + ImageObserver observer) { + int imgWidth = img.getWidth(observer); + int imgHeight = img.getHeight(observer); + int px, py, x1, y1, x2, y2; + + if (canvas3d.ctx == 0) { + canvas3d.getGraphicsContext3D().doClear(); + } + + // format needs to be 4BYTE_ABGR and abgr needs to be supported + // also must be in canvas callback + data = ((DataBufferByte)img.getRaster().getDataBuffer()).getData(); + + // Transform the affine transform, + // note we do not handle scale/rotate etc. + AffineTransform tr = getTransform(); + ptSrc.x = x; + ptSrc.y = y; + tr.transform(ptSrc, ptDst1); + px = (int) ptDst1.x; + py = (int) ptDst1.y; + + // clip to offscreen buffer size + + if (px + imgWidth > width) { + x2 = width - px; + } else { + x2 = imgWidth; + } + + if (px < 0) { + x1 = -px; + px = 0; + } else { + x1 = 0; + } + + if (py + imgHeight > height) { + y2 = height - py; + } else { + y2 = imgHeight; + } + + if (py < 0) { + y1 = -py; + py = 0; + } else { + y1 = 0; + } + + if ((y2 - y1 > 0) && (x2 - x1 > 0)) { + copyDataToCanvas(px, py, x1,y1, x2, y2,imgWidth, imgHeight); + } + + } + + + void copyDataToCanvas(int px, int py, int x1, int y1, + int x2, int y2, int w, int h) { + try { + if (!canvas3d.drawingSurfaceObject.renderLock()) { + return; + } + + if (useDrawPixel) { + canvas3d.composite(canvas3d.ctx, px, py, + x1, y1, x2, y2, w, data, width, height); + } else { + if (!initTexMap) { + if (objectId == -1) { + objectId = VirtualUniverse.mc.getTexture2DId(); + } + texWidth = getGreaterPowerOf2(w); + texHeight = getGreaterPowerOf2(h); + + // Canvas got resize, need to init texture map again + // in Renderer thread + if (!canvas3d.initTexturemapping(canvas3d.ctx, + texWidth, texHeight, + objectId)) { + // Fail to get the texture surface, most likely + // there is not enough texture memory + initTexMap = false; + VirtualUniverse.mc.freeTexture2DId(objectId); + objectId = -1; + // Use DrawPixel next time + useDrawPixel = true; + } else { + initTexMap = true; + } + } + if (initTexMap) { + canvas3d.texturemapping(canvas3d.ctx, px, py, + x1, y1, x2, y2, + texWidth, texHeight, w, + (abgr ? ImageComponentRetained.BYTE_ABGR: + ImageComponentRetained.BYTE_RGBA), + objectId, data, width, height); + } else { + // Fall back to composite for this round + canvas3d.composite(canvas3d.ctx, px, py, + x1, y1, x2, y2, w, data, + width, height); + + } + } + canvas3d.drawingSurfaceObject.unLock(); + } catch (NullPointerException ne) { + canvas3d.drawingSurfaceObject.unLock(); + throw ne; + } + + clearOffScreen(); + runMonitor(J3dThread.NOTIFY); + } + + void clearOffScreen() { + Composite comp = offScreenGraphics2D.getComposite(); + Color c = offScreenGraphics2D.getColor(); + offScreenGraphics2D.setComposite(AlphaComposite.Src); + offScreenGraphics2D.setColor(blackTransparent); + offScreenGraphics2D.fillRect(xmin, ymin, (xmax-xmin), (ymax-ymin)); + offScreenGraphics2D.setComposite(comp); + offScreenGraphics2D.setColor(c); + } + + /** + * Return an integer of power 2 greater than x + */ + static int getGreaterPowerOf2(int x) { + int i = -1; + if (x >= 0) { + for (i = 1; i < x; i <<= 1); + } + return i; + } + + /** + * MC may not scheduler Renderer thread or Renderer thread + * may not process message FLUSH. This will hang user + * thread. + */ + synchronized void runMonitor(int action) { + if (action == J3dThread.WAIT) { + if (threadWaiting) { + try { + wait(); + } catch (InterruptedException e){} + } + } else if (action == J3dThread.NOTIFY) { + notify(); + threadWaiting = false; + } + } +} diff --git a/src/classes/share/javax/media/j3d/J3dDebug.java b/src/classes/share/javax/media/j3d/J3dDebug.java new file mode 100644 index 0000000..3b056a5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3dDebug.java @@ -0,0 +1,442 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class J3dDebug { + + // For production release devPhase must be set to false. + + // Do no debugging. + static final int NO_DEBUG = 0; + + // How much debugging information do we want ? + // (LEVEL_1 is very terse, LEVEL_5 is very verbose) + static final int LEVEL_1 = 1; + static final int LEVEL_2 = 2; + static final int LEVEL_3 = 3; + static final int LEVEL_4 = 4; + static final int LEVEL_5 = 5; + + // This static final variable is used to turn on/off debugging, + // checking, and initializing codes that may be preferred in + // development phase but not necessarily required in the + // production release. + // + // Beside for debugging, use this variable to do initialization, + // checking objects existence, and other checks that may help in + // uncovering potential bugs during code development. This + // variable should be turned off during production release as it + // may cause performance hit. + static final boolean devPhase = true; + + // This is a property variable. It allows a true/false be sent to + // J3d from command line, to on/off code segments. To avoid + // performance hit in production release, this variable MUST be + // used with devPhase when guarding code segments for execution. + // eg. if(J3dDebug.devPhase && J3dDebug.debug) + // do code_segment; + // Note: devPhase is a static final variable and debug isn't. If + // devPhase is put before debug, smart compiler will not include + // code_segment when devPhase is false. + static boolean debug; + + // Class debug variable, there is one debug variable per class. + // Set one of the 5 debug levels to the class debug variable when + // debugging. + // For example, alpha = !devPhase?NO_DEBUG:LEVEL_2; will cause + // code segments guarded by LEVEL_1 and LEVEL_2 be executed. And + // alpha = !devPhase?NO_DEBUG:NO_DEBUG; means do no debug. + static final int alpha = !devPhase?NO_DEBUG:NO_DEBUG; + static final int alternateAppearance = !devPhase?NO_DEBUG:NO_DEBUG; + static final int ambientLight = !devPhase?NO_DEBUG:NO_DEBUG; + static final int ambientLightRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int appearance = !devPhase?NO_DEBUG:NO_DEBUG; + static final int appearanceRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int assertionFailureException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int attributeBin = !devPhase?NO_DEBUG:NO_DEBUG; + static final int audioDevice = !devPhase?NO_DEBUG:NO_DEBUG; + static final int audioDevice3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int audioDeviceEnumerator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int auralAttributes = !devPhase?NO_DEBUG:NO_DEBUG; + static final int auralAttributesRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int bHInsertStructure = !devPhase?NO_DEBUG:NO_DEBUG; + static final int bHInternalNode = !devPhase?NO_DEBUG:NO_DEBUG; + static final int bHLeafInterface = !devPhase?NO_DEBUG:NO_DEBUG; + static final int bHLeafNode = !devPhase?NO_DEBUG:NO_DEBUG; + static final int bHNode = !devPhase?NO_DEBUG:NO_DEBUG; + static final int bHTree = !devPhase?NO_DEBUG:NO_DEBUG; + static final int background = !devPhase?NO_DEBUG:NO_DEBUG; + static final int backgroundRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int backgroundSound = !devPhase?NO_DEBUG:NO_DEBUG; + static final int backgroundSoundRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int badTransformException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int behavior = !devPhase?NO_DEBUG:NO_DEBUG; + static final int behaviorRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int behaviorScheduler = !devPhase?NO_DEBUG:NO_DEBUG; + static final int behaviorStructure = !devPhase?NO_DEBUG:NO_DEBUG; + static final int billboard = !devPhase?NO_DEBUG:NO_DEBUG; + static final int boundingBox = !devPhase?NO_DEBUG:NO_DEBUG; + static final int boundingLeaf = !devPhase?NO_DEBUG:NO_DEBUG; + static final int boundingLeafRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int boundingPolytope = !devPhase?NO_DEBUG:NO_DEBUG; + static final int boundingSphere = !devPhase?NO_DEBUG:NO_DEBUG; + static final int bounds = !devPhase?NO_DEBUG:NO_DEBUG; + static final int branchGroup = !devPhase?NO_DEBUG:NO_DEBUG; + static final int branchGroupRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int cachedFrustum = !devPhase?NO_DEBUG:NO_DEBUG; + static final int canvas3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int canvasViewCache = !devPhase?NO_DEBUG:NO_DEBUG; + static final int canvasViewEventCatcher = !devPhase?NO_DEBUG:NO_DEBUG; + static final int capabilityBits = !devPhase?NO_DEBUG:NO_DEBUG; + static final int capabilityNotSetException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int clip = !devPhase?NO_DEBUG:NO_DEBUG; + static final int clipRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int colorInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int coloringAttributes = !devPhase?NO_DEBUG:NO_DEBUG; + static final int coloringAttributesRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int compileState = !devPhase?NO_DEBUG:LEVEL_3; + static final int compressedGeometry = !devPhase?NO_DEBUG:NO_DEBUG; + static final int compressedGeometryHeader = !devPhase?NO_DEBUG:NO_DEBUG; + static final int compressedGeometryRenderMethod = !devPhase?NO_DEBUG:NO_DEBUG; + static final int compressedGeometryRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int coneSound = !devPhase?NO_DEBUG:NO_DEBUG; + static final int coneSoundRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int danglingReferenceException = !devPhase?NO_DEBUG:NO_DEBUG; + + static final int decalGroup = !devPhase?NO_DEBUG:NO_DEBUG; + static final int decalGroupRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int defaultRenderMethod = !devPhase?NO_DEBUG:NO_DEBUG; + static final int depthComponent = !devPhase?NO_DEBUG:NO_DEBUG; + static final int depthComponentFloat = !devPhase?NO_DEBUG:NO_DEBUG; + static final int depthComponentFloatRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int depthComponentInt = !devPhase?NO_DEBUG:NO_DEBUG; + static final int depthComponentIntRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int depthComponentNative = !devPhase?NO_DEBUG:NO_DEBUG; + static final int depthComponentNativeRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int depthComponentRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int directionalLight = !devPhase?NO_DEBUG:NO_DEBUG; + static final int directionalLightRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int displayListRenderMethod = !devPhase?NO_DEBUG:NO_DEBUG; + static final int distanceLOD = !devPhase?NO_DEBUG:NO_DEBUG; + static final int environmentSet = !devPhase?NO_DEBUG:NO_DEBUG; + static final int eventCatcher = !devPhase?NO_DEBUG:NO_DEBUG; + static final int exponentialFog = !devPhase?NO_DEBUG:NO_DEBUG; + static final int exponentialFogRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int fog = !devPhase?NO_DEBUG:NO_DEBUG; + static final int fogRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int font3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int fontExtrusion = !devPhase?NO_DEBUG:NO_DEBUG; + static final int generalizedStrip = !devPhase?NO_DEBUG:NO_DEBUG; + static final int generalizedStripFlags = !devPhase?NO_DEBUG:NO_DEBUG; + static final int generalizedVertexList = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometry = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryAtom = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryDecompressor = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryDecompressorRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryDecompressorShape3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryLock = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryLockInterface = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryStripArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryStripArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryStructure = !devPhase?NO_DEBUG:NO_DEBUG; + static final int geometryUpdater = !devPhase?NO_DEBUG:NO_DEBUG; + static final int graphicsConfigTemplate3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int graphicsContext3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int group = !devPhase?NO_DEBUG:NO_DEBUG; + static final int groupRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int hashKey = !devPhase?NO_DEBUG:NO_DEBUG; + static final int hiResCoord = !devPhase?NO_DEBUG:NO_DEBUG; + static final int illegalRenderingStateException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int illegalSharingException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int imageComponent = !devPhase?NO_DEBUG:NO_DEBUG; + static final int imageComponent2D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int imageComponent2DRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int imageComponent3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int imageComponent3DRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int imageComponentRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedGeometryArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedGeometryArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedGeometryStripArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedGeometryStripArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedLineArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedLineArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedLineStripArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedLineStripArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedPointArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedPointArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedQuadArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedQuadArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedTriangleArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedTriangleArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedTriangleFanArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedTriangleFanArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedTriangleStripArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int indexedTriangleStripArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int inputDevice = !devPhase?NO_DEBUG:NO_DEBUG; + static final int inputDeviceBlockingThread = !devPhase?NO_DEBUG:NO_DEBUG; + static final int inputDeviceScheduler = !devPhase?NO_DEBUG:NO_DEBUG; + static final int interpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dDataInputStream = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dDataOutputStream = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dDebug = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dI18N = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dMessage = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dNodeTable = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dQueryProps = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dStructure = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dThread = !devPhase?NO_DEBUG:NO_DEBUG; + static final int j3dThreadData = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lOD = !devPhase?NO_DEBUG:NO_DEBUG; + static final int leaf = !devPhase?NO_DEBUG:NO_DEBUG; + static final int leafRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int light = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lightBin = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lightRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lightSet = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lineArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lineArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lineAttributes = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lineAttributesRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lineStripArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int lineStripArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int linearFog = !devPhase?NO_DEBUG:NO_DEBUG; + static final int linearFogRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int link = !devPhase?NO_DEBUG:NO_DEBUG; + static final int linkRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int locale = !devPhase?NO_DEBUG:NO_DEBUG; + static final int mRSWLock = !devPhase?NO_DEBUG:NO_DEBUG; + static final int masterControl = !devPhase?NO_DEBUG:NO_DEBUG; + static final int material = !devPhase?NO_DEBUG:NO_DEBUG; + static final int materialRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int mediaContainer = !devPhase?NO_DEBUG:NO_DEBUG; + static final int mediaContainerRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int modelClip = !devPhase?NO_DEBUG:NO_DEBUG; + static final int modelClipRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int morph = !devPhase?NO_DEBUG:NO_DEBUG; + static final int morphRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int multipleParentException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int node = !devPhase?NO_DEBUG:NO_DEBUG; + static final int nodeComponent = !devPhase?NO_DEBUG:NO_DEBUG; + static final int nodeComponentRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int nodeReferenceTable = !devPhase?NO_DEBUG:NO_DEBUG; + static final int nodeRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int objectUpdate = !devPhase?NO_DEBUG:NO_DEBUG; + static final int orderedBin = !devPhase?NO_DEBUG:NO_DEBUG; + static final int orderedCollection = !devPhase?NO_DEBUG:NO_DEBUG; + static final int orderedGroup = !devPhase?NO_DEBUG:NO_DEBUG; + static final int orderedGroupRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pathInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int physicalBody = !devPhase?NO_DEBUG:NO_DEBUG; + static final int physicalEnvironment = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pickBounds = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pickCone = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pickCylinderRay = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pickCylinderSegment = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pickPoint = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pickRay = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pickSegment = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pickShape = !devPhase?NO_DEBUG:NO_DEBUG; + static final int picking = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pointArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pointArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pointAttributes = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pointAttributesRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pointLight = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pointLightRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pointSound = !devPhase?NO_DEBUG:NO_DEBUG; + static final int pointSoundRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int polygonAttributes = !devPhase?NO_DEBUG:NO_DEBUG; + static final int polygonAttributesRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int positionInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int positionPathInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int quadArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int quadArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int raster = !devPhase?NO_DEBUG:NO_DEBUG; + static final int rasterRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderAtom = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderBin = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderBinLock = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderMethod = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderMolecule = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderer = !devPhase?NO_DEBUG:NO_DEBUG; + static final int rendererStructure = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderingAttributes = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderingAttributesRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderingAttributesStructure = !devPhase?NO_DEBUG:NO_DEBUG; + static final int renderingEnvironmentStructure = !devPhase?NO_DEBUG:NO_DEBUG; + static final int restrictedAccessException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int rotPosPathInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int rotPosScalePathInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int rotationInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int rotationPathInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int scaleInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sceneGraphCycleException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sceneGraphObject = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sceneGraphObjectRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sceneGraphPath = !devPhase?NO_DEBUG:NO_DEBUG; + static final int screen3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int screenViewCache = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sensor = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sensorRead = !devPhase?NO_DEBUG:NO_DEBUG; + static final int setLiveState = !devPhase?NO_DEBUG:NO_DEBUG; + static final int shape3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int shape3DRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sharedGroup = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sharedGroupRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int sound = !devPhase?NO_DEBUG:NO_DEBUG; + static final int soundException = !devPhase?NO_DEBUG:NO_DEBUG; + static final int soundRenderer = !devPhase?NO_DEBUG:NO_DEBUG; + static final int soundRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int soundScheduler = !devPhase?NO_DEBUG:NO_DEBUG; + static final int soundStructure = !devPhase?NO_DEBUG:NO_DEBUG; + static final int soundscape = !devPhase?NO_DEBUG:NO_DEBUG; + static final int soundscapeRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int spotLight = !devPhase?NO_DEBUG:NO_DEBUG; + static final int spotLightRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int structureUpdateThread = !devPhase?NO_DEBUG:NO_DEBUG; + + // switch is a reserved word. + static final int Switch = !devPhase?NO_DEBUG:NO_DEBUG; + static final int switchRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int switchValueInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int table = !devPhase?NO_DEBUG:NO_DEBUG; + static final int texCoordGeneration = !devPhase?NO_DEBUG:NO_DEBUG; + static final int texCoordGenerationRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int text3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int text3DRenderMethod = !devPhase?NO_DEBUG:NO_DEBUG; + static final int text3DRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int texture = !devPhase?NO_DEBUG:NO_DEBUG; + static final int texture2D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int texture2DRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int texture3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int texture3DRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int textureAttributes = !devPhase?NO_DEBUG:NO_DEBUG; + static final int textureAttributesRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int textureBin = !devPhase?NO_DEBUG:NO_DEBUG; + static final int textureRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int textureSetting = !devPhase?NO_DEBUG:NO_DEBUG; + static final int timerThread = !devPhase?NO_DEBUG:NO_DEBUG; + static final int transform3D = !devPhase?NO_DEBUG:NO_DEBUG; + static final int transformGroup = !devPhase?NO_DEBUG:NO_DEBUG; + static final int transformGroupRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int transformStructure = !devPhase?NO_DEBUG:J3dDebug.LEVEL_3; + static final int transparencyAttributes = !devPhase?NO_DEBUG:NO_DEBUG; + static final int transparencyAttributesRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int transparencyInterpolator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int triangleArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int triangleArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int triangleFanArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int triangleFanArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int triangleStripArray = !devPhase?NO_DEBUG:NO_DEBUG; + static final int triangleStripArrayRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int unorderList = !devPhase?NO_DEBUG:NO_DEBUG; + static final int vertexArrayRenderMethod = !devPhase?NO_DEBUG:NO_DEBUG; + static final int view = !devPhase?NO_DEBUG:NO_DEBUG; + static final int viewCache = !devPhase?NO_DEBUG:NO_DEBUG; + static final int viewPlatform = !devPhase?NO_DEBUG:NO_DEBUG; + static final int viewPlatformRetained = !devPhase?NO_DEBUG:NO_DEBUG; + static final int virtualUniverse = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupAnd = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupAndOfOrs = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupCondition = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupCriteriaEnumerator = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupCriterion = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnAWTEvent = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnActivation = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnBehaviorPost = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnCollisionEntry = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnCollisionExit = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnCollisionMovement = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnDeactivation = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnElapsedFrames = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnElapsedTime = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnElapsedTimeHeap = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnSensorEntry = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnSensorExit = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnTransformChange = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnViewPlatformEntry = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOnViewPlatformExit = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOr = !devPhase?NO_DEBUG:NO_DEBUG; + static final int wakeupOrOfAnds = !devPhase?NO_DEBUG:NO_DEBUG; + + + static boolean doDebug(int j3dClassLevel, int level, String str) { + if(j3dClassLevel >= level) { + System.out.print(str); + return true; + } + return false; + } + + static boolean doDebug(int j3dClassLevel, int level) { + if(j3dClassLevel >= level) { + return true; + } + return false; + } + + static void doAssert(boolean expr, String str) { + if (! expr) { + throw new AssertionFailureException("(" + str + ")" + "is false"); + } + } + + static void pkgInfo(ClassLoader classLoader, + String pkgName, + String className) { + + try { + classLoader.loadClass(pkgName + "." + className); + + Package p = Package.getPackage(pkgName); + if (p == null) { + System.out.println("WARNING: Package.getPackage(" + + pkgName + + ") is null"); + } + else { + if(devPhase && debug) { + System.out.println(p); + System.out.println("Specification Title = " + + p.getSpecificationTitle()); + System.out.println("Specification Vendor = " + + p.getSpecificationVendor()); + System.out.println("Specification Version = " + + p.getSpecificationVersion()); + System.out.println("Implementation Vendor = " + + p.getImplementationVendor()); + System.out.println("Implementation Version = " + + p.getImplementationVersion()); + } + else if(devPhase) + System.out.println(", Java 3D " + p.getImplementationVersion() + "."); + } + } + catch (ClassNotFoundException e) { + System.out.println("Unable to load " + pkgName); + } + + // System.out.println(); + } + + + static { + // initialize the debug flag + debug = false; + } + +} + diff --git a/src/classes/share/javax/media/j3d/J3dI18N.java b/src/classes/share/javax/media/j3d/J3dI18N.java new file mode 100644 index 0000000..1d62767 --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3dI18N.java @@ -0,0 +1,31 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.io.*; +import java.util.*; + + +class J3dI18N { + static String getString(String key) { + String s; + try { + s = (String) ResourceBundle.getBundle("javax.media.j3d.ExceptionStrings").getString(key); + } + catch (MissingResourceException e) { + System.err.println("J3dI18N: Error looking up: " + key); + s = key; + } + return s; + } +} diff --git a/src/classes/share/javax/media/j3d/J3dMessage.java b/src/classes/share/javax/media/j3d/J3dMessage.java new file mode 100644 index 0000000..586c23a --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3dMessage.java @@ -0,0 +1,165 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The J3dMessage is the super class of all messages in Java 3D. It implements + * all of the common data members needed. + */ + +class J3dMessage extends Object { + /** + * The various message types. + */ + static final int INVALID_TYPE = -1; + static final int INSERT_NODES = 0; + static final int REMOVE_NODES = 1; + static final int RUN = 2; + static final int TRANSFORM_CHANGED = 3; + static final int UPDATE_VIEW = 4; + static final int STOP_THREAD = 5; + static final int COLORINGATTRIBUTES_CHANGED = 6; + static final int LINEATTRIBUTES_CHANGED = 7; + static final int POINTATTRIBUTES_CHANGED = 8; + static final int POLYGONATTRIBUTES_CHANGED = 9; + static final int RENDERINGATTRIBUTES_CHANGED = 10; + static final int TEXTUREATTRIBUTES_CHANGED = 11; + static final int TRANSPARENCYATTRIBUTES_CHANGED = 12; + static final int MATERIAL_CHANGED = 13; + static final int TEXCOORDGENERATION_CHANGED = 14; + static final int TEXTURE_CHANGED = 15; + static final int MORPH_CHANGED = 16; + static final int GEOMETRY_CHANGED = 17; + static final int APPEARANCE_CHANGED = 18; + static final int LIGHT_CHANGED = 19; + static final int BACKGROUND_CHANGED = 20; + static final int CLIP_CHANGED = 21; + static final int FOG_CHANGED = 22; + static final int BOUNDINGLEAF_CHANGED = 23; + static final int SHAPE3D_CHANGED = 24; + static final int TEXT3D_TRANSFORM_CHANGED = 25; + static final int TEXT3D_DATA_CHANGED = 26; + static final int SWITCH_CHANGED = 27; + static final int COND_MET = 28; + static final int BEHAVIOR_ENABLE = 29; + static final int BEHAVIOR_DISABLE = 30; + static final int INSERT_RENDERATOMS = 31; + static final int ORDERED_GROUP_INSERTED = 32; + static final int ORDERED_GROUP_REMOVED = 33; + static final int COLLISION_BOUND_CHANGED = 34; + static final int REGION_BOUND_CHANGED = 35; + static final int MODELCLIP_CHANGED = 36; + static final int BOUNDS_AUTO_COMPUTE_CHANGED = 37; + static final int SOUND_ATTRIB_CHANGED = 38; + static final int AURALATTRIBUTES_CHANGED = 39; + static final int SOUNDSCAPE_CHANGED = 40; + static final int ALTERNATEAPPEARANCE_CHANGED = 41; + static final int RENDER_OFFSCREEN = 42; + static final int RENDER_RETAINED = 43; + static final int RENDER_IMMEDIATE = 44; + static final int SOUND_STATE_CHANGED = 45; + static final int ORIENTEDSHAPE3D_CHANGED = 46; + static final int TEXTURE_UNIT_STATE_CHANGED = 47; + static final int UPDATE_VIEWPLATFORM = 48; + static final int BEHAVIOR_ACTIVATE = 49; + static final int GEOMETRYARRAY_CHANGED = 50; + static final int MEDIA_CONTAINER_CHANGED = 51; + static final int RESIZE_CANVAS = 52; + static final int TOGGLE_CANVAS = 53; + static final int IMAGE_COMPONENT_CHANGED = 54; + static final int SCHEDULING_INTERVAL_CHANGED = 55; + static final int VIEWSPECIFICGROUP_CHANGED = 56; + static final int VIEWSPECIFICGROUP_INIT = 57; + static final int VIEWSPECIFICGROUP_CLEAR = 58; + static final int ORDERED_GROUP_TABLE_CHANGED = 59; + static final int BEHAVIOR_REEVALUATE = 60; + + /** + * This is the time snapshot at which this change occured + */ + long time = -1; + + /** + * This is the number of references to this message + */ + private int refcount = 0; + + /** + * This is a bitmask of the types of threads that need to be run + * once this message is consumed. + */ + int threads = 0; + + /** + * The universe that this message originated from + */ + VirtualUniverse universe; + + /** + * This holds the type of this message + */ + int type = -1; + + /** + * This holds the target view of this message, null means all views + */ + View view = null; + + + /** + * The arguements for a message, 5 for now + */ + static final int MAX_ARGS = 6; + + Object[] args = new Object[MAX_ARGS]; + + /** + * This constructor does nothing + */ + J3dMessage() { + } + + final synchronized void clear() { + // System.out.println("J3dMessage : " + this ); + view = null; + universe = null; + args[0] = null; + args[1] = null; + args[2] = null; + args[3] = null; + args[4] = null; + args[5] = null; + } + + /** + * This increments the reference count for this message + */ + final synchronized void incRefcount() { + refcount++; + } + + /** + * This decrements the reference count for this message. If it goes + * to 0, the message is put on the MasterControl freelist. + */ + final synchronized void decRefcount() { + if (--refcount == 0) { + clear(); + FreeListManager.freeObject(FreeListManager.MESSAGE, this); + } + } + + final synchronized int getRefcount() { + return refcount; + } +} diff --git a/src/classes/share/javax/media/j3d/J3dNodeTable.java b/src/classes/share/javax/media/j3d/J3dNodeTable.java new file mode 100644 index 0000000..ee38669 --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3dNodeTable.java @@ -0,0 +1,290 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.io.DataOutputStream; +import java.io.OutputStream; +import java.util.Hashtable; +import javax.vecmath.Color3f; +import javax.vecmath.Tuple3f; + +/** + * The J3dNodeTable object provides utilities for the save/load + * methods in the Java3d nodes. Specifically, it holds an enumerated + * list of the Java3D node types and their respective Class names. + * It keeps these lists in a Hashtable and an array and allows + * other classes to get an enumerated value associated with an Object + * type or an instance of an Object associated with an enumerated value. + * + */ +class J3dNodeTable { + + // nodeTable holds the enumerated value/Classname pairs. This is + // used to look up enumerated values given a Class name. + Hashtable nodeTable = new Hashtable(); + + // nodeArray holds the value/Classname pairs also, but allows lookups + // in the other direction (given a value, what's the Class name?) + String nodeArray[]; + + // Following is a list of the current scene graph objects of java3D. + // In order to make later node insertion easier, and to add some logic to + // this arbitrary list, the groups of nodes are grouped in terms of + // similar functionality of node types. + + // Maximum number of nodes + static final int MAX_NUM_NODES = 200; + + // Nothing occupies slot 0 - thus we can return 0 from this class' + // methods to denote failure. + static final int NOTHING = 0; + + // 1 - 9: Groups + static final int GROUP = 1; + static final int TRANSFORM_GROUP = 2; + static final int SWITCH_GROUP = 3; + static final int ORDERED_GROUP = 4; + static final int BRANCH_GROUP = 5; + static final int ENDGROUP = 9; // denotes done with group + + // 10 - 19: Shape3D (in a class by itself) + static final int SHAPE3D = 10; + + // 20 - 49: Appearance and all of its attributes + static final int APPEARANCE = 20; + static final int MATERIAL = 21; + static final int TEXTURE = 22; + static final int TEX_COORD_GENERATION = 23; + static final int TEXTURE_ATTRIBUTES = 24; + static final int COLORING_ATTRIBUTES = 25; + static final int TRANSPARENCY_ATTRIBUTES = 26; + static final int RENDERING_ATTRIBUTES = 27; + static final int POLYGON_ATTRIBUTES = 28; + static final int LINE_ATTRIBUTES = 29; + static final int POINT_ATTRIBUTES = 30; + static final int TEXTURE_2D = 31; + static final int TEXTURE_3D = 32; + static final int IMAGE_COMPONENT = 33; + static final int IMAGE_COMPONENT_2D = 34; + static final int IMAGE_COMPONENT_3D = 35; + static final int ENDAPPEARANCE = 49; + + // 100 - 149: All Geometry types + static final int GEOMETRY = 100; + static final int COMPRESSED_GEOMETRY = 101; + static final int GEOMETRY_ARRAY = 102; + static final int GEOMETRY_STRIP_ARRAY = 103; + static final int INDEXED_GEOMETRY_ARRAY = 104; + static final int INDEXED_GEOMETRY_STRIP_ARRAY = 105; + static final int INDEXED_LINE_ARRAY = 106; + static final int INDEXED_LINE_STRIP_ARRAY = 107; + static final int INDEXED_POINT_ARRAY = 108; + static final int INDEXED_QUAD_ARRAY = 109; + static final int INDEXED_TRIANGLE_ARRAY = 110; + static final int INDEXED_TRIANGLE_FAN_ARRAY = 111; + static final int INDEXED_TRIANGLE_STRIP_ARRAY = 112; + static final int LINE_ARRAY = 113; + static final int LINE_STRIP_ARRAY = 114; + static final int POINT_ARRAY = 115; + static final int QUAD_ARRAY = 116; + static final int TRIANGLE_ARRAY = 117; + static final int TRIANGLE_FAN_ARRAY = 118; + static final int TRIANGLE_STRIP_ARRAY = 119; + static final int BACKGROUND_SOUND = 120; + static final int POINT_SOUND = 121; + static final int CONE_SOUND = 122; + static final int MEDIA_CONTAINER = 123; + + // 150 - 169: Behaviors + static final int ROTATION_INTERPOLATOR = 150; + static final int ROTPOSSCALEPATH_INTERPOLATOR = 151; + static final int ROTATIONPATH_INTERPOLATOR = 152; + static final int POSITIONPATH_INTERPOLATOR = 153; + static final int ROTPOSPATH_INTERPOLATOR = 154; + static final int POSITION_INTERPOLATOR = 155; + static final int SWITCHVALUE_INTERPOLATOR = 156; + static final int COLOR_INTERPOLATOR = 157; + static final int SCALE_INTERPOLATOR = 158; + // Problem: these next two are non j3d-core items + static final int SOUND_PLAYER = 159; + static final int SOUND_FADER = 160; + + // 170 - 189: Various utility nodes + static final int BOUNDS = 170; + static final int BOUNDING_SPHERE = 171; + static final int BOUNDING_BOX = 172; + static final int BOUNDING_POLYTOPE = 173; + static final int TRANSFORM3D = 180; + static final int BACKGROUND = 181; + + // 190 - 199: Lights + static final int LIGHT = 190; + static final int POINT_LIGHT = 191; + static final int SPOT_LIGHT = 192; + static final int DIRECTIONAL_LIGHT = 193; + static final int AMBIENT_LIGHT = 194; + + + /** + * Constructs this Object, which initializes the array and Hashtable + */ + J3dNodeTable() { + nodeArray = new String[MAX_NUM_NODES]; + //Initialize all table entries to null + for (int i = 0; i < MAX_NUM_NODES; ++i) + nodeArray[i] = null; + // Setup slots of nodeArray where there should be valid values + nodeArray[GROUP] = "Group"; + nodeArray[TRANSFORM_GROUP] = "TransformGroup"; + nodeArray[SWITCH_GROUP] = "Switch"; + nodeArray[ORDERED_GROUP] = "OrderedGroup"; + nodeArray[BRANCH_GROUP] = "BranchGroup"; + + nodeArray[SHAPE3D] = "Shape3D"; + + nodeArray[APPEARANCE] = "Appearance"; + nodeArray[MATERIAL] = "Material"; + nodeArray[TEXTURE] = "Texture"; + nodeArray[TEXTURE_2D] = "Texture2D"; + nodeArray[TEXTURE_3D] = "Texture3D"; + nodeArray[IMAGE_COMPONENT] = "ImageComponent"; + nodeArray[IMAGE_COMPONENT_2D] = "ImageComponent2D"; + nodeArray[IMAGE_COMPONENT_3D] = "ImageComponent3D"; + nodeArray[TRANSPARENCY_ATTRIBUTES] = "TransparencyAttributes"; + + nodeArray[GEOMETRY] = "Geometry"; + nodeArray[COMPRESSED_GEOMETRY] = "CompressedGeometry"; + nodeArray[GEOMETRY_ARRAY] = "GeometryArray"; + nodeArray[GEOMETRY_STRIP_ARRAY] = "GeometryStripArray"; + nodeArray[INDEXED_GEOMETRY_ARRAY] = "IndexedGeometryArray"; + nodeArray[INDEXED_GEOMETRY_STRIP_ARRAY] = "IndexedGeometryStripArray"; + nodeArray[INDEXED_LINE_ARRAY] = "IndexedLineArray"; + nodeArray[INDEXED_LINE_STRIP_ARRAY] = "IndexedLineStripArray"; + nodeArray[INDEXED_POINT_ARRAY] = "IndexedPointArray"; + nodeArray[INDEXED_QUAD_ARRAY] = "IndexedQuadArray"; + nodeArray[INDEXED_TRIANGLE_ARRAY] = "IndexedTriangleArray"; + nodeArray[INDEXED_TRIANGLE_FAN_ARRAY] = "IndexedTriangleFanArray"; + nodeArray[INDEXED_TRIANGLE_STRIP_ARRAY] = "indexedTriangleStripArray"; + nodeArray[LINE_ARRAY] = "LineArray"; + nodeArray[LINE_STRIP_ARRAY] = "LineStripArray"; + nodeArray[POINT_ARRAY] = "PointArray"; + nodeArray[QUAD_ARRAY] = "QuadArray"; + nodeArray[TRIANGLE_ARRAY] = "TriangleArray"; + nodeArray[TRIANGLE_FAN_ARRAY] = "TriangleFanArray"; + nodeArray[TRIANGLE_STRIP_ARRAY] = "TriangleStripArray"; + nodeArray[BACKGROUND_SOUND] = "BackgroundSound"; + nodeArray[POINT_SOUND] = "PointSound"; + nodeArray[CONE_SOUND] = "ConeSound"; + nodeArray[MEDIA_CONTAINER] = "MediaContainer"; + + nodeArray[ROTATION_INTERPOLATOR] = "RotationInterpolator"; + nodeArray[ROTPOSSCALEPATH_INTERPOLATOR] = "RotPosScalePathInterpolator"; + nodeArray[ROTATIONPATH_INTERPOLATOR] = "RotationPathInterpolator"; + nodeArray[POSITIONPATH_INTERPOLATOR] = "PositionPathInterpolator"; + nodeArray[ROTPOSPATH_INTERPOLATOR] = "RotPosPathInterpolator"; + nodeArray[POSITION_INTERPOLATOR] = "PositionInterpolator"; + nodeArray[SWITCHVALUE_INTERPOLATOR] = "SwitchValueInterpolator"; + nodeArray[COLOR_INTERPOLATOR] = "ColorInterpolator"; + nodeArray[SCALE_INTERPOLATOR] = "ScaleInterpolator"; + nodeArray[SOUND_PLAYER] = "SoundPlayer"; + nodeArray[SOUND_FADER] = "SoundFader"; + + nodeArray[BOUNDS] = "Bounds"; + nodeArray[BOUNDING_SPHERE] = "BoundingSphere"; + nodeArray[BOUNDING_BOX] = "BoundingBox"; + nodeArray[BOUNDING_POLYTOPE] = "BoundingPolytope"; + nodeArray[TRANSFORM3D] = "Transform3D"; + nodeArray[BACKGROUND] = "Background"; + + nodeArray[LIGHT] = "Light"; + nodeArray[POINT_LIGHT] = "PointLight"; + nodeArray[SPOT_LIGHT] = "SpotLight"; + nodeArray[DIRECTIONAL_LIGHT] = "DirectionalLight"; + nodeArray[AMBIENT_LIGHT] = "AmbientLight"; + + for (int i = 0; i < MAX_NUM_NODES; ++i) { + if (nodeArray[i] != null) + nodeTable.put(nodeArray[i], new Integer(i)); + } + + } + + /** + * Returns the enumerated value associated with an Object. This + * method retrieves the base class name (with no package name and + * with no "Retained" portion (if it's part of the Object's name); + * we're just looking for the base Java3d node type here. + */ + int getNodeValue(Object object) { + Integer i; + String fullName = object.getClass().getName(); + int firstIndex; + int lastIndex ; + if ((firstIndex = fullName.lastIndexOf(".")) == -1) + firstIndex = 0; + else + firstIndex++; + if ((lastIndex = fullName.lastIndexOf("Retained")) == -1) + lastIndex = fullName.length(); + String nodeName = fullName.substring(firstIndex, lastIndex); + if ((i = (Integer)nodeTable.get(nodeName)) + != null) { + return i.intValue(); + } + else { + // This conditional exists because if a group node is + // actually a subclass of some standard Java3d Group type + // (e.g., VrmlParser), then we'll just save it and reload + // it as a Group. + if (object instanceof TransformGroup || + object instanceof TransformGroupRetained) + return TRANSFORM_GROUP; + else if (object instanceof BranchGroup || + object instanceof BranchGroupRetained) + return BRANCH_GROUP; + else if (object instanceof Switch || + object instanceof SwitchRetained) + return SWITCH_GROUP; + else if (object instanceof Group || + object instanceof GroupRetained) + return GROUP; + else if (object instanceof Shape3D) + return SHAPE3D; // For Text2D object in particular + else { + System.err.println("Warning: Don't know how to save object of type " + object); + return 0; + } + } + } + + /** + * Returns new instance of an object with the Class name + * associated with the given enumerated value. + */ + Object getObject(int nodeValue) { + try { + if (nodeArray[nodeValue] != null) { + String nodeName = "javax.media.j3d." + nodeArray[nodeValue]; + return Class.forName(nodeName).newInstance(); + } + } + catch (Exception e) { + throw new RuntimeException("Exception creating object for nodeValue " + + nodeValue + "\n nodeName = javax.media.j3d." + + nodeArray[nodeValue]); + } + return null; + } + + +} diff --git a/src/classes/share/javax/media/j3d/J3dQueryProps.java b/src/classes/share/javax/media/j3d/J3dQueryProps.java new file mode 100644 index 0000000..9b3a4c3 --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3dQueryProps.java @@ -0,0 +1,110 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + + +/** + * Properties object for query operations. It is a read-only Map backed + * up by a Hashtable. + */ +class J3dQueryProps extends AbstractMap { + private Hashtable table; + private Set entrySet = null; + + + /** + * Constructs a new J3dQueryProps object using the specified + * array of keys and the specified values. The arrays must be + * the same size. + */ + J3dQueryProps(String[] keys, Object[] values) { + table = new Hashtable(); + for (int i = 0; i < keys.length; i++) { + table.put(keys[i], values[i]); + } + } + + /** + * Gets value corresponding to specified key + */ + public Object get(Object key) { + return table.get(key); + } + + /** + * Returns true if the specified key is contained in this Map + */ + public boolean containsKey(Object key) { + return table.containsKey(key); + } + + /** + * Returns true if the specified value is contained in this Map + */ + public boolean containsValue(Object value) { + return table.containsValue(value); + } + + /** + * Returns a new Set object for the entries of this map + */ + public Set entrySet() { + if (entrySet == null) + entrySet = new EntrySet(); + + return entrySet; + } + + + /** + * Entry set class + */ + private class EntrySet extends AbstractSet { + private EntrySet() { + } + + public int size() { + return table.size(); + } + + public Iterator iterator() { + return new MapIterator(); + } + } + + + /** + * Entry set class + */ + private class MapIterator implements Iterator { + private Iterator i; + + private MapIterator() { + i = table.entrySet().iterator(); + } + + public boolean hasNext() { + return i.hasNext(); + } + + public Object next() { + return i.next(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } +} diff --git a/src/classes/share/javax/media/j3d/J3dStructure.java b/src/classes/share/javax/media/j3d/J3dStructure.java new file mode 100644 index 0000000..7c4faab --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3dStructure.java @@ -0,0 +1,153 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * The J3dStructure is the super class of all structures in Java 3D. + * A structure is a object that organizes a collection of objects. + */ + +abstract class J3dStructure extends Object { + /** + * This is the list of messages to be processed by this structure + */ + UnorderList messageList = new UnorderList(5, J3dMessage.class); + + /** + * This is the update Thread for this structure + */ + + StructureUpdateThread updateThread = null; + + /** + * This is the type of update thread + */ + int threadType = -1; + + /** + * The universe of this structure + */ + VirtualUniverse universe = null; + + /** + * The thread data for the update thread + */ + J3dThreadData threadData = new J3dThreadData(); + + /** + * number of messages for this snapshot of time + */ + int nMessage = 0; + J3dMessage[] msgList = new J3dMessage[5]; + + /** + * This constructor does nothing + */ + J3dStructure(VirtualUniverse u, int type) { + universe = u; + threadType = type; + threadData.threadType = type; + } + + /** + * This returns the thread data for this thread. + */ + final J3dThreadData getUpdateThreadData() { + return (threadData); + } + + /** + * This adds a message to the list of messages for this structure + */ + final void addMessage(J3dMessage message) { + + if (threadData != null) { + threadData.lastUpdateTime = message.time; + } else { + // this force message to consume when initialized + message.time = -1; + } + message.incRefcount(); + messageList.add(message); + } + + + /** + * This returns whether or not there are any pending messages + */ + final J3dMessage[] getMessages(long referenceTime) { + int sz, n = 0; + + synchronized (messageList) { + if ((sz = messageList.size()) > 0) { + J3dMessage mess[] = (J3dMessage []) messageList.toArray(false); + for (n = 0; n < sz; n++) { + if (mess[n].time > referenceTime) { + break; + } + } + if (n > 0) { + if (msgList.length < n) { + msgList = new J3dMessage[n]; + } + messageList.shift(msgList, n); + } + } + } + + nMessage = n; + return msgList; + } + + final void clearMessages() { + synchronized (messageList) { + int nmessage = messageList.size(); + if (nmessage > 0) { + J3dMessage mess[] = (J3dMessage []) messageList.toArray(false); + for (int i = nmessage-1; i >=0; i--) { + mess[i].decRefcount(); + } + messageList.clear(); + } + nMessage = 0; + msgList = new J3dMessage[5]; + } + + } + + int getNumMessage() { + return nMessage; + } + + /** + * This gets overriden by the structure + */ + abstract void processMessages(long referenceTime); + + /** + * This is used by MasterControl to process any unused message + * for final cleanup. DON'T decrememt message count in + * the method, as it is done by MasterControl. + */ + abstract void removeNodes(J3dMessage m); + + /** + * Release resource associate with this structure before GC + * We need to clear all those IndexedUnorderSet/WakeupIndexedList + * so that the listIdx associate with IndexedObject reset to -1. + */ + abstract void cleanup(); +} diff --git a/src/classes/share/javax/media/j3d/J3dThread.java b/src/classes/share/javax/media/j3d/J3dThread.java new file mode 100644 index 0000000..4e94e5e --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3dThread.java @@ -0,0 +1,316 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The J3dThread is the super class of all slave threads in Java 3D. It implements + * all of the common flow control constructs. + */ + +abstract class J3dThread extends Thread { + /** + * These are the thread types that a message may affect + */ + static final int BEHAVIOR_SCHEDULER = 0x01; + static final int SOUND_SCHEDULER = 0x02; + static final int INPUT_DEVICE_SCHEDULER = 0x04; + static final int RENDER_THREAD = 0x10; +// static final int COLLISION_THREAD = 0x20; + static final int UPDATE_GEOMETRY = 0x40; + static final int UPDATE_RENDER = 0x80; + static final int UPDATE_BEHAVIOR = 0x100; + static final int UPDATE_SOUND = 0x200; + static final int UPDATE_RENDERING_ATTRIBUTES = 0x400; + static final int UPDATE_RENDERING_ENVIRONMENT = 0x1000; + static final int UPDATE_TRANSFORM = 0x2000; + + /** + * The classification types. + */ + static final int WORK_THREAD = 0x01; + static final int UPDATE_THREAD = 0x02; + + /** + * This runMonitor action puts the thread into an initial wait state + */ + static final int WAIT = 0; + + /** + * This runMonitor action notifies MasterControl that this thread + * has completed and wait. + */ + static final int NOTIFY_AND_WAIT = 1; + + /** + * This is used by Canvas3D Renderer to notify user thread + * that swap is completed. + */ + static final int NOTIFY = 2; + + /** + * This runMonitor action tells the thread to run N number of + * iterations. + */ + static final int RUN = 2; + + /** + * This runMonitor action tells the thread to stop running + */ + static final int STOP = 3; + + /** + * This indicates that this thread has been activated by MC + */ + boolean active = false; + + /** + * This indicates that this thread is alive and running + */ + private boolean running = true; + + /** + * The thread data for this thread + */ + private J3dThreadData[] data = null; + + /** + * This indicates that this thread is ready + */ + private volatile boolean started = false; + + /** + * The time values passed into this thread + */ + long referenceTime; + + /** + * Use to assign threadOpts WAIT_ALL_THREADS + */ + long lastWaitTimestamp = 0; + + /** + * The type of this thread. It is one of the above constants. + */ + int type; + + /** + * The classification of this thread. It is one of the above constants. + */ + int classification = WORK_THREAD; + + /** + * The arguments passed in for this thread + */ + Object[] args = null; + + /** + * Flag to indicate that user initiate a thread stop + */ + volatile boolean userStop = false; + + /** + * Flag to indicate that this thread is waiting to be notify + */ + volatile boolean waiting = false; + + /** + * Some variables used to name threads correctly + */ + private static int numInstances = 0; + private int instanceNum = -1; + + private synchronized int newInstanceNum() { + return (++numInstances); + } + + int getInstanceNum() { + if (instanceNum == -1) + instanceNum = newInstanceNum(); + return instanceNum; + } + + /** + * This method is defined by all slave threads to implement + * one iteration of work. + */ + abstract void doWork(long referenceTime); + + /** + * This constructor simply assigns the given id. + */ + J3dThread(ThreadGroup t) { + super(t, ""); + } + + /** + * This returns the thread data for this thread. + */ + synchronized J3dThreadData getThreadData(View v, Canvas3D c) { + J3dThreadData threadData; + int i, j; + J3dThreadData[] newData; + + if (type != RENDER_THREAD) { // Regular Thread + if (data == null) { + data = new J3dThreadData[1]; + data[0] = new J3dThreadData(); + data[0].thread = this; + data[0].threadType = type; + data[0].view = null; + data[0].canvas = null; + } + threadData = data[0]; + } else { // Render thread + + // Note: each renderer has multiple thread data mappings + // for its render and swap threads + + if (data == null) { + data = new J3dThreadData[1]; + data[0] = new J3dThreadData(); + data[0].thread = this; + data[0].threadType = type; + data[0].view = v; + data[0].canvas = c; + data[0].threadArgs = new Object[4]; + threadData = data[0]; + } else { + for (i=0; i<data.length; i++) { + if (data[i].view == v && data[i].canvas == c) { + break; + } + } + if (i==data.length) { + newData = new J3dThreadData[data.length+1]; + for (j=0; j<data.length; j++) { + newData[j] = data[j]; + } + data = newData; + data[j] = new J3dThreadData(); + data[j].thread = this; + data[j].threadType = type; + data[j].view = v; + data[j].canvas = c; + data[j].threadArgs = new Object[4]; + threadData = data[j]; + } else { + threadData = data[i]; + Object args[] = (Object []) threadData.threadArgs; + args[0] = null; + args[1] = null; + args[2] = null; + args[3] = null; + } + } + + } + + return (threadData); + } + + /** + * This initializes this thread. Once this method returns, the thread is + * ready to do work. + */ + void initialize() { + this.start(); + while (!started) { + MasterControl.threadYield(); + } + } + + /** + * This causes the threads run method to exit. + */ + void finish() { + while (!waiting) { + MasterControl.threadYield(); + } + runMonitor(STOP, 0,null); + + } + + /** + * This thread controls the syncing of all the canvases attached to + * this view. + */ + public void run() { + runMonitor(WAIT, 0, null); + while (running) { + doWork(referenceTime); + runMonitor(NOTIFY_AND_WAIT, 0, null); + } + // resource clean up + shutdown(); + } + + synchronized void runMonitor(int action, long referenceTime, + Object[] args) { + switch (action) { + case WAIT: + try { + started = true; + waiting = true; + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + waiting = false; + break; + case NOTIFY_AND_WAIT: + VirtualUniverse.mc.runMonitor(MasterControl.THREAD_DONE, null, + null, null, this); + try { + waiting = true; + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + waiting = false; + break; + case RUN: + this.referenceTime = referenceTime; + this.args = args; + notify(); + break; + case STOP: + running = false; + notify(); + break; + } + } + + void cleanupView() { + // renderer will reconstruct threadData next time + // in getThreadData + data = null; + } + + // default resource clean up method + void shutdown() { + } + + void cleanup() { + active = false; + running = true; + data = null; + started = true; + lastWaitTimestamp = 0; + classification = WORK_THREAD; + args = null; + userStop = false; + referenceTime = 0; + + } + +} diff --git a/src/classes/share/javax/media/j3d/J3dThreadData.java b/src/classes/share/javax/media/j3d/J3dThreadData.java new file mode 100644 index 0000000..59f03a6 --- /dev/null +++ b/src/classes/share/javax/media/j3d/J3dThreadData.java @@ -0,0 +1,92 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The J3dThreadData is the data wrapper for threads in Java 3D. + */ + +class J3dThreadData extends Object { + /** + * Thread run options + */ + static final int WAIT_ALL_THREADS = 0x01; + static final int CONT_THREAD = 0x02; + static final int WAIT_THIS_THREAD = 0x04; + static final int START_TIMER = 0x08; + static final int STOP_TIMER = 0x10; + static final int LAST_STOP_TIMER = 0x20; + //static final int LOCK_RENDERBIN = 0x20; + //static final int RELEASE_RENDERBIN = 0x40; + + /** + * The thread for this data + */ + J3dThread thread = null; + + /** + * The last time that a message was sent to this thread. + */ + long lastUpdateTime = -1; + + /** + * The last time that this thread was run + */ + long lastRunTime = -1; + + /** + * The thread type + */ + int threadType = 0; + + /** + * The run options for this thread. + */ + int threadOpts = 0; + + /** + * The arguments to be passed to this thread + */ + Object threadArgs = null; + + /** + * This indicates whether or not this thread needs to run. + */ + boolean needsRun = false; + + /** + * The following data is only used by the Render Thread + */ + + /** + * The type of the thread invocation. RENDER or SWAP + */ + int type = 0; + + /** + * The view that this Render invocation belongs to. + */ + View view = null; + + /** + * The Canvas3D that this Render invocation belongs to. + * It is null for the SWAP invocation. + */ + Canvas3D canvas = null; + + /** + * This constructor does nothing + */ + J3dThreadData() { + } +} diff --git a/src/classes/share/javax/media/j3d/LOD.java b/src/classes/share/javax/media/j3d/LOD.java new file mode 100644 index 0000000..e3d5305 --- /dev/null +++ b/src/classes/share/javax/media/j3d/LOD.java @@ -0,0 +1,220 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; +import java.util.Vector; + +/** + * An LOD leaf node is an abstract behavior class that operates on + * a list of Switch group nodes to select one of the children of the + * Switch nodes. + * The LOD class is extended to implement various selection criteria. + */ + +public abstract class LOD extends Behavior { + + /** + * Wakeup condition for all LOD nodes + */ + WakeupOnElapsedFrames wakeupFrame = new WakeupOnElapsedFrames(0, true); + + + /** + * The LOD Node's vector of switch nodes. + */ + Vector switches = new Vector(5); + + /** + * Constructs and initializes an LOD node. + */ + public LOD() { + } + + /** + * Appends the specified switch node to this LOD's list of switches. + * @param switchNode the switch node to add to this LOD's list of switches + */ + public void addSwitch(Switch switchNode) { + switches.addElement(switchNode); + } + + /** + * Replaces the specified switch node with the switch node provided. + * @param switchNode the new switch node + * @param index which switch node to replace + */ + public void setSwitch(Switch switchNode, int index) { + Switch sw = getSwitch(index); + switches.setElementAt(switchNode, index); + } + + /** + * Inserts the specified switch node at specified index. + * @param switchNode the new switch node + * @param index position to insert new switch node at + */ + public void insertSwitch(Switch switchNode, int index) { + switches.insertElementAt(switchNode, index); + } + + /** + * Removes the switch node at specified index. + * @param index which switch node to remove + */ + public void removeSwitch(int index) { + Switch sw = getSwitch(index); + switches.removeElementAt(index); + } + + /** + * Returns the switch node specified by the index. + * @param index which switch node to return + * @return the switch node at location index + */ + public Switch getSwitch(int index) { + return (Switch) switches.elementAt(index); + } + + /** + * Returns the enumeration object of all switches. + * @return the enumeration object of all switches + */ + public Enumeration getAllSwitches() { + return switches.elements(); + } + + /** + * Returns a count of this LOD's switches. + * @return the number of switches controlled by this LOD + */ + public int numSwitches() { + return switches.size(); + } + + + /** + * Retrieves the index of the specified switch node in + * this LOD node's list of switches. + * + * @param switchNode the switch node to be looked up. + * @return the index of the specified switch node; + * returns -1 if the object is not in the list. + * + * @since Java 3D 1.3 + */ + public int indexOfSwitch(Switch switchNode) { + return switches.indexOf(switchNode); + } + + + /** + * Removes the specified switch node from this LOD node's + * list of switches. + * If the specified object is not in the list, the list is not modified. + * + * @param switchNode the switch node to be removed. + * + * @since Java 3D 1.3 + */ + public void removeSwitch(Switch switchNode) { + int index = switches.indexOf(switchNode); + if (index >= 0) + removeSwitch(index); + } + + + /** + * Removes all switch nodes from this LOD node. + * + * @since Java 3D 1.3 + */ + public void removeAllSwitches() { + int numSwitches = switches.size(); + + // Remove in reverse order to ensure valid indices + for (int index = numSwitches - 1; index >= 0; index--) { + removeSwitch(index); + } + } + + + /** + * Copies all LOD information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + LOD lod = (LOD) originalNode; + + int numSwitch = lod.numSwitches(); + for (int i = 0; i < numSwitch; i++) { + addSwitch(lod.getSwitch(i)); + } + } + + /** + * Callback used to allow a node to check if any nodes referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any node references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding Node in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * node is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + int numSwitch = numSwitches(); + + for (int i = 0; i < numSwitch; i++) { + Switch curSwitch = getSwitch(i); + if (curSwitch != null) { + setSwitch((Switch) + referenceTable.getNewObjectReference(curSwitch), i); + } + } + } +} diff --git a/src/classes/share/javax/media/j3d/Leaf.java b/src/classes/share/javax/media/j3d/Leaf.java new file mode 100644 index 0000000..755c51c --- /dev/null +++ b/src/classes/share/javax/media/j3d/Leaf.java @@ -0,0 +1,34 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The Leaf node is an abstract class for all scene graph nodes that + * have no children. + * Leaf nodes specify lights, geometry, and sounds. They specify special + * linking and instancing capabilities for sharing scene graphs and + * provide a view platform for positioning and orienting a view in the + * virtual world. + * <p> + * NOTE: Applications should <i>not</i> extend this class directly. + */ + +public abstract class Leaf extends Node { + + /** + * Construct and initialize the Leaf object. + */ + public Leaf(){ + } + +} diff --git a/src/classes/share/javax/media/j3d/LeafRetained.java b/src/classes/share/javax/media/j3d/LeafRetained.java new file mode 100644 index 0000000..42f00ef --- /dev/null +++ b/src/classes/share/javax/media/j3d/LeafRetained.java @@ -0,0 +1,48 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.Hashtable; + +import java.util.ArrayList; + +/** + * LeafRetained node. + */ +abstract class LeafRetained extends NodeRetained { + + SwitchState switchState = null; + + // temporary variable used during bounds computation, since + // multiple mirror shapes could be pointing to the same shape3D + boolean boundsDirty = false; + + // Appicable only to the mirror object + void updateBoundingLeaf() { + + } + protected Object clone(boolean forceDuplicate) { + return super.clone(); + } + + void updateMirrorObject(Object[] args) { + } + + void updateTransformChange() { + } + + void updateBounds() { + } + + void getMirrorObjects(ArrayList l, HashKey k) { + } +} diff --git a/src/classes/share/javax/media/j3d/Light.java b/src/classes/share/javax/media/j3d/Light.java new file mode 100644 index 0000000..be5dee5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Light.java @@ -0,0 +1,696 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; +import java.util.Enumeration; + +/** + * The Light leaf node is an abstract class that defines a set of + * parameters common to all + * types of light. These parameters include the light color, an enable + * flag, and a region of influence in which this Light node is active. + * A Light node also contains a list of Group nodes that specifies the + * hierarchical scope of this Light. If the scope list is empty, + * the Light node has universe scope: all nodes within the region of + * influence are affected by this Light node. If the scope list is + * non-empty, only those Leaf nodes under the Group nodes in the + * scope list are affected by this Light node (subject to the + * influencing bounds). + * <p> + * The light in a scene may come from several light sources that can + * be individually defined. Some of the light in a scene may + * come from a specific direction, known as a directional light, + * from a specific position, known as a point light, or + * from no particular direction or source as with ambient light. + * <p> + * Java 3D supports an arbitrary number of lights. However, the number + * of lights that can be active within the region of influence is + * implementation-dependent and cannot be defined here. + * <p> + * <b>Light Color</b> + * <p> + * The Java 3D lighting model approximates the way light works in + * the real world. Light is defined in terms of the red, green, and + * blue components that combine to create the light color. The + * three color components represent the amount of light emitted + * by the source. + * <p> + * Each of the three colors is represented by a + * floating point value that ranges from 0.0 to 1.0. A combination + * of the three colors such as (1.0, 1.0, 1.0), representing + * the red, green, and blue color values respectively, creates a white + * light with maximum brightness. A combination such as (0.0, 0.0, + * 0.0) creates no light (black). Values between the minimum and + * maximum values of the range produce corresponding brightness + * and colors. For example, a combination of (0.5, 0.5, 0.5) + * produces a 50% grey light. A combination of (1.0, 1.0, 0.0), + * red and green but no blue, produces a yellow light. + * <p> + * If a scene has multiple lights and all lights illuminate an object, + * the effect of the light on the object is the sum of the + * lights. For example, in a scene with two lights, if the first + * light emits (R<sub>1</sub>, G<sub>1</sub>, B<sub>1</sub>) and + * the second light emits (R<sub>2</sub>, G<sub>2</sub>, + * B<sub>2</sub>), the components are added together giving + * (R<sub>1</sub>+R<sub>2</sub>, G<sub>1</sub>+G<sub>2</sub>, + * B<sub>1</sub>+B<sub>2</sub>). + * If the sums of any of the color values is greater than 1.0, + * brighter than the maximum brightness permitted, the color value is + * clamped to 1.0. + * <p> + * <b>Material Colors</b> + * <p> + * In the Java 3D lighting model, the light sources have an effect + * on the scene only when there are object surfaces to absorb or + * reflect the light. Java 3D approximates an object's color + * by calculating the percentage of red, green, and blue light + * the object reflects. An object with a surface color of pure green + * absorbs all of the red and blue light that strikes it and + * reflects all of the green light. Viewing the object in a + * white light, the green color is reflected and you see a green + * object. However, if the green object is viewed in a red light, + * all of the red light is absorbed and the object appears black. + * <p> + * The surface of each object in the scene has + * certain material properties that define how light affects its + * appearance. The object might reflect light in various ways, + * depending on the object's surface type. The object + * might even emit its own light. The Java 3D lighting model specifies + * these material properties as five independent components: emitted + * color, ambient color, diffuse color, specular color, and shininess. + * All of these properties are computed independently, then added + * together to define how the surface of the object appears under + * light (an exception is Ambient light, which does not contribute + * to specular reflection). The material properties are defined + * in the Material class. + * <p> + * <b>Influencing Bounds</b> + * <p> + * Since a scene may be quite large, as large as the universe for + * example, it is often reasonable to limit the influence of lighting + * to a region that is within viewing range. There is no reason + * to waste all that computing power on illuminating objects that + * are too far away to be viewed. In Java 3D, the influencing bounds + * is defined by a Bounds object or a BoundingLeaf object. It should + * be noted that a BoundingLeaf object overrides a Bounds object, + * should both objects be set. + * <p> + * A Bounds object represents a convex, closed volume. Bounds + * defines three different types of containing + * volumes: an axis-aligned-box volume, a spherical volume, and a + * bounding polytope. A BoundingLeaf object also specifies a region + * of influence, but allows an application to specify a bounding + * region in one coordinate system (the local coordinate system of + * the BoundingLeaf node) other than the local coordinate + * system of the node that references the bounds (the Light). + * <p> + * <b>Limiting the Scope</b> + * <p> + * In addition to limiting the lighting calculations to a given + * region of a scene, lighting can also be limited to groups of + * nodes, defined by a Group object. This is known as "scoping." + * All nodes attached to a Group node define a <i>list of scopes</i>. + * Methods in the Light class permit the setting, addition, insertion, + * removal, and enumeration of nodes in the list of scopes. + * <p> + * <b>Two-sided Lighting of Polygons</b> + * <p> + * Java 3D performs lighting calculations for all polygons, whether + * they are front-facing or back-facing. Since most polygon objects + * are constructed with the front face in mind, the back-facing + * portion may not be correctly illuminated. For example, a sphere + * with part of the face cut away so you can see its inside. + * You might want to have the inside surface lit as well as the + * outside surface and you mught also want to define a different + * Material description to reduce shininess, specular color, etc. + * <p> + * For more information, see the "Face culling" and "Back-facing + * normal flip" descriptions in the PolygonAttributes class + * description. + * <p> + * <b>Turning on the Lights</b> + * <p> + * Lighting needs to be explicitly enabled with the setEnable method + * or with the lightOn parameter in the constructor + * before any of the child light sources have any effect on illuminating + * the scene. The child lights may also be enabled or disabled individually. + * <p> + * If lighting is not enabled, the current color of an + * object in the scene is simply mapped onto the object, and none of + * the lighting equations regarding Material properties, such as ambient + * color, diffuse color, specular color, and shininess, are performed. + * However, an object's emitted color, if specified and enabled, will + * still affect that object's appearance. + * <p> + * To disable lighting, call setEnable with <code>false</code> as + * the argument. + * + * @see Material + * @see Bounds + * @see BoundingLeaf + * @see Group + * @see PolygonAttributes + */ + +public abstract class Light extends Leaf { + /** + * Specifies that this Light allows read access to its current state + * information at runtime. + */ + public static final int + ALLOW_STATE_READ = CapabilityBits.LIGHT_ALLOW_STATE_READ; + + /** + * Specifies that this Light allows write access to its current state + * information at runtime. + */ + public static final int + ALLOW_STATE_WRITE = CapabilityBits.LIGHT_ALLOW_STATE_WRITE; + + /** + * Specifies that this Light allows read access to its color + * information at runtime. + */ + public static final int + ALLOW_COLOR_READ = CapabilityBits.LIGHT_ALLOW_COLOR_READ; + + /** + * Specifies that this Light allows write access to its color + * information at runtime. + */ + public static final int + ALLOW_COLOR_WRITE = CapabilityBits.LIGHT_ALLOW_COLOR_WRITE; + + /** + * Specifies that this Light allows read access to its + * influencing bounds and bounds leaf information. + */ + public static final int + ALLOW_INFLUENCING_BOUNDS_READ = CapabilityBits.LIGHT_ALLOW_INFLUENCING_BOUNDS_READ; + + /** + * Specifies that this Light allows write access to its + * influencing bounds and bounds leaf information. + */ + public static final int + ALLOW_INFLUENCING_BOUNDS_WRITE = CapabilityBits.LIGHT_ALLOW_INFLUENCING_BOUNDS_WRITE; + + /** + * Specifies that this Light allows read access to its scope + * information at runtime. + */ + public static final int + ALLOW_SCOPE_READ = CapabilityBits.LIGHT_ALLOW_SCOPE_READ; + + /** + * Specifies that this Light allows write access to its scope + * information at runtime. + */ + public static final int + ALLOW_SCOPE_WRITE = CapabilityBits.LIGHT_ALLOW_SCOPE_WRITE; + + /** + * Constructs a Light node with default parameters. The default + * values are as follows: + * <ul> + * enable flag : true<br> + * color : white (1,1,1)<br> + * scope : empty (universe scope)<br> + * influencing bounds : null<br> + * influencing bounding leaf : null<br> + * </ul> + */ + public Light() { + } + + /** + * Constructs and initializes a Light node using the specified color. + * @param color the color of the light source + */ + public Light(Color3f color) { + ((LightRetained)this.retained).initColor(color); + } + + /** + * Constructs and initializes a Light node using the specified enable + * flag and color. + * @param lightOn flag indicating whether this light is on or off + * @param color the color of the light source + */ + public Light(boolean lightOn, Color3f color) { + ((LightRetained)this.retained).initEnable(lightOn); + ((LightRetained)this.retained).initColor(color); + } + + /** + * Turns the light on or off. + * @param state true or false to set light on or off + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setEnable(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_STATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light0")); + + if (isLive()) + ((LightRetained)this.retained).setEnable(state); + else + ((LightRetained)this.retained).initEnable(state); + } + + /** + * Retrieves this Light's current state (on/off). + * @return this node's current state (on/off) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getEnable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_STATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Light1")); + + return ((LightRetained)this.retained).getEnable(); + } + + /** + * Sets the Light's current color. + * @param color the value of this node's new color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setColor(Color3f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light2")); + + if (isLive()) + ((LightRetained)this.retained).setColor(color); + else + ((LightRetained)this.retained).initColor(color); + } + + /** + * Gets this Light's current color and places it in the parameter specified. + * @param color the vector that will receive this node's color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getColor(Color3f color) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Light3")); + + ((LightRetained)this.retained).getColor(color); + } + + /** + * Replaces the node at the specified index in this Light node's + * list of scopes with the specified Group node. + * By default, Light nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be stored at the specified index. + * @param index the index of the Group node to be replaced. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void setScope(Group scope, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light4")); + + if (isLive()) + ((LightRetained)this.retained).setScope(scope, index); + else + ((LightRetained)this.retained).initScope(scope, index); + } + + + /** + * Retrieves the Group node at the specified index from this Light node's + * list of scopes. + * @param index the index of the Group node to be returned. + * @return the Group node at the specified index. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Group getScope(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Light5")); + + return ((LightRetained)this.retained).getScope(index); + } + + + /** + * Inserts the specified Group node into this Light node's + * list of scopes at the specified index. + * By default, Light nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be inserted at the specified index. + * @param index the index at which the Group node is inserted. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void insertScope(Group scope, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light6")); + + if (isLive()) + ((LightRetained)this.retained).insertScope(scope, index); + else + ((LightRetained)this.retained).initInsertScope(scope, index); + } + + + /** + * Removes the node at the specified index from this Light node's + * list of scopes. If this operation causes the list of scopes to + * become empty, then this Light will have universe scope: all nodes + * within the region of influence will be affected by this Light node. + * @param index the index of the Group node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the group node at the + * specified index is part of a compiled scene graph + */ + public void removeScope(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light7")); + + if (isLive()) + ((LightRetained)this.retained).removeScope(index); + else + ((LightRetained)this.retained).initRemoveScope(index); + } + + + /** + * Returns an enumeration of this Light node's list of scopes. + * @return an Enumeration object containing all nodes in this Light node's + * list of scopes. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Enumeration getAllScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Light8")); + + return (Enumeration) ((LightRetained)this.retained).getAllScopes(); + } + + + /** + * Appends the specified Group node to this Light node's list of scopes. + * By default, Light nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be appended. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void addScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light9")); + + if (isLive()) + ((LightRetained)this.retained).addScope(scope); + else + ((LightRetained)this.retained).initAddScope(scope); + } + + + /** + * Returns the number of nodes in this Light node's list of scopes. + * If this number is 0, then the list of scopes is empty and this + * Light node has universe scope: all nodes within the region of + * influence are affected by this Light node. + * @return the number of nodes in this Light node's list of scopes. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int numScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Light8")); + + return ((LightRetained)this.retained).numScopes(); + } + + + /** + * Retrieves the index of the specified Group node in this + * Light node's list of scopes. + * + * @param scope the Group node to be looked up. + * @return the index of the specified Group node; + * returns -1 if the object is not in the list. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int indexOfScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Light8")); + + return ((LightRetained)this.retained).indexOfScope(scope); + } + + + /** + * Removes the specified Group node from this Light + * node's list of scopes. If the specified object is not in the + * list, the list is not modified. If this operation causes the + * list of scopes to become empty, then this Light + * will have universe scope: all nodes within the region of + * influence will be affected by this Light node. + * + * @param scope the Group node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light7")); + + if (isLive()) + ((LightRetained)this.retained).removeScope(scope); + else + ((LightRetained)this.retained).initRemoveScope(scope); + } + + + /** + * Removes all Group nodes from this Light node's + * list of scopes. The Light node will then have + * universe scope: all nodes within the region of influence will + * be affected by this Light node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if any group node in this + * node's list of scopes is part of a compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeAllScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light7")); + + if (isLive()) + ((LightRetained)this.retained).removeAllScopes(); + else + ((LightRetained)this.retained).initRemoveAllScopes(); + } + + + /** + * Sets the Light's influencing region to the specified bounds. + * This is used when the influencing bounding leaf is set to null. + * @param region the bounds that contains the Light's new influencing + * region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInfluencingBounds(Bounds region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light11")); + + if (isLive()) + ((LightRetained)this.retained).setInfluencingBounds(region); + else + ((LightRetained)this.retained).initInfluencingBounds(region); + } + + /** + * Retrieves the Light node's influencing bounds. + * @return this Light's influencing bounds information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getInfluencingBounds() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Light12")); + + return ((LightRetained)this.retained).getInfluencingBounds(); + } + + /** + * Sets the Light's influencing region to the specified bounding leaf. + * When set to a value other than null, this overrides the influencing + * bounds object. + * @param region the bounding leaf node used to specify the Light + * node's new influencing region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInfluencingBoundingLeaf(BoundingLeaf region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Light11")); + + if (isLive()) + ((LightRetained)this.retained).setInfluencingBoundingLeaf(region); + else + ((LightRetained)this.retained).initInfluencingBoundingLeaf(region); + } + + /** + * Retrieves the Light node's influencing bounding leaf. + * @return this Light's influencing bounding leaf information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BoundingLeaf getInfluencingBoundingLeaf() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INFLUENCING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Light12")); + + return ((LightRetained)this.retained).getInfluencingBoundingLeaf(); + } + + + + /** + * Copies all Light information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + LightRetained attr = (LightRetained) originalNode.retained; + LightRetained rt = (LightRetained) retained; + + Color3f c = new Color3f(); + attr.getColor(c); + rt.initColor(c); + rt.initInfluencingBounds(attr.getInfluencingBounds()); + + Enumeration elm = attr.getAllScopes(); + while (elm.hasMoreElements()) { + // this reference will set correctly in updateNodeReferences() callback + rt.initAddScope((Group) elm.nextElement()); + } + + // this reference will set correctly in updateNodeReferences() callback + rt.initInfluencingBoundingLeaf(attr.getInfluencingBoundingLeaf()); + + rt.initEnable(attr.getEnable()); + } + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + + + LightRetained rt = (LightRetained) retained; + BoundingLeaf bl = rt.getInfluencingBoundingLeaf(); + + if (bl != null) { + Object o = referenceTable.getNewObjectReference(bl); + rt.initInfluencingBoundingLeaf((BoundingLeaf)o); + } + + int num = rt.numScopes(); + for (int i=0; i < num; i++) { + rt.initScope((Group) referenceTable. + getNewObjectReference(rt.getScope(i)), i); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/LightBin.java b/src/classes/share/javax/media/j3d/LightBin.java new file mode 100644 index 0000000..28aeb08 --- /dev/null +++ b/src/classes/share/javax/media/j3d/LightBin.java @@ -0,0 +1,445 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; + +/** + * The LightBin manages a collection of EnvironmentSet objects. + * The number of objects managed depends upon the number of Lights + * in each EnvironmentSet and the number of lights supported by + * the underlying rendering layer. + */ + +class LightBin extends Object implements ObjectUpdate { + + /** + * The maximum number of lights in a LightBin + */ + int maxLights = -1; + + /** + * The Array of Light references in this LightBin. + * This array is always maxLights in length. + */ + LightRetained[] lights = null; + + /** + * An Array of reference counts for shared lights in + * among EnvirionmentSets + */ + int[] lightsRef = null; + + /** + * The number of empty light slots in this LightBin + */ + int numEmptySlots = -1; + + /** + * The RenderBin for this object + */ + RenderBin renderBin = null; + + /** + * The references to the next and previous LightBins in the + * list. + */ + LightBin next = null; + LightBin prev = null; + + /** + * The list of EnvironmentSets in this LightBin. + */ + EnvironmentSet environmentSetList = null; + + /** + * List of envSet to be added for the next iteration + */ + ArrayList insertEnvSet = new ArrayList(); + + + + /** + * cache of the canvasDirty + */ + int canvasDirty = 0; + + /** + * lightDirty Mask cache , used to + * mark the lightdirty bits for next frame + */ + int lightDirtyMaskCache = 0; + + + /** + * lightDirty Mask used during rendering + */ + int lightDirtyMask = 0; + + /** + * List of pointLts in this lightbin + * Need to reload these lights when vworld scale changes + */ + ArrayList pointLts = new ArrayList(); + int[] pointLtsSlotIndex; + + // OrderedGroup info + OrderedCollection orderedCollection = null; + + boolean onUpdateList = false; + + // background node that contains geometry + BackgroundRetained geometryBackground = null; + + + + LightBin(int maxLights, RenderBin rb, boolean isOpaque) { + this.maxLights = maxLights; + this.numEmptySlots = maxLights; + lights = new LightRetained[maxLights]; + lightsRef = new int[maxLights]; + renderBin = rb; + } + + void reset(boolean inOpaque) { + prev = null; + next = null; + orderedCollection = null; + environmentSetList = null; + onUpdateList = false; + geometryBackground = null; + // No need to reset the lights and lightRef + if (J3dDebug.devPhase && J3dDebug.debug) { + for (int i=0; i<maxLights; i++) { + J3dDebug.doAssert(lights[i] == null, "lights[i] == null"); + J3dDebug.doAssert(lightsRef[i] == 0, "lightsRef[i] == 0"); + } + } + } + + void setOrderedInfo(OrderedCollection oc) { + orderedCollection = oc; + } + + /** + * Checks to see if an EnvironmentSet will fit into + * this LightBin. It takes into account shared lights. + */ + boolean willEnvironmentSetFit(EnvironmentSet e) { + int i, j, numEsLights, slotsNeeded; + LightRetained light; + + numEsLights = e.lights.size(); + slotsNeeded = numEsLights; + for (i=0; i<numEsLights; i++) { + light = (LightRetained) e.lights.get(i); + if (light instanceof AmbientLightRetained) { + continue; + } + for (j=0; j<maxLights; j++) { + if (lights[j] == light) { + slotsNeeded--; + break; + } + } + } + if (slotsNeeded > numEmptySlots) { + return (false); + } else { + return (true); + } + } + + /** + * Adds the new EnvironmentSet to this LightBin. + */ + void addEnvironmentSet(EnvironmentSet e, RenderBin rb) { + int i, j, numEsLights; + LightRetained light; + + numEsLights = e.lights.size(); + for (i=0; i<numEsLights; i++) { + light = (LightRetained) e.lights.get(i); + if (light instanceof AmbientLightRetained) { + continue; + } + for (j=0; j<maxLights; j++) { + if (lights[j] == light) { + if (light.lightOn) { + e.enableMask |= 1<<j; + } + lightsRef[j]++; + // Keep a reference to the position of the light + // in the light bin that this light in the envSet + // refers + e.ltPos[i] = j; + break; + } + } + if (j==maxLights) { + // Find an empty slot + for (j=0; j<maxLights; j++) { + if (lights[j] == null) { + lights[j] = light; + lightsRef[j] = 1; + if (light instanceof PointLightRetained) { + pointLts.add(light); + + // save the destinated light slot for point + // so that point light can be updated without + // referencing the lights list + int pointLtsSlotIndexLen = 0; + if (pointLtsSlotIndex != null) + pointLtsSlotIndexLen = pointLtsSlotIndex.length; + if (pointLtsSlotIndexLen < pointLts.size()) { + + int[] newIndexList = + new int[pointLtsSlotIndexLen + 8]; + for (int x = 0; x < pointLtsSlotIndexLen; x++) { + newIndexList[x] = pointLtsSlotIndex[x]; + } + pointLtsSlotIndex = newIndexList; + } + pointLtsSlotIndex[pointLts.size() - 1] = j; + } + if (light.lightOn) { + e.enableMask |= 1<<j; + } + // Keep a reference to the position of the light + // in the light bin that this light in the envSet + // refers + e.ltPos[i] = j; + numEmptySlots--; + break; + } + } + } + } + e.lightBin = this; + e.enableMaskCache = e.enableMask; + insertEnvSet.add(e); + if (!onUpdateList) { + rb.objUpdateList.add(this); + onUpdateList = true; + } + + } + + public void updateObject() { + int i, j; + EnvironmentSet e ; + + + // Handle insertion + if (insertEnvSet.size() > 0) { + e = (EnvironmentSet)insertEnvSet.get(0); + if (environmentSetList == null) { + environmentSetList = e; + } + else { + e.next = environmentSetList; + environmentSetList.prev = e; + environmentSetList = e; + } + for (i = 1; i < insertEnvSet.size(); i++) { + e = (EnvironmentSet)insertEnvSet.get(i); + e.next = environmentSetList; + environmentSetList.prev = e; + environmentSetList = e; + } + } + + + insertEnvSet.clear(); + if (canvasDirty != 0) { + + Canvas3D canvases[] = renderBin.view.getCanvases(); + for (i = 0; i < canvases.length; i++) { + canvases[i].canvasDirty |= canvasDirty; + } + lightDirtyMask = lightDirtyMaskCache; + canvasDirty = 0; + lightDirtyMaskCache = 0; + } + onUpdateList = false; + } + + + + /** + * Removes the given EnvironmentSet from this LightBin. + */ + void removeEnvironmentSet(EnvironmentSet e) { + int i, j, numEsLights; + LightRetained light; + + e.lightBin = null; + // If envSet being remove is contained in envSet, then + // remove the envSet from the addList + if (insertEnvSet.contains(e)) { + insertEnvSet.remove(insertEnvSet.indexOf(e)); + } + else { + numEsLights = e.lights.size(); + for (i=0; i<numEsLights; i++) { + light = (LightRetained) e.lights.get(i); + for (j=0; j<maxLights; j++) { + if (lights[j] == light) { + lightsRef[j]--; + if (lightsRef[j] == 0) { + if (light instanceof PointLightRetained) + pointLts.remove(pointLts.indexOf(light)); + lights[j] = null; + // If the lightBin is dirty unset the mask + lightDirtyMaskCache &= ~(1 << j); + // since the canvas may already be updated, + lightDirtyMask &= ~(1 << j); + numEmptySlots++; + } + break; + } + } + } + + if (e.prev == null) { // At the head of the list + environmentSetList = e.next; + if (e.next != null) { + e.next.prev = null; + } + } else { // In the middle or at the end. + e.prev.next = e.next; + if (e.next != null) { + e.next.prev = e.prev; + } + } + + // Mark all canvases that uses this environment set as + Canvas3D canvases[] = renderBin.view.getCanvases(); + for (i = 0; i < canvases.length; i++) { + // Mark the environmentSet cached by all the canvases as null + // to force to reEvaluate when it comes back from the freelist + // During envset::render(), we only check for the pointers not + // being the same, so we need to take care of the env set + // gotten from the freelist from one frame to another + canvases[i].environmentSet = null; + } + + } + e.prev = null; + e.next = null; + renderBin.envSetFreelist.add(e); + + if (environmentSetList == null && insertEnvSet.size() == 0) { + renderBin.removeLightBin(this); + geometryBackground = null; + } + + } + + /** + * Renders this LightBin + */ + void render(Canvas3D cv) { + EnvironmentSet e; + + // include this LightBin to the to-be-updated list in Canvas + cv.setStateToUpdate(Canvas3D.LIGHTBIN_BIT, this); + + e = environmentSetList; + while (e != null) { + e.render(cv); + e = e.next; + } + } + + void updateAttributes(Canvas3D cv) { + int i; + double scale; + + int frameCount = VirtualUniverse.mc.frameCount; + + // within frames + if (cv.lightBin != this) { + + if (geometryBackground == null) { + scale = cv.canvasViewCache.getVworldToCoexistenceScale(); + cv.setModelViewMatrix(cv.ctx, cv.vpcToEc.mat, + renderBin.vworldToVpc); + } else { + scale = cv.canvasViewCache.getInfVworldToCoexistenceScale(); + cv.setModelViewMatrix(cv.ctx, cv.vpcToEc.mat, + renderBin.infVworldToVpc); + } + + + for (i=0; i<maxLights; i++) { + if (lights[i] != null) { + if (cv.lights[i] != lights[i] || + cv.frameCount[i] != frameCount) { + cv.lights[i] = lights[i]; + cv.frameCount[i] = frameCount; + lights[i].update(cv.ctx, i,scale); + } + } + } + cv.lightBin = this; + cv.canvasDirty &= ~Canvas3D.LIGHTBIN_DIRTY; + // invalidate canvas cached enableMask + cv.enableMask = -1; + } + // across frames + else if ((cv.canvasDirty & Canvas3D.LIGHTBIN_DIRTY) != 0) { + // Just update the dirty lights + if (geometryBackground == null) { + scale = cv.canvasViewCache.getVworldToCoexistenceScale(); + cv.setModelViewMatrix(cv.ctx, cv.vpcToEc.mat, + renderBin.vworldToVpc); + } else { + scale = cv.canvasViewCache.getInfVworldToCoexistenceScale(); + cv.setModelViewMatrix(cv.ctx, cv.vpcToEc.mat, + renderBin.infVworldToVpc); + } + i = 0; + int mask = lightDirtyMask; + while (mask != 0) { + if ((mask & 1) != 0) { + lights[i].update(cv.ctx, i, scale); + cv.lights[i] = lights[i]; + cv.frameCount[i] = frameCount; + } + mask >>= 1; + i++; + } + + cv.canvasDirty &= ~Canvas3D.LIGHTBIN_DIRTY; + } + else if ((pointLts.size() > 0) && ((cv.canvasDirty & Canvas3D.VWORLD_SCALE_DIRTY) != 0 )) { + if (geometryBackground == null) { + scale = cv.canvasViewCache.getVworldToCoexistenceScale(); + cv.setModelViewMatrix(cv.ctx, cv.vpcToEc.mat, + renderBin.vworldToVpc); + } else { + scale = cv.canvasViewCache.getInfVworldToCoexistenceScale(); + cv.setModelViewMatrix(cv.ctx, cv.vpcToEc.mat, + renderBin.infVworldToVpc); + } + for (i = 0; i < pointLts.size(); i++) { + LightRetained lt = (LightRetained) pointLts.get(i); + lt.update(cv.ctx, pointLtsSlotIndex[i], scale); + cv.lights[pointLtsSlotIndex[i]] = lt; + cv.frameCount[pointLtsSlotIndex[i]] = frameCount; + } + } + } +} diff --git a/src/classes/share/javax/media/j3d/LightRetained.java b/src/classes/share/javax/media/j3d/LightRetained.java new file mode 100644 index 0000000..0132e9d --- /dev/null +++ b/src/classes/share/javax/media/j3d/LightRetained.java @@ -0,0 +1,1062 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Enumeration; +import java.util.Vector; +import java.util.ArrayList; + +/** + * LightRetained is an abstract class that contains instance variable common to + * all lights. + */ + +abstract class LightRetained extends LeafRetained { + + // Statics used when something in the light changes + static final int ENABLE_CHANGED = 0x0001; + static final int SCOPE_CHANGED = 0x0002; + static final int BOUNDS_CHANGED = 0x0004; + static final int COLOR_CHANGED = 0x0008; + static final int BOUNDINGLEAF_CHANGED = 0x0010; + static final int INIT_MIRROR = 0x0020; + static final int CLEAR_MIRROR = 0x0040; + static final int LAST_DEFINED_BIT = 0x0040; + + // Indicates whether the light is turned on. + boolean lightOn = true; + + // The color of the light (white by default). + Color3f color = new Color3f(1.0f, 1.0f, 1.0f); + + // This node which specifies the hierarchical scope of the + // light. A null reference means that this light has universal + // scope. + Vector scopes = new Vector(); + + /** + * The Boundary object defining the lights's region of influence. + */ + Bounds regionOfInfluence = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * The transformed value of the applicationRegion. + */ + Bounds region = null; + + /** + * This bitmask is set when something changes in the light + */ + int lightDirty = 0xffff; + + // This is a copy of the sgLight's dirty bits + int sgLightDirty = 0xffff; + + // The type of light + int lightType = -1; + + // This is true when this light is needed in the current light set + boolean isNeeded = false; + + // This is true when this light is referenced in an immediate mode context + boolean inImmCtx = false; + + // A back reference to the scene graph light, when this is a mirror light + LightRetained sgLight = null; + + // A HashKey for lights in a shared group + HashKey key = null; + + // An array of mirror lights, one for each instance of this light in a + // shared group. Entry 0 is the only one valid if we are not in a shared + // group. + LightRetained[] mirrorLights = new LightRetained[1]; + + // The number of valid lights in mirrorLights + int numMirrorLights = 0; + + // Indicated whether the light is a scoped light + boolean isScoped = false; + + // The object that contains the dynamic HashKey - a string type object + // Used in scoping + HashKey tempKey = new HashKey(250); + + /** + * A list of all the EnvironmentSets that reference this light. + * Note that multiple RenderBin update thread may access + * this shared environmentSets simultaneously. + * So we use UnorderList when sync. all the operations. + */ + UnorderList environmentSets = new UnorderList(1, EnvironmentSet.class); + + // Is true, if the mirror light is viewScoped + boolean isViewScoped = false; + + + /** + * Temporary list of newly added mirror lights, during any setlive + */ + ArrayList newlyAddedMirrorLights = new ArrayList(); + + // Target threads to be notified when light changes + static final int targetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + + /** + * Initialize the light on or off. + * @param state true or false to enable or disable the light + */ + void initEnable(boolean state) { + this.lightOn = state; + } + + /** + * Turns the light on or off and send a message + * @param state true or false to enable or disable the light + */ + void setEnable(boolean state) { + initEnable(state); + sendMessage(ENABLE_CHANGED, + (state ? Boolean.TRUE: Boolean.FALSE)); + } + + + /** + * Returns the state of the light (on/off). + * @return true if the light is on, false if the light is off. + */ + boolean getEnable() { + return this.lightOn; + } + + /** + * Initialize the color of this light node. + * @param color the value of this new light color + */ + void initColor(Color3f color) { + this.color.set(color); + } + + /** + * Sets the color of this light node and send a message + * @param color the value of this new light color + */ + void setColor(Color3f color) { + initColor(color); + sendMessage(COLOR_CHANGED, new Color3f(color)); + } + + /** + * Retrieves the color of this light. + * @param color the vector that will receive the color of this light + */ + void getColor(Color3f color) { + color.set(this.color); + } + + /** + * Initializes the specified scope with the scope provided. + * @param scope the new scope + * @param index which scope to replace + */ + void initScope(Group scope, int index) { + GroupRetained group = (GroupRetained)scope.retained; + scopes.setElementAt(group, index); + + } + + + /** + * Replaces the specified scope with the scope provided and + * send a message + * @param scope the new scope + * @param index which scope to replace + */ + void setScope(Group scope, int index) { + ArrayList addScopeList = new ArrayList(); + ArrayList removeScopeList = new ArrayList(); + GroupRetained group; + Object[] scopeInfo = new Object[3]; + + group = (GroupRetained) scopes.get(index); + tempKey.reset(); + group.removeAllNodesForScopedLight((inSharedGroup?numMirrorLights:1), mirrorLights, removeScopeList, tempKey); + + + group = (GroupRetained)scope.retained; + tempKey.reset(); + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + group.addAllNodesForScopedLight((inSharedGroup?numMirrorLights:1), mirrorLights,addScopeList, tempKey); + + + initScope(scope, index); + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + scopeInfo[0] = addScopeList; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + /** + * Inserts the specified scope at specified index. + * @param scope the new scope + * @param index position to insert new scope at + */ + void initInsertScope(Group scope, int index) { + GroupRetained group = (GroupRetained)scope.retained; + scopes.insertElementAt(group, index); + group.setLightScope(); + } + + /** + * Inserts the specified scope at specified index. + * @param scope the new scope + * @param index position to insert new scope at + */ + void insertScope(Group scope, int index) { + + Object[] scopeInfo = new Object[3]; + ArrayList addScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scope.retained; + + tempKey.reset(); + group.addAllNodesForScopedLight((inSharedGroup?numMirrorLights:1), mirrorLights,addScopeList, tempKey); + + initInsertScope(scope, index); + scopeInfo[0] = addScopeList; + scopeInfo[1] = null; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + + /** + * Removes the scope at specified index. + * @param index which scope to remove + */ + void initRemoveScope(int index) { + GroupRetained group = (GroupRetained)scopes.elementAt(index); + scopes.removeElementAt(index); + group.removeLightScope(); + } + + + /** + * Removes the scope at specified index. + * @param index which scope to remove + */ + void removeScope(int index) { + + Object[] scopeInfo = new Object[3]; + ArrayList removeScopeList = new ArrayList(); + + GroupRetained group = (GroupRetained)scopes.elementAt(index); + tempKey.reset(); + group.removeAllNodesForScopedLight((inSharedGroup?numMirrorLights:1), mirrorLights, removeScopeList, tempKey); + initRemoveScope(index); scopeInfo[0] = null; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + + /** + * Removes the specified scope + * @param scope to be removed + */ + void removeScope(Group scope) { + int ind = indexOfScope(scope); + if(ind >= 0) + removeScope(ind); + } + + void initRemoveScope(Group scope) { + int ind = indexOfScope(scope); + if(ind >= 0) + initRemoveScope(ind); + } + + /** + * Removes all the scopes from this Light's list of scopes + */ + void removeAllScopes() { + int n = scopes.size(); + Object[] scopeInfo = new Object[3]; + ArrayList removeScopeList = new ArrayList(); + GroupRetained group; + + for(int index = n-1; index >= 0; index--) { + group = (GroupRetained)scopes.elementAt(index); + tempKey.reset(); + group.removeAllNodesForScopedLight((inSharedGroup?numMirrorLights:1), mirrorLights, removeScopeList, tempKey); + initRemoveScope(index); + } + scopeInfo[0] = null; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + void initRemoveAllScopes() { + int n = scopes.size(); + for(int i = n-1; i >= 0; i--) + initRemoveScope(i); + } + + /** + * Returns the scope specified by the index. + * @param index of the scope to be returned + * @return the scope at location index + */ + Group getScope(int index) { + return (Group)(((GroupRetained)(scopes.elementAt(index))).source); + } + + /** + * Returns an enumeration object of the scope + * @return an enumeration object of the scope + */ + Enumeration getAllScopes() { + Enumeration elm = scopes.elements(); + Vector v = new Vector(scopes.size()); + while (elm.hasMoreElements()) { + v.add( ((GroupRetained) elm.nextElement()).source); + } + return v.elements(); + } + + /** + * Appends the specified scope to this node's list of scopes. + * @param scope the scope to add to this node's list of scopes + */ + void initAddScope(Group scope) { + GroupRetained group = (GroupRetained)scope.retained; + scopes.addElement(group); + group.setLightScope(); + } + + /** + * Appends the specified scope to this node's list of scopes. + * @param scope the scope to add to this node's list of scopes + */ + void addScope(Group scope) { + Object[] scopeInfo = new Object[3]; + ArrayList addScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scope.retained; + + initAddScope(scope); + tempKey.reset(); + group.addAllNodesForScopedLight((inSharedGroup?numMirrorLights:1), mirrorLights,addScopeList, tempKey); + scopeInfo[0] = addScopeList; + scopeInfo[1] = null; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo); + } + + /** + * Returns a count of this nodes' scopes. + * @return the number of scopes descendant from this node + */ + int numScopes() { + return scopes.size(); + } + + /** + * Returns the index of the specified scope + * @return index of the scope in this Light's list of scopes + */ + int indexOfScope(Group scope) { + if(scope != null) + return scopes.indexOf((GroupRetained)scope.retained); + else + return scopes.indexOf(null); + } + + /** + * Initializes the Light's region of influence. + * @param region a region that contains the Light's new region of influence + */ + void initInfluencingBounds(Bounds region) { + if (region != null) { + regionOfInfluence = (Bounds) region.clone(); + if (staticTransform != null) { + regionOfInfluence.transform(staticTransform.transform); + } + } else { + regionOfInfluence = null; + } + } + + + /** + * Set the Light's region of influence and send a message + * @param region a region that contains the Light's new region of influence + */ + void setInfluencingBounds(Bounds region) { + initInfluencingBounds(region); + sendMessage(BOUNDS_CHANGED, + (region != null ? region.clone() : null)); + } + + /** + * Get the Light's region of influence + * @return this Light's region of influence information + */ + Bounds getInfluencingBounds() { + Bounds b = null; + + if (regionOfInfluence != null) { + b = (Bounds) regionOfInfluence.clone(); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + b.transform(invTransform); + } + } + return b; + } + + /** + * Initializes the Light's region of influence to the specified Leaf node. + */ + void initInfluencingBoundingLeaf(BoundingLeaf region) { + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + } else { + boundingLeaf = null; + } + } + + /** + * Set the Light's region of influence to the specified Leaf node. + */ + void setInfluencingBoundingLeaf(BoundingLeaf region) { + int i, numLgts; + + numLgts = numMirrorLights; + if (numMirrorLights == 0) + numLgts = 1; + + if (boundingLeaf != null) { + // Remove the mirror lights as users of the original bounding leaf + for (i = 0; i < numLgts; i++) { + boundingLeaf.mirrorBoundingLeaf.removeUser(mirrorLights[i]); + } + } + + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + // Add all mirror lights as user of this bounding leaf + for (i = 0; i < numLgts; i++) { + boundingLeaf.mirrorBoundingLeaf.addUser(mirrorLights[i]); + } + } else { + boundingLeaf = null; + } + + sendMessage(BOUNDINGLEAF_CHANGED, + (boundingLeaf != null ? + boundingLeaf.mirrorBoundingLeaf : null)); + } + + /** + * Get the Light's region of influence. + */ + BoundingLeaf getInfluencingBoundingLeaf() { + return (boundingLeaf != null ? + (BoundingLeaf)boundingLeaf.source : null); + } + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return (inImmCtx); + } + + + // Called on the parent Light object and loops over the mirror object + void initMirrorObject(Object[] args) { + Shape3DRetained shape; + Object[] scopeInfo = (Object[])((Object[])args[4])[5]; + ArrayList gAtomList = (ArrayList)scopeInfo[1]; + Boolean scoped = (Boolean)scopeInfo[0]; + BoundingLeafRetained bl=(BoundingLeafRetained)((Object[])args[4])[0]; + Bounds bnds = (Bounds)((Object[])args[4])[1]; + int numLgts = ((Integer)args[2]).intValue(); + LightRetained[] mLgts = (LightRetained[]) args[3]; + int k; + + for ( k = 0; k < numLgts; k++) { + for (int i = 0; i < gAtomList.size(); i++) { + shape = ((GeometryAtom)gAtomList.get(i)).source; + shape.addLight(mLgts[k]); + } + mLgts[k].isScoped = scoped.booleanValue(); + } + + for (k = 0; k < numLgts; k++) { + mLgts[k].inBackgroundGroup = ((Boolean)((Object[])args[4])[2]).booleanValue(); + mLgts[k].geometryBackground = (BackgroundRetained)((Object[])args[4])[3]; + + + if (bl != null) { + mLgts[k].boundingLeaf = bl.mirrorBoundingLeaf; + mLgts[k].region = mLgts[k].boundingLeaf.transformedRegion; + } else { + mLgts[k].boundingLeaf = null; + mLgts[k].region = null; + } + + if (bnds != null) { + mLgts[k].regionOfInfluence = bnds; + if (mLgts[k].region == null) { + mLgts[k].region = (Bounds)regionOfInfluence.clone(); + mLgts[k].region.transform(regionOfInfluence, getLastLocalToVworld()); + } + } + else { + mLgts[k].regionOfInfluence = null; + } + mLgts[k].lightOn = ((Boolean)((Object[])args[4])[4]).booleanValue(); + + } + // if its a ambient light,then do a immediate update of color + if (this instanceof AmbientLightRetained) { + Color3f clr = (Color3f) ((Object[])args[4])[6]; + for (int i = 0; i < numLgts; i++) { + mLgts[i].color.set(clr); + } + } + + } + + /** + * This method is implemented by each light for rendering + * context updates. This default one does nothing. + */ + abstract void update(long ctx, int lightSlot, double scale); + + + // This routine is called when rendering Env structure + // get a message, this routine updates values in the mirror object + // that are not used by the renderer + void updateImmediateMirrorObject(Object[] objs) { + Transform3D trans = null; + int component = ((Integer)objs[1]).intValue(); + int numLgts = ((Integer)objs[2]).intValue(); + LightRetained[] mLgts = (LightRetained[]) objs[3]; + + // Color changed called immediately only for ambient lights + if ((component & COLOR_CHANGED) != 0) { + for (int i = 0; i < numLgts; i++) { + mLgts[i].color.set(((Color3f)objs[4])); + } + } + else if ((component & ENABLE_CHANGED) != 0) { + for (int i = 0; i < numLgts; i++) + mLgts[i].lightOn = ((Boolean)objs[4]).booleanValue(); + } + else if ((component & BOUNDS_CHANGED) != 0) { + for (int i = 0; i < numLgts; i++) { + mLgts[i].regionOfInfluence = (Bounds) objs[4]; + if (mLgts[i].boundingLeaf == null) { + if (objs[4] != null) { + mLgts[i].region = + ((Bounds)mLgts[i].regionOfInfluence).copy(mLgts[i].region); + mLgts[i].region.transform(mLgts[i].regionOfInfluence, + mLgts[i].getCurrentLocalToVworld()); + } + else { + mLgts[i].region = null; + } + } + } + } + else if ((component & BOUNDINGLEAF_CHANGED) != 0) { + for (int i = 0; i < numLgts; i++) { + mLgts[i].boundingLeaf=((BoundingLeafRetained)objs[4]); + if (objs[4] != null) { + mLgts[i].region = (Bounds)mLgts[i].boundingLeaf.transformedRegion; + } + else { // evaluate regionOfInfluence if not null + if (mLgts[i].regionOfInfluence != null) { + mLgts[i].region = + ((Bounds)mLgts[i].regionOfInfluence).copy(mLgts[i].region); + mLgts[i].region.transform(mLgts[i].regionOfInfluence, + mLgts[i].getCurrentLocalToVworld()); + } + else { + mLgts[i].region = null; + } + } + } + } + else if ((component & SCOPE_CHANGED) != 0) { + int nscopes, j, i; + GroupRetained group; + Vector currentScopes; + Object[] scopeList = (Object[])objs[4]; + ArrayList addList = (ArrayList)scopeList[0]; + ArrayList removeList = (ArrayList)scopeList[1]; + boolean isScoped = ((Boolean)scopeList[2]).booleanValue(); + + if (addList != null) { + for (i = 0; i < numLgts; i++) { + mLgts[i].isScoped = isScoped; + for (j = 0; j < addList.size(); j++) { + Shape3DRetained obj = ((GeometryAtom)addList.get(j)).source; + obj.addLight(mLgts[i]); + } + } + } + + if (removeList != null) { + for (i = 0; i < numLgts; i++) { + mLgts[i].isScoped = isScoped; + for (j = 0; j < removeList.size(); j++) { + Shape3DRetained obj = ((GeometryAtom)removeList.get(j)).source; + ((Shape3DRetained)obj).removeLight(mLgts[i]); + } + } + } + } + + + } + + + + // The update Object function called during RenderingEnv objUpdate + // Note : if you add any more fields here , you need to update + // updateLight() in RenderingEnvironmentStructure + void updateMirrorObject(Object[] objs) { + + Transform3D trans = null; + int component = ((Integer)objs[1]).intValue(); + int numLgts = ((Integer)objs[2]).intValue(); + LightRetained[] mLgts = (LightRetained[]) objs[3]; + + if ((component & COLOR_CHANGED) != 0) { + for (int i = 0; i < numLgts; i++) { + mLgts[i].color.set(((Color3f)objs[4])); + } + } + + if ((component & INIT_MIRROR) != 0) { + for (int i = 0; i < numLgts; i++) { + Color3f clr = (Color3f) ((Object[])objs[4])[6]; + mLgts[i].color.set(clr); + } + } + } + + /** Note: This routine will only be called on + * the mirror object - will update the object's + * cached region and transformed region + */ + + void updateBoundingLeaf() { + // This is necessary, if for example, the region + // changes from sphere to box. + if (boundingLeaf != null && boundingLeaf.switchState.currentSwitchOn) { + region = boundingLeaf.transformedRegion; + } else { // evaluate regionOfInfluence if not null + if (regionOfInfluence != null) { + region = regionOfInfluence.copy(region); + region.transform(regionOfInfluence, getCurrentLocalToVworld()); + } else { + region = null; + } + } + } + void getMirrorObjects(ArrayList leafList, HashKey key) { + if (!inSharedGroup) { + leafList.add(mirrorLights[0]); + } + else { + for (int i=0; i<numMirrorLights; i++) { + if (mirrorLights[i].key.equals(key)) { + leafList.add(mirrorLights[i]); + break; + } + } + + } + } + /** + * This gets the mirror light for this light given the key. + */ + LightRetained getMirrorLight(HashKey key) { + int i; + LightRetained[] newLights; + + if (inSharedGroup) { + for (i=0; i<numMirrorLights; i++) { + if (mirrorLights[i].key.equals(key)) { + return(mirrorLights[i]); + } + } + if (numMirrorLights == mirrorLights.length) { + newLights = new LightRetained[numMirrorLights*2]; + for (i=0; i<numMirrorLights; i++) { + newLights[i] = mirrorLights[i]; + } + mirrorLights = newLights; + } + // mirrorLights[numMirrorLights] = (LightRetained) + // this.clone(true); + mirrorLights[numMirrorLights] = (LightRetained) this.clone(); + // If the bounding leaf is not null , add this + // mirror object as a user + if (boundingLeaf != null) { + mirrorLights[numMirrorLights].boundingLeaf = this.boundingLeaf.mirrorBoundingLeaf; + if (mirrorLights[numMirrorLights].boundingLeaf != null) + mirrorLights[numMirrorLights].boundingLeaf.addUser(mirrorLights[numMirrorLights]); + } + // mirrorLights[numMirrorLights].key = new HashKey(key); + mirrorLights[numMirrorLights].key = key; + mirrorLights[numMirrorLights].sgLight = this; + return(mirrorLights[numMirrorLights++]); + } else { + if (mirrorLights[0] == null) { + //mirrorLights[0] = (LightRetained) this.clone(true); + mirrorLights[0] = (LightRetained) this.clone(); + // If the bounding leaf is not null , add this + // mirror object as a user + if (boundingLeaf != null) { + mirrorLights[0].boundingLeaf = this.boundingLeaf.mirrorBoundingLeaf; + if (mirrorLights[0].boundingLeaf != null) + mirrorLights[0].boundingLeaf.addUser(mirrorLights[0]); + } + mirrorLights[0].sgLight = this; + } + return(mirrorLights[0]); + } + } + + void setLive(SetLiveState s) { + LightRetained ml; + int i, j; + + newlyAddedMirrorLights.clear(); + if (inImmCtx) { + throw new IllegalSharingException(J3dI18N.getString("LightRetained0")); + } + super.doSetLive(s); + + if (s.inSharedGroup) { + for (i=0; i<s.keys.length; i++) { + ml = this.getMirrorLight(s.keys[i]); + ml.localToVworld = new Transform3D[1][]; + ml.localToVworldIndex = new int[1][]; + + j = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + if(j < 0) { + System.out.println("LightRetained : Can't find hashKey"); + } + + ml.localToVworld[0] = localToVworld[j]; + ml.localToVworldIndex[0] = localToVworldIndex[j]; + // If its view Scoped, then add this list + // to be sent to Rendering Env + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(ml); + s.scopedNodesViewList.add(s.viewLists.get(i)); + } else { + s.nodeList.add(ml); + } + + newlyAddedMirrorLights.add(ml); + if (boundingLeaf != null) { + boundingLeaf.mirrorBoundingLeaf.addUser(ml); + } + if (s.transformTargets != null && + s.transformTargets[i] != null) { + s.transformTargets[i].addNode(ml, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + if (s.switchTargets != null && + s.switchTargets[i] != null) { + s.switchTargets[i].addNode(ml, Targets.ENV_TARGETS); + } + ml.switchState = (SwitchState)s.switchStates.get(j); + + } + } else { + ml = this.getMirrorLight(null); + ml.localToVworld = new Transform3D[1][]; + ml.localToVworldIndex = new int[1][]; + ml.localToVworld[0] = this.localToVworld[0]; + ml.localToVworldIndex[0] = this.localToVworldIndex[0]; + // Initialization of the mirror object + // If its view Scoped, then add this list + // to be sent to Rendering Env + // System.out.println("lightSetLive, s.viewList = "+s.viewLists); + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(ml); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(ml); + } + newlyAddedMirrorLights.add(ml); + if (boundingLeaf != null) { + boundingLeaf.mirrorBoundingLeaf.addUser(ml); + } + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(ml, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(ml, Targets.ENV_TARGETS); + } + ml.switchState = (SwitchState)s.switchStates.get(0); + } + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + super.markAsLive(); + + } + + J3dMessage initMessage(int num) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.LIGHT_CHANGED; + createMessage.args[0] = this; + // a snapshot of all attributes that needs to be initialized + // in the mirror object + createMessage.args[1]= new Integer(INIT_MIRROR); + + LightRetained[] mlts = new LightRetained[newlyAddedMirrorLights.size()]; + for (int i = 0; i < mlts.length; i++) { + mlts[i] = (LightRetained)newlyAddedMirrorLights.get(i); + } + createMessage.args[2] = new Integer(mlts.length); + createMessage.args[3] = mlts; + + Object[] obj = new Object[num]; + obj[0] = boundingLeaf; + obj[1] = (regionOfInfluence != null?regionOfInfluence.clone():null); + obj[2] = (inBackgroundGroup? Boolean.TRUE:Boolean.FALSE); + obj[3] = geometryBackground; + obj[4] = (lightOn? Boolean.TRUE:Boolean.FALSE); + + ArrayList addScopeList = new ArrayList(); + for (int i = 0; i < scopes.size(); i++) { + GroupRetained group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.addAllNodesForScopedLight(mlts.length, mlts, addScopeList, tempKey); + } + Object[] scopeInfo = new Object[2]; + scopeInfo[0] = ((scopes.size() > 0) ? Boolean.TRUE:Boolean.FALSE); + scopeInfo[1] = addScopeList; + obj[5] = scopeInfo; + Color3f clr = new Color3f(color); + obj[6] = clr; + createMessage.args[4] = obj; + return createMessage; + + } + + // The default set of clearLive actions + void clearLive(SetLiveState s) { + LightRetained ml; + newlyAddedMirrorLights.clear(); + super.clearLive(s); + + if (inSharedGroup) { + for (int i=0; i<s.keys.length; i++) { + ml = this.getMirrorLight(s.keys[i]); + if (s.transformTargets != null && + s.transformTargets[i] != null) { + s.transformTargets[i].addNode(ml, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + newlyAddedMirrorLights.add(ml); + // Remove this mirror light as users of the bounding leaf + if (ml.boundingLeaf != null) { + ml.boundingLeaf.removeUser(ml); + ml.boundingLeaf = null; + } + if (s.switchTargets != null && + s.switchTargets[i] != null) { + s.switchTargets[i].addNode(ml, Targets.ENV_TARGETS); + } + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(ml); + s.scopedNodesViewList.add(s.viewLists.get(i)); + } else { + s.nodeList.add(ml); + } + } + } else { + ml = this.getMirrorLight(null); + + // Remove this mirror light as users of the bounding leaf + if (ml.boundingLeaf != null) { + ml.boundingLeaf.removeUser(ml); + ml.boundingLeaf = null; + } + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(ml, Targets.ENV_TARGETS); + } + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(ml); + //System.out.println("s.viewList is " + s.viewLists); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(ml); + } + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(ml, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + + newlyAddedMirrorLights.add(ml); + } + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + + + + if (scopes.size() > 0) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + LightRetained[] mlts = new LightRetained[newlyAddedMirrorLights.size()]; + for (int i = 0; i < mlts.length; i++) { + mlts[i] = (LightRetained)newlyAddedMirrorLights.get(i); + } + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.LIGHT_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(CLEAR_MIRROR); + ArrayList removeScopeList = new ArrayList(); + for (int i = 0; i < scopes.size(); i++) { + GroupRetained group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.removeAllNodesForScopedLight(mlts.length, mlts, removeScopeList, tempKey); + } + createMessage.args[2] = removeScopeList; + createMessage.args[3] = new Integer(mlts.length); + createMessage.args[4] = mlts; + VirtualUniverse.mc.processMessage(createMessage); + } + } + + void clearMirrorObject(Object[] args) { + Shape3DRetained shape; + ArrayList shapeList = (ArrayList)args[2]; + ArrayList removeScopeList = new ArrayList(); + LightRetained[] mLgts = (LightRetained[]) args[4]; + int numLgts = ((Integer)args[3]).intValue(); + + for (int k = 0; k < numLgts; k++) { + for (int i = 0; i < shapeList.size(); i++) { + shape = ((GeometryAtom)shapeList.get(i)).source; + shape.removeLight(mLgts[k]); + } + mLgts[k].isScoped = false; + + } + + } + + + + /** + * Clones only the retained side, internal use only + */ + protected Object clone() { + LightRetained lr = (LightRetained)super.clone(); + lr.color = new Color3f(color); + lr.scopes = (Vector) scopes.clone(); + lr.initInfluencingBoundingLeaf(getInfluencingBoundingLeaf()); + lr.region = null; + lr.lightDirty = 0xffff; + lr.sgLightDirty = 0xffff; + lr.universe = null; + lr.isNeeded = false; + lr.inImmCtx = false; + lr.sgLight = null; + lr.key = null; + lr.mirrorLights = new LightRetained[1]; + lr.numMirrorLights = 0; + lr.environmentSets = new UnorderList(1, EnvironmentSet.class); + return lr; + } + + + // Called during RenderingEnv object update + void updateTransformChange() { + } + + // Called on mirror object and updated when message is received + void updateImmediateTransformChange() { + // If bounding leaf is null, tranform the bounds object + if (boundingLeaf == null) { + if (regionOfInfluence != null) { + region = regionOfInfluence.copy(region); + region.transform(regionOfInfluence, + getCurrentLocalToVworld()); + } + + } + } + + void sendMessage(int attrMask, Object attr) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.LIGHT_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + if (inSharedGroup) + createMessage.args[2] = new Integer(numMirrorLights); + else + createMessage.args[2] = new Integer(1); + + createMessage.args[3] = mirrorLights.clone(); + createMessage.args[4] = attr; + VirtualUniverse.mc.processMessage(createMessage); + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + if (regionOfInfluence != null) { + regionOfInfluence.transform(xform.transform); + } + } +} + diff --git a/src/classes/share/javax/media/j3d/LightSet.java b/src/classes/share/javax/media/j3d/LightSet.java new file mode 100644 index 0000000..df59b1a --- /dev/null +++ b/src/classes/share/javax/media/j3d/LightSet.java @@ -0,0 +1,91 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Vector; + +class LightSet extends Object { + /** + * The Lights that make up this set + */ + LightRetained[] lights = null; + + // The number of lights in this lightset, may be less than lights.length + int nlights = 0; + + // A reference to the next LightSet + LightSet next = null; + + // A reference to the previous LightSet + LightSet prev = null; + + // A flag that indicates that lighting is on + boolean lightingOn = true; + + // A flag that indicates that this light set has changed. + boolean isDirty = true; + + /** + * Constructs a new LightSet + */ + LightSet(RenderBin rb, RenderAtom ra, LightRetained[] lights, + int nlights, boolean lightOn) { + this.reset(rb, ra, lights, nlights, lightOn); + } + + void reset(RenderBin rb, RenderAtom ra, LightRetained[] lights, + int nlights, boolean lightOn) { + int i; + + this.isDirty = true; + this.lightingOn = lightOn; + if (this.lights == null || this.lights.length < nlights) { + this.lights = new LightRetained[nlights]; + } + + for (i=0; i<nlights; i++) { + this.lights[i] = lights[i]; + } + + this.nlights = nlights; + + //lists = new RenderList(ro); + //lists.prims[ro.geometry.geoType-1] = ro; + } + + boolean equals(RenderBin rb, LightRetained[] lights, int nlights, + boolean lightOn) { + int i, j; + LightRetained light; + + if (this.nlights != nlights) + return(false); + + if (this.lightingOn != lightOn) + return(false); + + for (i=0; i<nlights; i++) { + for (j=0; j<this.nlights; j++) { + if (this.lights[j] == lights[i]) { + break; + } + } + if (j==this.nlights) { + return(false); + } + } + return(true); + } + +} diff --git a/src/classes/share/javax/media/j3d/LineArray.java b/src/classes/share/javax/media/j3d/LineArray.java new file mode 100644 index 0000000..125083a --- /dev/null +++ b/src/classes/share/javax/media/j3d/LineArray.java @@ -0,0 +1,161 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The LineArray object draws the array of vertices as individual + * line segments. Each pair of vertices defines a line to be drawn. + */ + +public class LineArray extends GeometryArray { + + /** + * packaged scope default constructor. + */ + LineArray() { + } + + /** + * Constructs an empty LineArray object with the specified + * number of vertices, and vertex format. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @exception IllegalArgumentException if vertexCount is less than 2 + * or vertexCount is <i>not</i> a multiple of 2 + */ + public LineArray(int vertexCount, int vertexFormat) { + super(vertexCount,vertexFormat); + + if (vertexCount < 2 || ((vertexCount%2) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("LineArray0")); + } + + /** + * Constructs an empty LineArray object with the specified + * number of vertices, and vertex format, number of texture coordinate + * sets, and texture coordinate mapping array. + * + * @param vertexCount the number of vertex elements in this array<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used. + * + * @exception IllegalArgumentException if vertexCount is less than 2 + * or vertexCount is <i>not</i> a multiple of 2 + * + * @since Java 3D 1.2 + */ + public LineArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap); + + if (vertexCount < 2 || ((vertexCount%2) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("LineArray0")); + } + + /** + * Creates the retained mode LineArrayRetained object that this + * LineArray object will point to. + */ + void createRetained() { + this.retained = new LineArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + LineArrayRetained rt = (LineArrayRetained) retained; + + + int texSetCount = rt.getTexCoordSetCount(); + LineArray l; + if (texSetCount == 0) { + l = new LineArray(rt.getVertexCount(), + rt.getVertexFormat()); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + l = new LineArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap); + + } + + l.duplicateNodeComponent(this); + return l; + } + +} diff --git a/src/classes/share/javax/media/j3d/LineArrayRetained.java b/src/classes/share/javax/media/j3d/LineArrayRetained.java new file mode 100644 index 0000000..6391b4f --- /dev/null +++ b/src/classes/share/javax/media/j3d/LineArrayRetained.java @@ -0,0 +1,367 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The LineArray object draws the array of vertices as individual + * line segments. Each pair of vertices defines a line to be drawn. + */ + +class LineArrayRetained extends GeometryArrayRetained implements Cloneable { + + LineArrayRetained() { + this.geoType = GEO_TYPE_LINE_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[2]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectLineAndRay(pnts[0], pnts[1], pickRay.origin, + pickRay.direction, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + Vector3d dir = + new Vector3d(pickSegment.end.x - pickSegment.start.x, + pickSegment.end.y - pickSegment.start.y, + pickSegment.end.z - pickSegment.start.z); + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectLineAndRay(pnts[0], pnts[1], + pickSegment.start, + dir, sdist, iPnt) && + (sdist[0] <= 1.0)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectBoundingPolytope(pnts, bpolytope, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("LineArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + + } + + + boolean intersect(Point3d[] pnts) { + Point3d[] points = new Point3d[2]; + double dist[] = new double[1]; + Vector3d dir; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle + case 4: // Quad + while (i<validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + if (intersectSegment(pnts, points[0], points[1], dist, + null)) { + return true; + } + } + break; + case 2: // Line + dir = new Vector3d(); + while (i<validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + dir.x = points[1].x - points[0].x; + dir.y = points[1].y - points[0].y; + dir.z = points[1].z - points[0].z; + if (intersectLineAndRay(pnts[0], pnts[1], points[0], + dir, dist, null) && + (dist[0] <= 1.0)) { + return true; + } + } + break; + case 1: // Point + dir = new Vector3d(); + while (i<validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + dir.x = points[1].x - points[0].x; + dir.y = points[1].y - points[0].y; + dir.z = points[1].z - points[0].z; + if (intersectPntAndRay(pnts[0], points[0], dir, dist) && + (dist[0] <= 1.0)) { + return true; + } + } + break; + } + return false; + } + + boolean intersect(Transform3D thisToOtherVworld, + GeometryRetained geom) { + Point3d[] pnts = new Point3d[2]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + thisToOtherVworld.transform(pnts[0]); + thisToOtherVworld.transform(pnts[1]); + if (geom.intersect(pnts)) { + return true; + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + Point3d[] pnts = new Point3d[2]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, null, + null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + if (intersectBoundingPolytope(pnts, bpolytope, + null, null)) { + return true; + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + + return false; + } + // From Graphics Gems IV (pg5) and Graphics Gems II, Pg170 + void computeCentroid() { + Point3d pnt0 = new Point3d(); + Point3d pnt1 = new Point3d(); + double length; + double totallength = 0; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + centroid.x = 0; + centroid.y = 0; + centroid.z = 0; + + while (i < validVertexCount) { + getVertexData(i++, pnt0); + getVertexData(i++, pnt1); + length = pnt0.distance(pnt1); + centroid.x += (pnt0.x + pnt1.x) * length; + centroid.y += (pnt0.y + pnt1.y) * length; + centroid.z += (pnt0.z + pnt1.z) * length; + totallength += length; + } + + if (totallength != 0.0) { + length = 1.0/(2.0 * totallength); + centroid.x *= length; + centroid.y *= length; + centroid.z *= length; + } + } + + int getClassType() { + return LINE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/LineAttributes.java b/src/classes/share/javax/media/j3d/LineAttributes.java new file mode 100644 index 0000000..7d9caab --- /dev/null +++ b/src/classes/share/javax/media/j3d/LineAttributes.java @@ -0,0 +1,472 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The LineAttributes object defines all rendering state that can be set + * as a component object of a Shape3D node. + * The line attributes that can be defined are:<P> + * <UL><LI>Pattern - specifies the pattern used to draw the line:<p> + * <ul> + * <li>PATTERN_SOLID - draws a solid line with no pattern. This is + * the default.</li> + * <p> + * <li>PATTERN_DASH - draws dashed lines. Ideally, these will be drawn with + * a repeating pattern of 8 pixels on and 8 pixels off.</li> + * <p> + * <li>PATTERN_DOT - draws dotted lines. Ideally, these will be drawn with + * a repeating pattern of 1 pixel on and 7 pixels off.</li> + * <p> + * <li>PATTERN_DASH_DOT - draws dashed-dotted lines. Ideally, these will be + * drawn with a repeating pattern of 7 pixels on, 4 pixels off, 1 pixel on, + * and 4 pixels off.</li> + * <p> + * <li>PATTERN_USER_DEFINED - draws lines with a user-defined line pattern. + * See "User-defined Line Patterns," below.</li><p> + * </ul> + * <p> + * <LI>Antialiasing (enabled or disabled). By default, antialiasing + * is disabled.</LI> + * <p> + * <p> + * If antialiasing is enabled, the lines are considered transparent + * for rendering purposes. They are rendered with all the other transparent + * objects and adhere to the other transparency settings such as the + * View transparency sorting policy and the View depth buffer freeze + * transparent enable. + * </p> + * <LI>Width (in pixels). The default is a line width of one pixel. + * </LI></UL><p> + * + * <b>User-defined Line Patterns</b> + * <p> + * A user-defined line pattern is specified with a pattern mask and + * an optional scale factor. + * <p> + * The Pattern Mask<p> + * + * The pattern is specified + * using a 16-bit mask that specifies on and off segments. Bit 0 in + * the pattern mask corresponds to the first pixel of the line or line + * strip primitive. A value of 1 for a bit in the pattern mask indicates + * that the corresponding pixel is drawn, while a value of 0 + * indicates that the corresponding pixel is not drawn. After all 16 bits + * in the pattern are used, the pattern is repeated. + * <p> + * For example, a mask of 0x00ff defines a dashed line with a repeating + * pattern of 8 pixels on followed by 8 pixels off. A value of 0x0101 + * defines a a dotted line with a repeating pattern of 1 pixel on and 7 + * pixels off. + * <p> + * The pattern continues around individual line segments of a line strip + * primitive. It is restarted at the beginning of each new line strip. + * For line array primitives, the pattern is restarted at the beginning + * of each line. + * <p> + * The Scale Factor + * <p> + * The pattern is multiplied by the scale factor such that each bit in + * the pattern mask corresponds to that many consecutive pixels. + * For example, a scale factor of 3 applied to a pattern mask of 0x001f + * would produce a repeating pattern of 15 pixels on followed by 33 + * pixels off. The valid range for this attribute is [1,15]. Values + * outside this range are clamped.<p> + * + * @see Appearance + * @see View + */ +public class LineAttributes extends NodeComponent { + + /** + * Specifies that this LineAttributes object allows reading its + * line width information. + */ + public static final int + ALLOW_WIDTH_READ = CapabilityBits.LINE_ATTRIBUTES_ALLOW_WIDTH_READ; + + /** + * Specifies that this LineAttributes object allows writing its + * line width information. + */ + public static final int + ALLOW_WIDTH_WRITE = CapabilityBits.LINE_ATTRIBUTES_ALLOW_WIDTH_WRITE; + + /** + * Specifies that this LineAttributes object allows reading its + * line pattern information. + */ + public static final int + ALLOW_PATTERN_READ = CapabilityBits.LINE_ATTRIBUTES_ALLOW_PATTERN_READ; + + /** + * Specifies that this LineAttributes object allows writing its + * line pattern information. + */ + public static final int + ALLOW_PATTERN_WRITE = CapabilityBits.LINE_ATTRIBUTES_ALLOW_PATTERN_WRITE; + + /** + * Specifies that this LineAttributes object allows reading its + * line antialiasing flag. + */ + public static final int + ALLOW_ANTIALIASING_READ = CapabilityBits.LINE_ATTRIBUTES_ALLOW_ANTIALIASING_READ; + + /** + * Specifies that this LineAttributes object allows writing its + * line antialiasing flag. + */ + public static final int + ALLOW_ANTIALIASING_WRITE = CapabilityBits.LINE_ATTRIBUTES_ALLOW_ANTIALIASING_WRITE; + + + /** + * Draw solid lines with no pattern. + * @see #setLinePattern + */ + public static final int PATTERN_SOLID = 0; + + /** + * Draw dashed lines. Ideally, these will be drawn with + * a repeating pattern of 8 pixels on and 8 pixels off. + * @see #setLinePattern + */ + public static final int PATTERN_DASH = 1; + + /** + * Draw dotted lines. Ideally, these will be drawn with + * a repeating pattern of 1 pixel on and 7 pixels off. + * @see #setLinePattern + */ + public static final int PATTERN_DOT = 2; + + /** + * Draw dashed-dotted lines. Ideally, these will be drawn with + * a repeating pattern of 7 pixels on, 4 pixels off, 1 pixel on, + * and 4 pixels off. + * @see #setLinePattern + */ + public static final int PATTERN_DASH_DOT = 3; + + /** + * Draw lines with a user-defined line pattern. The line pattern + * is specified with a pattern mask and scale factor. + * @see #setLinePattern + * @see #setPatternMask + * @see #setPatternScaleFactor + * + * @since Java 3D 1.2 + */ + public static final int PATTERN_USER_DEFINED = 4; + + + /** + * Constructs a LineAttributes object with default parameters. + * The default values are as follows: + * <ul> + * line width : 1<br> + * line pattern : PATTERN_SOLID<br> + * pattern mask : 0xffff<br> + * pattern scale factor : 1<br> + * line antialiasing : false<br> + * </ul> + */ + public LineAttributes(){ + } + + /** + * Constructs a LineAttributes object with specified values. + * @param lineWidth the width of lines in pixels + * @param linePattern the line pattern, one of PATTERN_SOLID, + * PATTERN_DASH, PATTERN_DOT, or PATTERN_DASH_DOT + * @param lineAntialiasing flag to set line antialising ON or OFF + */ + public LineAttributes(float lineWidth, int linePattern, + boolean lineAntialiasing){ + + if (linePattern < PATTERN_SOLID || linePattern > PATTERN_DASH_DOT) + throw new IllegalArgumentException(J3dI18N.getString("LineAttributes0")); + + ((LineAttributesRetained)this.retained).initLineWidth(lineWidth); + ((LineAttributesRetained)this.retained).initLinePattern(linePattern); + ((LineAttributesRetained)this.retained).initLineAntialiasingEnable(lineAntialiasing); + } + + /** + * Sets the line width for this LineAttributes component object. + * @param lineWidth the width, in pixels, of line primitives + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setLineWidth(float lineWidth) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_WIDTH_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes1")); + if (isLive()) + ((LineAttributesRetained)this.retained).setLineWidth(lineWidth); + else + ((LineAttributesRetained)this.retained).initLineWidth(lineWidth); + + } + + /** + * Gets the line width for this LineAttributes component object. + * @return the width, in pixels, of line primitives + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getLineWidth() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_WIDTH_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes2")); + return ((LineAttributesRetained)this.retained).getLineWidth(); + } + + /** + * Sets the line pattern for this LineAttributes component object. + * @param linePattern the line pattern to be used, one of: + * PATTERN_SOLID, PATTERN_DASH, PATTERN_DOT, PATTERN_DASH_DOT, or + * PATTERN_USER_DEFINED. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setLinePattern(int linePattern) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PATTERN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes3")); + + if (linePattern < PATTERN_SOLID || linePattern > PATTERN_USER_DEFINED) + throw new IllegalArgumentException(J3dI18N.getString("LineAttributes4")); + + if (isLive()) + ((LineAttributesRetained)this.retained).setLinePattern(linePattern); + else + ((LineAttributesRetained)this.retained).initLinePattern(linePattern); + + +} + + /** + * Gets the line pattern for this LineAttributes component object. + * @return the line pattern + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getLinePattern() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PATTERN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes5")); + + return ((LineAttributesRetained)this.retained).getLinePattern(); + } + + + /** + * Sets the line pattern mask to the specified value. This is + * used when the linePattern attribute is set to + * PATTERN_USER_DEFINED. In this mode, the pattern is specified + * using a 16-bit mask that specifies on and off segments. Bit 0 + * in the pattern mask corresponds to the first pixel of the line + * or line strip primitive. A value of 1 for a bit in the pattern + * mask indicates that the corresponding pixel is drawn, while a + * value of 0 indicates that the corresponding pixel is not drawn. + * After all 16 bits in the pattern are used, the pattern is + * repeated. For example, a mask of 0x00ff defines a dashed line + * with a repeating pattern of 8 pixels on followed by 8 pixels + * off. A value of 0x0101 defines a a dotted line with a + * repeating pattern of 1 pixel on and 7 pixels off + * <p> + * The pattern continues around individual line segments of a line + * strip primitive. It is restarted at the beginning of each new + * line strip. For line array primitives, the pattern is + * restarted at the beginning of each line. + * @param mask the new line pattern mask + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @see #setPatternScaleFactor + * + * @since Java 3D 1.2 + */ + public void setPatternMask(int mask) { + if (isLiveOrCompiled() && + !this.getCapability(ALLOW_PATTERN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes8")); + + if (isLive()) + ((LineAttributesRetained)this.retained).setPatternMask(mask); + else + ((LineAttributesRetained)this.retained).initPatternMask(mask); + } + + + /** + * Retrieves the line pattern mask. + * @return the line pattern mask + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getPatternMask() { + if (isLiveOrCompiled() && + !this.getCapability(ALLOW_PATTERN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes9")); + + return ((LineAttributesRetained)this.retained).getPatternMask(); + } + + + /** + * Sets the line pattern scale factor to the specified value. + * This is used in conjunction with the patternMask when the + * linePattern attribute is set to PATTERN_USER_DEFINED. The + * pattern is multiplied by the scale factor such that each bit in + * the pattern mask corresponds to that many consecutive pixels. + * For example, a scale factor of 3 applied to a pattern mask of + * 0x001f would produce a repeating pattern of 15 pixels on + * followed by 33 pixels off. The valid range for this attribute + * is [1,15]. Values outside this range are clamped. + * @param scaleFactor the new line pattern scale factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @see #setPatternMask + * + * @since Java 3D 1.2 + */ + public void setPatternScaleFactor(int scaleFactor) { + if (isLiveOrCompiled() && + !this.getCapability(ALLOW_PATTERN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes10")); + + if (isLive()) + ((LineAttributesRetained)this.retained).setPatternScaleFactor(scaleFactor); + else + ((LineAttributesRetained)this.retained).initPatternScaleFactor(scaleFactor); + } + + + /** + * Retrieves the line pattern scale factor. + * @return the line pattern scale factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getPatternScaleFactor() { + if (isLiveOrCompiled() && + !this.getCapability(ALLOW_PATTERN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes11")); + + return ((LineAttributesRetained)this.retained).getPatternScaleFactor(); + } + + + /** + * Enables or disables line antialiasing + * for this LineAttributes component object. + * <p> + * If antialiasing is enabled, the lines are considered transparent + * for rendering purposes. They are rendered with all the other + * transparent objects and adhere to the other transparency settings + * such as the View transparency sorting policy and the View depth buffer + * freeze transparent enable. + * </p> + * @param state true or false to enable or disable line antialiasing + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @see View + */ + public void setLineAntialiasingEnable(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANTIALIASING_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes6")); + if (isLive()) + ((LineAttributesRetained)this.retained).setLineAntialiasingEnable(state); + else + ((LineAttributesRetained)this.retained).initLineAntialiasingEnable(state); + + + } + + /** + * Retrieves the state of the line antialiasing flag. + * @return true if line antialiasing is enabled, + * false if line antialiasing is disabled + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getLineAntialiasingEnable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANTIALIASING_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("LineAttributes7")); + + return ((LineAttributesRetained)this.retained).getLineAntialiasingEnable(); + } + + /** + * Creates a retained mode LineAttributesRetained object that this + * LineAttributes component object will point to. + */ + void createRetained() { + this.retained = new LineAttributesRetained(); + this.retained.setSource(this); + } + + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + LineAttributes la = new LineAttributes(); + la.duplicateNodeComponent(this); + return la; + } + + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, + forceDuplicate); + + LineAttributesRetained attr = (LineAttributesRetained) + originalNodeComponent.retained; + LineAttributesRetained rt = (LineAttributesRetained) retained; + + rt.initLineWidth(attr.getLineWidth()); + rt.initLinePattern(attr.getLinePattern()); + rt.initLineAntialiasingEnable(attr.getLineAntialiasingEnable()); + rt.initPatternMask(attr.getPatternMask()); + rt.initPatternScaleFactor(attr.getPatternScaleFactor()); + } + +} diff --git a/src/classes/share/javax/media/j3d/LineAttributesRetained.java b/src/classes/share/javax/media/j3d/LineAttributesRetained.java new file mode 100644 index 0000000..d84b449 --- /dev/null +++ b/src/classes/share/javax/media/j3d/LineAttributesRetained.java @@ -0,0 +1,335 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * The LineAttributesRetained object defines all rendering state that can be set + * as a component object of a Shape3D node. + */ +class LineAttributesRetained extends NodeComponentRetained { + // A list of pre-defined bits to indicate which component + // in this LineAttributesRetained object changed. + static final int LINE_WIDTH_CHANGED = 0x01; + static final int LINE_PATTERN_CHANGED = 0x02; + static final int LINE_AA_CHANGED = 0x04; + static final int LINE_PATTERN_MASK_CHANGED = 0x08; + static final int LINE_PATTERN_SCALEFACTOR_CHANGED = 0x10; + + // Width, in pixels, of line primitives + float lineWidth = 1.0f; + + // The line pattern to be used + int linePattern = LineAttributes.PATTERN_SOLID; + + // Line antialiasing switch + boolean lineAntialiasing = false; + + // user-defined line pattern mask + int linePatternMask = 0xffff; + + // line mask pattern scale factor + int linePatternScaleFactor = 1; + + /** + * Sets the line width for this lineAttributes component object. + * @param lineWidth the width, in pixels, of line primitives + */ + final void initLineWidth(float lineWidth) { + this.lineWidth = lineWidth; + } + + /** + * Sets the line width for this lineAttributes component object and sends a + * message notifying the interested structures of the change. + * @param lineWidth the width, in pixels, of line primitives + */ + final void setLineWidth(float lineWidth) { + initLineWidth(lineWidth); + sendMessage(LINE_WIDTH_CHANGED, new Float(lineWidth)); + } + + /** + * Gets the line width for this lineAttributes component object. + * @return the width, in pixels, of line primitives + */ + final float getLineWidth() { + return lineWidth; + } + + /** + * Sets the line pattern for this lineAttributes component object + * @param linePattern the line pattern to be used, one of: + * PATTERN_SOLID, PATTERN_DASH, PATTERN_DOT, or PATTERN_DASH_DOT + */ + final void initLinePattern(int linePattern) { + this.linePattern = linePattern; + } + /** + * Sets the line pattern for this lineAttributes component object + * and sends a message notifying the interested structures of the change. + * @param linePattern the line pattern to be used, one of: + * PATTERN_SOLID, PATTERN_DASH, PATTERN_DOT, or PATTERN_DASH_DOT + */ + final void setLinePattern(int linePattern) { + initLinePattern(linePattern); + sendMessage(LINE_PATTERN_CHANGED, new Integer(linePattern)); + } + + /** + * Gets the line pattern for this lineAttributes component object. + * @return the line pattern + */ + final int getLinePattern() { + return linePattern; + } + + /** + * Enables or disables line antialiasing + * for this lineAttributes component object and sends a + * message notifying the interested structures of the change. + * @param state true or false to enable or disable line antialiasing + */ + final void initLineAntialiasingEnable(boolean state) { + lineAntialiasing = state; + } + /** + * Enables or disables line antialiasing + * for this lineAttributes component object and sends a + * message notifying the interested structures of the change. + * @param state true or false to enable or disable line antialiasing + */ + final void setLineAntialiasingEnable(boolean state) { + initLineAntialiasingEnable(state); + sendMessage(LINE_AA_CHANGED, + (state ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Retrieves the state of the line antialiasing flag. + * @return true if line antialiasing is enabled, + * false if line antialiasing is disabled + */ + final boolean getLineAntialiasingEnable() { + return lineAntialiasing; + } + + + /** + * Sets the pattern mask for this LineAttributes component object. + * This is used when the linePattern attribute is set to + * PATTERN_USER_DEFINED. + * @param mask the line pattern mask to be used. + */ + final void initPatternMask(int mask) { + this.linePatternMask = mask; + } + + /** + * Sets the pattern mask for this LineAttributes component object + * and sends a message notifying the interested structures of change. + * This is used when the linePattern attribute is set to + * PATTERN_USER_DEFINED. + * @param mask the line pattern mask to be used. + */ + final void setPatternMask(int mask) { + initPatternMask(mask); + sendMessage(LINE_PATTERN_MASK_CHANGED, new Integer(mask)); + } + + /** + * Retrieves the pattern mask for this LineAttributes component object. + * @return the user-defined pattern mask + */ + final int getPatternMask() { + return linePatternMask; + } + + /** + * Sets the pattern mask scale factor for this LineAttributes + * component object. This is used when the linePattern attribute + * is set to PATTERN_USER_DEFINED. + * @param scaleFactor the scale factor of mask, clamp to [1, 15] + */ + final void initPatternScaleFactor(int scaleFactor) { + if (scaleFactor < 1) { + scaleFactor = 1; + } else if (scaleFactor > 15) { + scaleFactor = 15; + } + this.linePatternScaleFactor = scaleFactor; + } + + /** + * Sets the pattern mask scale factor for this LineAttributes + * component object and sends a message notifying the interested + * structures of change. This is used when the linePattern + * attribute is set to PATTERN_USER_DEFINED. + * @param scaleFactor the scale factor of mask, clamp to [1, 15] + */ + final void setPatternScaleFactor(int scaleFactor) { + initPatternScaleFactor(scaleFactor); + sendMessage(LINE_PATTERN_SCALEFACTOR_CHANGED, new Integer(scaleFactor)); + } + + /** + * Retrieves the pattern scale factor for this LineAttributes + * component object. + * @return the pattern mask scale factor + */ + final int getPatternScaleFactor() { + return linePatternScaleFactor; + } + + /** + * Creates and initializes a mirror object, point the mirror object + * to the retained object if the object is not editable + */ + synchronized void createMirrorObject() { + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror = this; + } else { + LineAttributesRetained mirrorLa = new LineAttributesRetained(); + mirrorLa.source = source; + mirrorLa.set(this); + mirror = mirrorLa; + } + } else { + ((LineAttributesRetained) mirror).set(this); + } + } + + + /** + * This (native) method updates the native context. + */ + native void updateNative(long ctx, + float lineWidth, int linePattern, + int linePatternMask, + int linePatternScaleFactor, + boolean lineAntialiasing); + + /** + * This method updates the native context. + */ + void updateNative(long ctx) { + updateNative(ctx, + lineWidth, linePattern, linePatternMask, + linePatternScaleFactor, lineAntialiasing); + } + + + /** + * Initializes a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + ((LineAttributesRetained)mirror).set(this); + } + + /** Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + + LineAttributesRetained mirrorLa = (LineAttributesRetained) mirror; + + if ((component & LINE_WIDTH_CHANGED) != 0) { + mirrorLa.lineWidth = ((Float)value).floatValue(); + } + else if ((component & LINE_PATTERN_CHANGED) != 0) { + mirrorLa.linePattern = ((Integer)value).intValue(); + } + else if ((component & LINE_AA_CHANGED) != 0) { + mirrorLa.lineAntialiasing = ((Boolean)value).booleanValue(); + } + else if ((component & LINE_PATTERN_MASK_CHANGED) != 0) { + mirrorLa.linePatternMask = ((Integer)value).intValue(); + } + else if ((component & LINE_PATTERN_SCALEFACTOR_CHANGED) != 0) + { + mirrorLa.linePatternScaleFactor = ((Integer)value).intValue(); + } + } + + + boolean equivalent(LineAttributesRetained lr) { + return ((lr != null) && + (lineWidth == lr.lineWidth) && + (linePattern == lr.linePattern) && + (lineAntialiasing == lr.lineAntialiasing) && + (linePatternMask == lr.linePatternMask) && + (linePatternScaleFactor == lr.linePatternScaleFactor)); + + } + + protected void set(LineAttributesRetained lr) { + super.set(lr); + lineWidth = lr.lineWidth; + linePattern = lr.linePattern; + linePatternScaleFactor = lr.linePatternScaleFactor; + linePatternMask = lr.linePatternMask; + lineAntialiasing = lr.lineAntialiasing; + } + + final void sendMessage(int attrMask, Object attr) { + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.LINEATTRIBUTES_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.LINEATTRIBUTES_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + + + } + void handleFrequencyChange(int bit) { + if (bit == LineAttributes.ALLOW_WIDTH_WRITE || + bit == LineAttributes.ALLOW_PATTERN_WRITE|| + bit == LineAttributes.ALLOW_ANTIALIASING_WRITE) { + setFrequencyChangeMask(bit, 0x1); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/LineStripArray.java b/src/classes/share/javax/media/j3d/LineStripArray.java new file mode 100644 index 0000000..8c7d415 --- /dev/null +++ b/src/classes/share/javax/media/j3d/LineStripArray.java @@ -0,0 +1,177 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The LineStripArray object draws an array of vertices as a set of + * connected line strips. An array of per-strip vertex counts specifies + * where the separate strips appear in the vertex array. + * For every strip in the set, each vertex, beginning with + * the second vertex in the array, defines a line segment to be drawn + * from the previous vertex to the current vertex. + */ + +public class LineStripArray extends GeometryStripArray { + + /** + * Package scoped default constructor used by cloneNodeComponent. + */ + LineStripArray() { + } + + /** + * Constructs an empty LineStripArray object with the specified + * number of vertices, vertex format, and + * array of per-strip vertex counts. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 2 + * or any element in the stripVertexCounts array is less than 2 + */ + public LineStripArray(int vertexCount, + int vertexFormat, + int stripVertexCounts[]) { + + super(vertexCount, vertexFormat, stripVertexCounts); + + if (vertexCount < 2 ) + throw new IllegalArgumentException(J3dI18N.getString("LineStripArray0")); + } + + /** + * Constructs an empty LineStripArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and + * array of per-strip vertex counts. + * + * @param vertexCount the number of vertex elements in this array<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 2 + * or any element in the stripVertexCounts array is less than 2 + * + * @since Java 3D 1.2 + */ + public LineStripArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int stripVertexCounts[]) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + stripVertexCounts); + + if (vertexCount < 2 ) + throw new IllegalArgumentException(J3dI18N.getString("LineStripArray0")); + } + + /** + * Creates the retained mode LineStripArrayRetained object that this + * LineStripArray object will point to. + */ + void createRetained() { + this.retained = new LineStripArrayRetained(); + this.retained.setSource(this); + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + LineStripArrayRetained rt = (LineStripArrayRetained) retained; + int stripcounts[] = new int[rt.getNumStrips()]; + rt.getStripVertexCounts(stripcounts); + int texSetCount = rt.getTexCoordSetCount(); + LineStripArray l; + if (texSetCount == 0) { + l = new LineStripArray(rt.getVertexCount(), + rt.getVertexFormat(), + stripcounts); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + l = new LineStripArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + stripcounts); + } + l.duplicateNodeComponent(this); + return l; + } +} diff --git a/src/classes/share/javax/media/j3d/LineStripArrayRetained.java b/src/classes/share/javax/media/j3d/LineStripArrayRetained.java new file mode 100644 index 0000000..80f1386 --- /dev/null +++ b/src/classes/share/javax/media/j3d/LineStripArrayRetained.java @@ -0,0 +1,463 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The LineStripArray object draws an array of vertices as a set of + * connected line strips. An array of per-strip vertex counts specifies + * where the separate strips appear in the vertex array. + * For every strip in the set, each vertex, beginning with + * the second vertex in the array, defines a line segment to be drawn + * from the previous vertex to the current vertex. + */ + +class LineStripArrayRetained extends GeometryStripArrayRetained { + + LineStripArrayRetained() { + this.geoType = GEO_TYPE_LINE_STRIP_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[2]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int j, end; + int i = 0; + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while(i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while (j < end) { + getVertexData(j++, pnts[1]); + + if (intersectLineAndRay(pnts[0], pnts[1], pickRay.origin, + pickRay.direction, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + Vector3d dir = + new Vector3d(pickSegment.end.x - pickSegment.start.x, + pickSegment.end.y - pickSegment.start.y, + pickSegment.end.z - pickSegment.start.z); + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while (j < end) { + getVertexData(j++, pnts[1]); + + if (intersectLineAndRay(pnts[0], pnts[1], + pickSegment.start, + dir, sdist, iPnt) && + (sdist[0] <= 1.0)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while (j < end) { + getVertexData(j++, pnts[1]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while (j < end) { + getVertexData(j++, pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while (j < end) { + getVertexData(j++, pnts[1]); + if (intersectBoundingPolytope(pnts, bpolytope, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while (j < end) { + getVertexData(j++, pnts[1]); + + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while (j < end) { + getVertexData(j++, pnts[1]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("LineStripArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + + } + + boolean intersect(Point3d[] pnts) { + int j, end; + Point3d[] points = new Point3d[2]; + double dist[] = new double[1]; + Vector3d dir; + int i = 0; + + points[0] = new Point3d(); + points[1] = new Point3d(); + + + + switch (pnts.length) { + case 3: + case 4: // Triangle, Quad + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + while (j < end) { + getVertexData(j++, points[1]); + if (intersectSegment(pnts, points[0], points[1], + dist, null)) { + return true; + } + points[0].set(points[1]); + } + } + break; + case 2: // Line + dir = new Vector3d(); + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + while (j < end) { + getVertexData(j++, points[1]); + dir.x = points[1].x - points[0].x; + dir.y = points[1].y - points[0].y; + dir.z = points[1].z - points[0].z; + if (intersectLineAndRay(pnts[0], pnts[1], + points[0], dir, dist, null) && + (dist[0] <= 1.0)) { + return true; + } + points[0].set(points[1]); + } + } + break; + case 1: // Point + dir = new Vector3d(); + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + while (j < end) { + getVertexData(j++, points[1]); + dir.x = points[1].x - points[0].x; + dir.y = points[1].y - points[0].y; + dir.z = points[1].z - points[0].z; + if (intersectPntAndRay(pnts[0], points[0], dir, + dist) && + (dist[0] <= 1.0)) { + return true; + } + points[0].set(points[1]); + } + } + break; + } + return false; + } + + boolean intersect(Transform3D thisToOtherVworld, + GeometryRetained geom) { + int i = 0; + int j, end; + Point3d[] pnts = new Point3d[2]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + thisToOtherVworld.transform(pnts[0]); + while (j < end) { + getVertexData(j++, pnts[1]); + thisToOtherVworld.transform(pnts[1]); + if (geom.intersect(pnts)) { + return true; + } + pnts[0].set(pnts[1]); + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + int i = 0; + int j, offset, end; + Point3d[] pnts = new Point3d[2]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + + + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while ( j < end) { + getVertexData(j++, pnts[1]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while ( j < end) { + getVertexData(j++, pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, null, + null)) { + return true; + } + pnts[0].set(pnts[1]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + while ( j < end) { + getVertexData(j++, pnts[1]); + if (intersectBoundingPolytope(pnts, bpolytope, + null, null)) { + return true; + } + pnts[0].set(pnts[1]); + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + + return false; + } + + // From Graphics Gems IV (pg5) and Graphics Gems II, Pg170 + void computeCentroid() { + int i = 0; + int j; + double length; + double totallength = 0; + int start, end; + boolean replaceVertex1; + Point3d pnt0 = new Point3d(); + Point3d pnt1 = new Point3d(); + + centroid.x = 0; + centroid.y = 0; + centroid.z = 0; + + + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnt0); + replaceVertex1 = true; + while (j < end) { + if (replaceVertex1) { + getVertexData(j++, pnt1); + replaceVertex1 = false; + } else { + getVertexData(j++, pnt0); + replaceVertex1 = true; + } + length = pnt0.distance(pnt1); + centroid.x += (pnt0.x + pnt1.x) * length; + centroid.y += (pnt0.y + pnt1.y) * length; + centroid.z += (pnt0.z + pnt1.z) * length; + totallength += length; + } + } + if (totallength != 0.0) { + length = 1.0/(2.0 * totallength); + centroid.x *= length; + centroid.y *= length; + centroid.z *= length; + } + } + + int getClassType() { + return LINE_TYPE; + } + +} diff --git a/src/classes/share/javax/media/j3d/LinearFog.java b/src/classes/share/javax/media/j3d/LinearFog.java new file mode 100644 index 0000000..3ccb35a --- /dev/null +++ b/src/classes/share/javax/media/j3d/LinearFog.java @@ -0,0 +1,232 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; + +/** + * The LinearFog leaf node defines fog distance parameters for + * linear fog. + * LinearFog extends the Fog node by adding a pair of distance values, + * in Z, at which the fog should start obscuring the scene and should maximally + * obscure the scene. + * <P> + * The front and back fog distances are defined in the local coordinate system of + * the node, but the actual fog equation will ideally take place in eye + * coordinates. + * <P> + * The linear fog blending factor, f, is computed as follows: + * <P><UL> + * f = backDistance - z / backDistance - frontDistance<P> + * where + * <ul>z is the distance from the viewpoint.<br> + * frontDistance is the distance at which fog starts obscuring objects.<br> + * backDistance is the distance at which fog totally obscurs objects. + * </ul><P></UL> + */ +public class LinearFog extends Fog { + /** + * Specifies that this LinearFog node allows read access to its distance + * information. + */ + public static final int + ALLOW_DISTANCE_READ = CapabilityBits.LINEAR_FOG_ALLOW_DISTANCE_READ; + + /** + * Specifies that this LinearFog node allows write access to its distance + * information. + */ + public static final int + ALLOW_DISTANCE_WRITE = CapabilityBits.LINEAR_FOG_ALLOW_DISTANCE_WRITE; + + /** + * Constructs a LinearFog node with default parameters. + * The default values are as follows: + * <ul> + * front distance : 0.1<br> + * back distance : 1.0<br> + * </ul> + */ + public LinearFog() { + // Just use the defaults + } + + /** + * Constructs a LinearFog node with the specified fog color. + * @param color the fog color + */ + public LinearFog(Color3f color) { + super(color); + } + + /** + * Constructs a LinearFog node with the specified fog color and distances. + * @param color the fog color + * @param frontDistance the front distance for the fog + * @param backDistance the back distance for the fog + */ + public LinearFog(Color3f color, double frontDistance, double backDistance) { + super(color); + ((LinearFogRetained)this.retained).initFrontDistance(frontDistance); + ((LinearFogRetained)this.retained).initBackDistance(backDistance); + } + + /** + * Constructs a LinearFog node with the specified fog color. + * @param r the red component of the fog color + * @param g the green component of the fog color + * @param b the blue component of the fog color + */ + public LinearFog(float r, float g, float b) { + super(r, g, b); + } + + /** + * Constructs a LinearFog node with the specified fog color and distances. + * @param r the red component of the fog color + * @param g the green component of the fog color + * @param b the blue component of the fog color + * @param frontDistance the front distance for the fog + * @param backDistance the back distance for the fog + */ + public LinearFog(float r, float g, float b, + double frontDistance, double backDistance) { + super(r, g, b); + ((LinearFogRetained)this.retained).initFrontDistance(frontDistance); + ((LinearFogRetained)this.retained).initBackDistance(backDistance); + } + + /** + * Sets front distance for fog. + * @param frontDistance the distance at which fog starts obscuring objects + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setFrontDistance(double frontDistance) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("LinearFog0")); + + if (isLive()) + ((LinearFogRetained)this.retained).setFrontDistance(frontDistance); + else + ((LinearFogRetained)this.retained).initFrontDistance(frontDistance); + + } + + /** + * Gets front distance for fog. + * @return the distance at which fog starts obscuring objects + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public double getFrontDistance() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("LinearFog1")); + + return ((LinearFogRetained)this.retained).getFrontDistance(); + } + + /** + * Sets back distance for fog. + * @param backDistance the distance at which fog totally obscurs objects + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setBackDistance(double backDistance) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("LinearFog0")); + if (isLive()) + ((LinearFogRetained)this.retained).setBackDistance(backDistance); + else + ((LinearFogRetained)this.retained).initBackDistance(backDistance); + + } + + /** + * Gets back distance for fog. + * @return the distance at which fog totally obscurs objects + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public double getBackDistance() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("LinearFog1")); + + return ((LinearFogRetained)this.retained).getBackDistance(); + } + + /** + * Creates the retained mode LinearFogRetained object that this + * LinearFog node will point to. + */ + void createRetained() { + this.retained = new LinearFogRetained(); + this.retained.setSource(this); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + LinearFog lf = new LinearFog(); + lf.duplicateNode(this, forceDuplicate); + return lf; + } + + + /** + * Copies all LinearFog information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + LinearFogRetained attr = (LinearFogRetained) originalNode.retained; + LinearFogRetained rt = (LinearFogRetained) retained; + + rt.initFrontDistance(attr.getFrontDistance()); + rt.initBackDistance(attr.getBackDistance()); + } +} diff --git a/src/classes/share/javax/media/j3d/LinearFogRetained.java b/src/classes/share/javax/media/j3d/LinearFogRetained.java new file mode 100644 index 0000000..f5b6204 --- /dev/null +++ b/src/classes/share/javax/media/j3d/LinearFogRetained.java @@ -0,0 +1,197 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; +import javax.vecmath.*; +import java.util.ArrayList; + +/** + * The LinearFog leaf node defines distance parameters for + * linear fog. + */ +class LinearFogRetained extends FogRetained { + /** + * Fog front and back distance + */ + double frontDistance = 0.1; + double backDistance = 1.0; + double localToVworldScale = 1.0; + double frontDistanceInEc; + double backDistanceInEc; + double vworldToCoexistenceScale; + + // dirty bits for LinearFog + static final int FRONT_DISTANCE_CHANGED = FogRetained.LAST_DEFINED_BIT << 1; + static final int BACK_DISTANCE_CHANGED = FogRetained.LAST_DEFINED_BIT << 2; + + LinearFogRetained() { + this.nodeType = NodeRetained.LINEARFOG; + } + + /** + * Initializes front distance for fog before the object is live + */ + void initFrontDistance(double frontDistance){ + this.frontDistance = frontDistance; + } + + /** + * Sets front distance for fog and sends a message + */ + void setFrontDistance(double frontDistance){ + this.frontDistance = frontDistance; + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.FOG_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(FRONT_DISTANCE_CHANGED); + createMessage.args[2] = new Double(frontDistance); + VirtualUniverse.mc.processMessage(createMessage); + + } + + /** + * Gets front distance for fog + */ + double getFrontDistance(){ + return this.frontDistance; + } + + /** + * Initializes back distance for fog + */ + void initBackDistance(double backDistance){ + this.backDistance = backDistance; + } + /** + * Sets back distance for fog + */ + void setBackDistance(double backDistance){ + this.backDistance = backDistance; + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.FOG_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(BACK_DISTANCE_CHANGED); + createMessage.args[2] = new Double(backDistance); + VirtualUniverse.mc.processMessage(createMessage); + } + + /** + * Gets back distance for fog + */ + double getBackDistance(){ + return this.backDistance; + } + /** + * This method and its native counterpart update the native context + * fog values. + */ + native void update(long ctx, float red, float green, float blue, + double fdist, double bdist); + + void update(long ctx, double scale) { + validateDistancesInEc(scale); + update(ctx, color.x, color.y, color.z, frontDistanceInEc, backDistanceInEc); + } + + + + void setLive(SetLiveState s) { + GroupRetained group; + + super.setLive(s); + + // Initialize the mirror object, this needs to be done, when + // renderBin is not accessing any of the fields + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.FOG_CHANGED; + createMessage.args[0] = this; + // a snapshot of all attributes that needs to be initialized + // in the mirror object + createMessage.args[1]= new Integer(INIT_MIRROR); + ArrayList addScopeList = new ArrayList(); + for (int i = 0; i < scopes.size(); i++) { + group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.addAllNodesForScopedFog(mirrorFog, addScopeList, tempKey); + } + Object[] scopeInfo = new Object[2]; + scopeInfo[0] = ((scopes.size() > 0) ? Boolean.TRUE:Boolean.FALSE); + scopeInfo[1] = addScopeList; + createMessage.args[2] = scopeInfo; + Color3f clr = new Color3f(color); + createMessage.args[3] = clr; + + Object[] obj = new Object[6]; + obj[0] = boundingLeaf; + obj[1] = (regionOfInfluence != null?regionOfInfluence.clone():null); + obj[2] = (inBackgroundGroup? Boolean.TRUE:Boolean.FALSE); + obj[3] = geometryBackground; + obj[4] = new Double(frontDistance); + obj[5] = new Double(backDistance); + + createMessage.args[4] = obj; + VirtualUniverse.mc.processMessage(createMessage); + + } + + + // The update Object function. + // Note : if you add any more fields here , you need to update + // updateFog() in RenderingEnvironmentStructure + synchronized void updateMirrorObject(Object[] objs) { + + int component = ((Integer)objs[1]).intValue(); + Transform3D trans; + + if ((component & FRONT_DISTANCE_CHANGED) != 0) + ((LinearFogRetained)mirrorFog).frontDistance = ((Double)objs[2]).doubleValue(); + if ((component & BACK_DISTANCE_CHANGED) != 0) + ((LinearFogRetained)mirrorFog).backDistance = ((Double)objs[2]).doubleValue(); + if ((component & INIT_MIRROR) != 0) { + ((LinearFogRetained)mirrorFog).frontDistance = ((Double)((Object[])objs[4])[4]).doubleValue(); + ((LinearFogRetained)mirrorFog).backDistance = ((Double)((Object[])objs[4])[5]).doubleValue(); + + } + ((LinearFogRetained)mirrorFog).localToVworldScale = + getLastLocalToVworld().getDistanceScale(); + + + super.updateMirrorObject(objs); + } + + /** + * Scale distances from local to eye coordinate + */ + + void validateDistancesInEc(double vworldToCoexistenceScale) { + // vworldToCoexistenceScale can be used here since + // CoexistenceToEc has a unit scale + double localToEcScale = localToVworldScale * vworldToCoexistenceScale; + + frontDistanceInEc = frontDistance * localToEcScale; + backDistanceInEc = backDistance * localToEcScale; + } + + // Called on mirror object + void updateTransformChange() { + super.updateTransformChange(); + localToVworldScale = sgFog.getLastLocalToVworld().getDistanceScale(); + } +} diff --git a/src/classes/share/javax/media/j3d/Link.java b/src/classes/share/javax/media/j3d/Link.java new file mode 100644 index 0000000..089f15f --- /dev/null +++ b/src/classes/share/javax/media/j3d/Link.java @@ -0,0 +1,140 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A Link leaf node allows an application to reference a shared graph, + * rooted by a SharedGroup node, from within a branch graph or another + * shared graph. + * Any number of Link nodes can refer to the same SharedGroup node. + */ + +public class Link extends Leaf { + /** + * For Link nodes, specifies that the node allows access to + * its object's SharedGroup information. + */ + public static final int + ALLOW_SHARED_GROUP_READ = CapabilityBits.LINK_ALLOW_SHARED_GROUP_READ; + + /** + * For Link nodes, specifies that the node allows writing + * its object's SharedGroup information. + */ + public static final int + ALLOW_SHARED_GROUP_WRITE = CapabilityBits.LINK_ALLOW_SHARED_GROUP_WRITE; + + /** + * Constructs a Link node object that does not yet point to a + * SharedGroup node. + */ + public Link() { + } + + /** + * Constructs a Link node object that points to the specified + * SharedGroup node. + * @param sharedGroup the SharedGroup node + */ + public Link(SharedGroup sharedGroup) { + ((LinkRetained)this.retained).setSharedGroup(sharedGroup); + } + + /** + * Creates the retained mode LinkRetained object that this + * Link object will point to. + */ + void createRetained() { + this.retained = new LinkRetained(); + this.retained.setSource(this); + } + + /** + * Sets the node's SharedGroup reference. + * @param sharedGroup the SharedGroup node to reference + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setSharedGroup(SharedGroup sharedGroup) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SHARED_GROUP_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Link0")); + ((LinkRetained)this.retained).setSharedGroup(sharedGroup); + } + + /** + * Retrieves the node's SharedGroup reference. + * @return the SharedGroup node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public SharedGroup getSharedGroup() { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SHARED_GROUP_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Link1")); + return ((LinkRetained)this.retained).getSharedGroup(); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * <br> + * The cloned Link node will refer to the same + * SharedGroup as the original node. The SharedGroup referred to by + * this Link node will not be cloned. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Link l = new Link(); + l.duplicateNode(this, forceDuplicate); + return l; + } + + /** + * Copies all Link information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + ((LinkRetained) retained).setSharedGroup( + ((LinkRetained) originalNode.retained).getSharedGroup()); + } +} diff --git a/src/classes/share/javax/media/j3d/LinkRetained.java b/src/classes/share/javax/media/j3d/LinkRetained.java new file mode 100644 index 0000000..84410a7 --- /dev/null +++ b/src/classes/share/javax/media/j3d/LinkRetained.java @@ -0,0 +1,330 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.*; + +/** + * A Link leaf node consisting of a reference to a SharedGroup node. + */ + +class LinkRetained extends LeafRetained { + /** + * The SharedGroup component of the link node. + */ + SharedGroupRetained sharedGroup; + + static String plus = "+"; + + // This is used when setLive to check for cycle scene graph + boolean visited = false; + + LinkRetained() { + this.nodeType = NodeRetained.LINK; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + /** + * Sets the SharedGroup reference. + * @param sharedGroup the SharedGroup node + */ + void setSharedGroup(SharedGroup sharedGroup) { + // Note that it is possible that the sharedGroup pass + // in already link to another link and live. + HashKey newKeys[] = null; + boolean abort = false; + + if (source.isLive()) { + // bug 4370407: if sharedGroup is a parent, then don't do anything + if (sharedGroup != null) { + synchronized(universe.sceneGraphLock) { + NodeRetained pa; + for (pa = parent; pa != null; pa = pa.parent) { + if (pa == (NodeRetained)sharedGroup.retained) { + abort = true; + throw new SceneGraphCycleException(J3dI18N.getString("LinkRetained1")); + } + } + } + if (abort) + return; + } + + newKeys = getNewKeys(locale.nodeId, localToVworldKeys); + + if (this.sharedGroup != null) { + ((GroupRetained) parent).checkClearLive(this.sharedGroup, + newKeys, true, null, + 0, 0, this); + this.sharedGroup.parents.removeElement(this); + } + } + + if (sharedGroup != null) { + this.sharedGroup = + (SharedGroupRetained)sharedGroup.retained; + } else { + this.sharedGroup = null; + } + + if (source.isLive() && (sharedGroup != null)) { + + this.sharedGroup.parents.addElement(this); + visited = true; + try { + int ci = ((GroupRetained) parent).indexOfChild((Node)this.sharedGroup.source); + ((GroupRetained) parent).checkSetLive(this.sharedGroup, ci, + newKeys, true, null, + 0, this); + } catch (SceneGraphCycleException e) { + throw e; + } finally { + visited = false; + } + } + + } + + /** + * Retrieves the SharedGroup reference. + * @return the SharedGroup node + */ + SharedGroup getSharedGroup() { + return (sharedGroup != null ? + (SharedGroup)this.sharedGroup.source : null); + } + + void computeCombineBounds(Bounds bounds) { + + if (boundsAutoCompute) { + sharedGroup.computeCombineBounds(bounds); + } else { + // Should this be lock too ? ( MT safe ? ) + synchronized(localBounds) { + bounds.combine(localBounds); + } + } + } + + + /** + * Gets the bounding object of a node. + * @return the node's bounding object + */ + Bounds getBounds() { + return (boundsAutoCompute ? + (Bounds)sharedGroup.getBounds().clone() : + super.getBounds()); + } + + + /** + * assign a name to this node when it is made live. + */ + void setLive(SetLiveState s) { + + super.doSetLive(s); + + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("LinkRetained0")); + } + + if (nodeId == null) { + nodeId = universe.getNodeId(); + } + + if (sharedGroup != null) { + this.sharedGroup.parents.addElement(this); + HashKey newKeys[] = getNewKeys(s.locale.nodeId, s.keys); + HashKey oldKeys[] = s.keys; + s.keys = newKeys; + s.inSharedGroup = true; + if (visited) { + throw new SceneGraphCycleException(J3dI18N.getString("LinkRetained1")); + } + visited = true; + try { + this.sharedGroup.setLive(s); + } catch (SceneGraphCycleException e) { + throw e; + } finally { + visited = false; + } + + s.inSharedGroup = inSharedGroup; + s.keys = oldKeys; + + localBounds.setWithLock(this.sharedGroup.localBounds); + } + + super.markAsLive(); + } + + void setNodeData(SetLiveState s) { + + super.setNodeData(s); + + // add this node to parentTransformLink's childTransformLink + if (s.childTransformLinks != null) { + // do not duplicate shared nodes + synchronized(s.childTransformLinks) { + if(!inSharedGroup || !s.childTransformLinks.contains(this)) { + s.childTransformLinks.add(this); + } + } + } + + // add this node to parentSwitchLink's childSwitchLink + if (s.childSwitchLinks != null) { + if(!inSharedGroup || + // only add if not already added in sharedGroup + !s.childSwitchLinks.contains(this)) { + s.childSwitchLinks.add(this); + } + } + } + + + void recombineAbove() { + localBounds.setWithLock(sharedGroup.localBounds); + parent.recombineAbove(); + } + + /** + * assign a name to this node when it is made live. + */ + void clearLive(SetLiveState s) { + + if (sharedGroup != null) { + HashKey newKeys[] = getNewKeys(s.locale.nodeId, s.keys); + super.clearLive(s); + HashKey oldKeys[] = s.keys; + s.keys = newKeys; + s.inSharedGroup = true; + this.sharedGroup.parents.removeElement(this); + this.sharedGroup.clearLive(s); + s.inSharedGroup = inSharedGroup; + s.keys = oldKeys; + } else { + super.clearLive(s); + } + } + + void removeNodeData(SetLiveState s) { + if(refCount <= 0) { + // either not in sharedGroup or last instance in sharedGroup + // remove this node from parentTransformLink's childTransformLink + if (parentTransformLink != null) { + ArrayList obj; + if (parentTransformLink instanceof TransformGroupRetained) { + obj = ((TransformGroupRetained) + parentTransformLink).childTransformLinks; + } else { + obj = ((SharedGroupRetained) + parentTransformLink).childTransformLinks; + } + synchronized(obj) { + obj.remove(this); + } + } + + // remove this node from parentSwitchLink's childSwitchLink + if (parentSwitchLink != null) { + ArrayList switchLinks; + for(int i=0; i<parentSwitchLink.childrenSwitchLinks.size();i++){ + switchLinks = (ArrayList) + parentSwitchLink.childrenSwitchLinks.get(i); + if (switchLinks.contains(this)) { + switchLinks.remove(this); + break; + } + } + } + } + super.removeNodeData(s); + } + + + void updatePickable(HashKey keys[], boolean pick[]) { + super.updatePickable(keys, pick); + + if (sharedGroup != null) { + HashKey newKeys[] = getNewKeys(locale.nodeId, keys); + sharedGroup.updatePickable(newKeys, pick); + } + } + + void updateCollidable(HashKey keys[], boolean collide[]) { + super.updateCollidable(keys, collide); + + if (sharedGroup != null) { + HashKey newKeys[] = getNewKeys(locale.nodeId, keys); + sharedGroup.updateCollidable(newKeys, collide); + } + } + + void setBoundsAutoCompute(boolean autoCompute) { + super.setBoundsAutoCompute(autoCompute); + if (!autoCompute) { + localBounds = getBounds(); + } + } + + void setCompiled() { + super.setCompiled(); + if (sharedGroup != null) { + sharedGroup.setCompiled(); + } + } + + void compile(CompileState compState) { + + super.compile(compState); + + // TODO: for now keep the static transform in the parent tg + compState.keepTG = true; + + // don't remove this group node + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numLinks++; + } + } + + HashKey[] getNewKeys(String localeNodeId, HashKey oldKeys[]) { + HashKey newKeys[]; + + if (!inSharedGroup) { + newKeys = new HashKey[1]; + newKeys[0] = new HashKey(localeNodeId); + newKeys[0].append(plus + nodeId); + } else { + // Need to append this link node id to all keys passed in. + newKeys = new HashKey[oldKeys.length]; + for (int i=oldKeys.length-1; i>=0; i--) { + newKeys[i] = new HashKey(oldKeys[i].toString() + + plus + nodeId); + } + } + return newKeys; + } + + void searchGeometryAtoms(UnorderList list) { + if (sharedGroup != null) { + sharedGroup.searchGeometryAtoms(list); + } + } +} diff --git a/src/classes/share/javax/media/j3d/Locale.java b/src/classes/share/javax/media/j3d/Locale.java new file mode 100644 index 0000000..68a1cdc --- /dev/null +++ b/src/classes/share/javax/media/j3d/Locale.java @@ -0,0 +1,656 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Vector; +import java.util.Enumeration; +import java.util.ArrayList; + +/** + * A Locale object defines a high-resolution position within a + * VirtualUniverse, and serves as a container for a collection of + * BranchGroup-rooted subgraphs (branch graphs), at that position. + * Objects within a Locale are defined using standard double-precision + * coordinates, relative to the origin of the Locale. This origin + * defines the Virtual World coordinate system for that Locale. + * <p> + * A Locale object defines methods to set and get its high-resolution + * coordinates, and methods to add, remove, and enumerate the branch + * graphs. + * + * @see VirtualUniverse + * @see HiResCoord + * @see BranchGroup + */ + +public class Locale extends Object { + + /** + * The virtual universe that this Locale object is contained within. + */ + VirtualUniverse universe; + + /** + * The high resolution coordinate associated with this Locale object. + */ + HiResCoord hiRes; + + /** + * List of BranchGroup objects included in this Locale + */ + Vector branchGroups = new Vector(); + + // locale's identifier + String nodeId = null; + + /** + * Constructs and initializes a new high resolution Locale object + * located at (0, 0, 0). + * @param universe the virtual universe that will contain this + * Locale object + */ + public Locale(VirtualUniverse universe) { + this.universe = universe; + this.universe.addLocale(this); + this.hiRes = new HiResCoord(); + nodeId = universe.getNodeId(); + } + + /** + * Constructs and initializes a new high resolution Locale object + * from the parameters provided. + * @param universe the virtual universe that will contain this + * Locale object + * @param x an eight element array specifying the x position + * @param y an eight element array specifying the y position + * @param z an eight element array specifying the z position + */ + public Locale(VirtualUniverse universe, int[] x, int[] y, int[] z) { + this.universe = universe; + this.universe.addLocale(this); + this.hiRes = new HiResCoord(x, y, z); + nodeId = universe.getNodeId(); + } + + /** + * Constructs and initializes a new high resolution Locale object + * at the location specified by the HiResCoord argument. + * @param universe the virtual universe that will contain this + * Locale object + * @param hiRes the HiRes coordinate to use in creating this Locale + */ + public Locale(VirtualUniverse universe, HiResCoord hiRes) { + this.universe = universe; + this.universe.addLocale(this); + this.hiRes = new HiResCoord(hiRes); + nodeId = universe.getNodeId(); + } + + /** + * Retrieves the virtual universe within which this Locale object + * is contained. A null reference indicates that this + * Locale has been removed from its VirtualUniverse. + * @return the virtual universe within which this Locale object + * is contained. + */ + public VirtualUniverse getVirtualUniverse() { + return universe; + } + + /** + * Sets the HiRes coordinate of this Locale to the location + * specified by the parameters provided. + * @param x an eight element array specifying the x position + * @param y an eight element array specifying the y position + * @param z an eight element array specifying the z position + */ + public void setHiRes(int[] x, int[] y, int[] z) { + this.hiRes.setHiResCoord(x, y, z); + } + + /** + * Sets the HiRes coordinate of this Locale + * to the location specified by the HiRes argument. + * @param hiRes the HiRes coordinate specifying this node's new location + */ + public void setHiRes(HiResCoord hiRes) { + this.hiRes.setHiResCoord(hiRes); + } + + /** + * Returns this node's HiResCoord. + * @param hiRes a HiResCoord object that will receive the + * HiRes coordinate of this Locale node + */ + public void getHiRes(HiResCoord hiRes) { + this.hiRes.getHiResCoord(hiRes); + } + + /** + * Add a new branch graph rooted at BranchGroup to + * the list of branch graphs. + * @param branchGroup root of the branch graph to be added + * @exception IllegalStateException if this Locale has been + * removed from its VirtualUniverse. + * @exception MultipleParentException if the specified BranchGroup node + * is already live. + */ + public void addBranchGraph(BranchGroup branchGroup){ + if (universe == null) { + throw new IllegalStateException(J3dI18N.getString("Locale4")); + } + + // if the BranchGroup already has a parent, or has already been + // added to a locale, throw MultipleParentException + if ((((BranchGroupRetained)branchGroup.retained).parent != null) || + (branchGroup.isLive())) { + throw new MultipleParentException(J3dI18N.getString("Locale0")); + } + + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + doAddBranchGraph(branchGroup); + universe.setLiveState.reset(this); + } + universe.waitForMC(); + } + + // The method that does the work once the lock is acquired. + void doAddBranchGraph(BranchGroup branchGroup) { + BranchGroupRetained bgr = (BranchGroupRetained)branchGroup.retained; + J3dMessage createMessage; + SetLiveState s = universe.setLiveState; + + // bgr.setLocale(this); + + // addElement needs to precede setLive or else any liveness checks + // in the initialize() call of a user behavior (ie, calling collision + // or picking constructor with a SceneGraphPath) will fail + // when SceneGraphPath.validate() attempts to verify that + // the proper Locale is associated with that SceneGraphPath + bgr.attachedToLocale = true; + branchGroups.addElement(branchGroup); + s.reset(this); + s.currentTransforms[0] = new Transform3D[2]; + s.currentTransforms[0][0] = new Transform3D(); + s.currentTransforms[0][1] = new Transform3D(); + s.currentTransformsIndex[0] = new int[2]; + s.currentTransformsIndex[0][0] = 0; + s.currentTransformsIndex[0][1] = 0; + + s.localToVworld = s.currentTransforms; + s.localToVworldIndex = s.currentTransformsIndex; + + s.branchGroupPaths = new ArrayList(); + s.branchGroupPaths.add(new BranchGroupRetained[0]); + + s.orderedPaths = new ArrayList(1); + s.orderedPaths.add(new OrderedPath()); + + s.switchStates = new ArrayList(1); + s.switchStates.add(new SwitchState(false)); + + bgr.setLive(s); + + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER| J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.type = J3dMessage.ORDERED_GROUP_INSERTED; + createMessage.universe = universe; + createMessage.args[0] = s.ogList.toArray(); + createMessage.args[1] = s.ogChildIdList.toArray(); + createMessage.args[2] = s.ogOrderedIdList.toArray(); + createMessage.args[3] = s.ogCIOList.toArray(); + createMessage.args[4] = s.ogCIOTableList.toArray(); + + VirtualUniverse.mc.processMessage(createMessage); + + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.type = J3dMessage.VIEWSPECIFICGROUP_INIT; + createMessage.universe = universe; + createMessage.args[0] = s.changedViewGroup; + createMessage.args[1] = s.changedViewList; + createMessage.args[2] = s.keyList; + VirtualUniverse.mc.processMessage(createMessage); + + + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = s.notifyThreads; + createMessage.type = J3dMessage.INSERT_NODES; + createMessage.universe = universe; + createMessage.args[0] = s.nodeList.toArray(); + createMessage.args[1] = null; + createMessage.args[2] = null; + if (s.viewScopedNodeList != null) { + createMessage.args[3] = s.viewScopedNodeList; + createMessage.args[4] = s.scopedNodesViewList; + } + VirtualUniverse.mc.processMessage(createMessage); + + int sz = s.behaviorNodes.size(); + for (int i=0; i< sz; i++) { + BehaviorRetained b; + b = (BehaviorRetained)s.behaviorNodes.get(i); + b.executeInitialize(); + } + + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_BEHAVIOR; + createMessage.type = J3dMessage.BEHAVIOR_ACTIVATE; + createMessage.universe = universe; + VirtualUniverse.mc.processMessage(createMessage); + + // Free up memory. + s.reset(null); + } + + /** + * Removes a branch graph rooted at BranchGroup from + * the list of branch graphs. + * @param branchGroup root of the branch graph to be removed + * @exception IllegalStateException if this Locale has been + * removed from its VirtualUniverse. + * @exception CapabilityNotSetException if the ALLOW_DETACH capability is + * not set in the specified BranchGroup node. + */ + public void removeBranchGraph(BranchGroup branchGroup){ + if (universe == null) { + throw new IllegalStateException(J3dI18N.getString("Locale4")); + } + + if (! branchGroup.getCapability(BranchGroup.ALLOW_DETACH)) { + throw new CapabilityNotSetException(J3dI18N.getString("Locale1")); + } + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + doRemoveBranchGraph(branchGroup, null, 0); + universe.setLiveState.reset(this); + } + universe.waitForMC(); + } + + + // Method to remove all branch graphs from this Locale and remove + // this Locale from the VirtualUniverse + void removeFromUniverse() { + if (branchGroups.size() > 0) { + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + // Make a copy of the branchGroups list so that we can safely + // iterate over it. + Object[] bg = branchGroups.toArray(); + for (int i = 0; i < bg.length; i++) { + doRemoveBranchGraph((BranchGroup)bg[i], null, 0); + } + } + // Put after sceneGraphLock to prevent deadlock + universe.waitForMC(); + } + + // free nodeId + if (nodeId != null) { + universe.nodeIdFreeList.addElement(nodeId); + nodeId = null; + } + + // Set universe pointer to null, indicating that this Locale + // has been removed from its universe + universe = null; + } + + + // The method that does the work once the lock is acquired. + void doRemoveBranchGraph(BranchGroup branchGroup, + J3dMessage messages[], int startIndex) { + + BranchGroupRetained bgr = (BranchGroupRetained)branchGroup.retained; + J3dMessage destroyMessage; + + if (!branchGroup.isLive()) + return; + bgr.attachedToLocale = false; + branchGroups.removeElement(branchGroup); + universe.setLiveState.reset(this); + bgr.clearLive(universe.setLiveState); + bgr.setParent(null); + bgr.setLocale(null); + + if (messages == null) { + destroyMessage = VirtualUniverse.mc.getMessage(); + } else { + destroyMessage = messages[startIndex++]; + } + destroyMessage.threads = J3dThread.UPDATE_RENDER| J3dThread.UPDATE_RENDERING_ENVIRONMENT; + destroyMessage.type = J3dMessage.ORDERED_GROUP_REMOVED; + destroyMessage.universe = universe; + destroyMessage.args[0] = universe.setLiveState.ogList.toArray(); + destroyMessage.args[1] = universe.setLiveState.ogChildIdList.toArray(); + destroyMessage.args[3] = universe.setLiveState.ogCIOList.toArray(); + destroyMessage.args[4] = universe.setLiveState.ogCIOTableList.toArray(); + + if (messages == null) { + VirtualUniverse.mc.processMessage(destroyMessage); + destroyMessage = VirtualUniverse.mc.getMessage(); + } else { + destroyMessage = messages[startIndex++]; + } + destroyMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + destroyMessage.type = J3dMessage.VIEWSPECIFICGROUP_CLEAR; + destroyMessage.universe = universe; + destroyMessage.args[0] = universe.setLiveState.changedViewGroup; + destroyMessage.args[1] = universe.setLiveState.keyList; + + if (messages == null) { + VirtualUniverse.mc.processMessage(destroyMessage); + destroyMessage = VirtualUniverse.mc.getMessage(); + } else { + destroyMessage = messages[startIndex++]; + } + + destroyMessage.threads = universe.setLiveState.notifyThreads; + destroyMessage.type = J3dMessage.REMOVE_NODES; + destroyMessage.universe = universe; + destroyMessage.args[0] = universe.setLiveState.nodeList.toArray(); + if (universe.setLiveState.viewScopedNodeList != null) { + destroyMessage.args[3] = universe.setLiveState.viewScopedNodeList; + destroyMessage.args[4] = universe.setLiveState.scopedNodesViewList; + } + if (messages == null) { + VirtualUniverse.mc.processMessage(destroyMessage); + } else { + destroyMessage = messages[startIndex++]; + } + + if (universe.isEmpty()) { + VirtualUniverse.mc.postRequest(MasterControl.EMPTY_UNIVERSE, + universe); + } + universe.setLiveState.reset(null); // cleanup memory + } + + /** + * Replaces the branch graph rooted at oldGroup in the list of + * branch graphs with the branch graph rooted at + * newGroup. + * @param oldGroup root of the branch graph to be replaced. + * @param newGroup root of the branch graph that will replace the old + * branch graph. + * @exception IllegalStateException if this Locale has been + * removed from its VirtualUniverse. + * @exception CapabilityNotSetException if the ALLOW_DETACH capability is + * not set in the old BranchGroup node. + * @exception MultipleParentException if the new BranchGroup node + * is already live. + */ + public void replaceBranchGraph(BranchGroup oldGroup, + BranchGroup newGroup){ + + if (universe == null) { + throw new IllegalStateException(J3dI18N.getString("Locale4")); + } + + if (! oldGroup.getCapability(BranchGroup.ALLOW_DETACH)) { + throw new CapabilityNotSetException(J3dI18N.getString("Locale1")); + } + + if (((BranchGroupRetained)newGroup.retained).parent != null) { + throw new MultipleParentException(J3dI18N.getString("Locale3")); + } + universe.resetWaitMCFlag(); + synchronized (universe.sceneGraphLock) { + doReplaceBranchGraph(oldGroup, newGroup); + universe.setLiveState.reset(this); + } + universe.waitForMC(); + } + + // The method that does the work once the lock is acquired. + void doReplaceBranchGraph(BranchGroup oldGroup, + BranchGroup newGroup){ + BranchGroupRetained obgr = (BranchGroupRetained)oldGroup.retained; + BranchGroupRetained nbgr = (BranchGroupRetained)newGroup.retained; + J3dMessage createMessage; + J3dMessage destroyMessage; + + + branchGroups.removeElement(oldGroup); + obgr.attachedToLocale = false; + universe.setLiveState.reset(this); + obgr.clearLive(universe.setLiveState); + + destroyMessage = VirtualUniverse.mc.getMessage(); + + destroyMessage.threads = J3dThread.UPDATE_RENDER| J3dThread.UPDATE_RENDERING_ENVIRONMENT; + destroyMessage.type = J3dMessage.ORDERED_GROUP_REMOVED; + destroyMessage.universe = universe; + destroyMessage.args[0] = universe.setLiveState.ogList.toArray(); + destroyMessage.args[1] = universe.setLiveState.ogChildIdList.toArray(); + destroyMessage.args[3] = universe.setLiveState.ogCIOList.toArray(); + destroyMessage.args[4] = universe.setLiveState.ogCIOTableList.toArray(); + VirtualUniverse.mc.processMessage(destroyMessage); + + destroyMessage = VirtualUniverse.mc.getMessage(); + destroyMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + destroyMessage.type = J3dMessage.VIEWSPECIFICGROUP_CLEAR; + destroyMessage.universe = universe; + destroyMessage.args[0] = universe.setLiveState.changedViewGroup; + destroyMessage.args[1] = universe.setLiveState.keyList; + VirtualUniverse.mc.processMessage(destroyMessage); + + + destroyMessage = VirtualUniverse.mc.getMessage(); + destroyMessage.threads = universe.setLiveState.notifyThreads; + destroyMessage.type = J3dMessage.REMOVE_NODES; + destroyMessage.universe = universe; + destroyMessage.args[0] = universe.setLiveState.nodeList.toArray(); + VirtualUniverse.mc.processMessage(destroyMessage); + + branchGroups.addElement(newGroup); + nbgr.attachedToLocale = true; + universe.setLiveState.reset(this); + universe.setLiveState.currentTransforms[0] = new Transform3D[2]; + universe.setLiveState.currentTransforms[0][0] = new Transform3D(); + universe.setLiveState.currentTransforms[0][1] = new Transform3D(); + universe.setLiveState.currentTransformsIndex[0] = new int[2]; + universe.setLiveState.currentTransformsIndex[0][0] = 0; + universe.setLiveState.currentTransformsIndex[0][1] = 0; + + universe.setLiveState.localToVworld = + universe.setLiveState.currentTransforms; + universe.setLiveState.localToVworldIndex = + universe.setLiveState.currentTransformsIndex; + + universe.setLiveState.branchGroupPaths = new ArrayList(); + universe.setLiveState.branchGroupPaths.add(new BranchGroupRetained[0]); + + universe.setLiveState.orderedPaths = new ArrayList(1); + universe.setLiveState.orderedPaths.add(new OrderedPath()); + + universe.setLiveState.switchStates = new ArrayList(1); + universe.setLiveState.switchStates.add(new SwitchState(false)); + + nbgr.setLive(universe.setLiveState); + + + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER| J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.type = J3dMessage.ORDERED_GROUP_INSERTED; + createMessage.universe = universe; + createMessage.args[0] = universe.setLiveState.ogList.toArray(); + createMessage.args[1] = universe.setLiveState.ogChildIdList.toArray(); + createMessage.args[2] = universe.setLiveState.ogOrderedIdList.toArray(); + createMessage.args[3] = universe.setLiveState.ogCIOList.toArray(); + createMessage.args[4] = universe.setLiveState.ogCIOTableList.toArray(); + VirtualUniverse.mc.processMessage(createMessage); + + // TODO: make these two into one message + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = universe.setLiveState.notifyThreads; + createMessage.type = J3dMessage.INSERT_NODES; + createMessage.universe = universe; + createMessage.args[0] = universe.setLiveState.nodeList.toArray(); + createMessage.args[1] = null; + createMessage.args[2] = null; + if (universe.setLiveState.viewScopedNodeList != null) { + createMessage.args[3] = universe.setLiveState.viewScopedNodeList; + createMessage.args[4] = universe.setLiveState.scopedNodesViewList; + } + VirtualUniverse.mc.processMessage(createMessage); + + Object behaviorNodes[] = universe.setLiveState.behaviorNodes.toArray(); + + if (universe.isEmpty()) { + VirtualUniverse.mc.postRequest(MasterControl.EMPTY_UNIVERSE, + universe); + } + + for (int i=0; i< behaviorNodes.length; i++) { + ((BehaviorRetained) behaviorNodes[i]).executeInitialize(); + } + + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_BEHAVIOR; + createMessage.type = J3dMessage.BEHAVIOR_ACTIVATE; + createMessage.universe = universe; + VirtualUniverse.mc.processMessage(createMessage); + + + // Free up memory. + universe.setLiveState.reset(null); + + + } + + /** + * Get number of branch graphs in this Locale. + * @return number of branch graphs in this Locale. + */ + public int numBranchGraphs(){ + return branchGroups.size(); + } + + /** + * Gets an Enumeration object of all branch graphs in this Locale. + * @return an Enumeration object of all branch graphs. + * @exception IllegalStateException if this Locale has been + * removed from its VirtualUniverse. + */ + public Enumeration getAllBranchGraphs(){ + if (universe == null) { + throw new IllegalStateException(J3dI18N.getString("Locale4")); + } + + return branchGroups.elements(); + } + + /** + * Returns an array referencing all the items that are pickable below this + * <code>Locale</code> that intersect with PickShape. + * The resultant array is unordered. + * + * @param pickShape the description of this picking volume or area. + * + * @exception IllegalStateException if this Locale has been + * removed from its VirtualUniverse. + * + * @see BranchGroup#pickAll + */ + public SceneGraphPath[] pickAll( PickShape pickShape ) { + if (universe == null) { + throw new IllegalStateException(J3dI18N.getString("Locale4")); + } + + return Picking.pickAll( this, pickShape ); + } + + + /** + * Returns a sorted array of references to all the Pickable items + * that intersect with the pickShape. Element [0] references the + * item closest to <i>origin</i> of PickShape successive array + * elements are further from the <i>origin</i> + * <br> + * NOTE: If pickShape is of type PickBounds, the resulting array + * is unordered. + * + * @param pickShape the description of this picking volume or area. + * + * @exception IllegalStateException if this Locale has been + * removed from its VirtualUniverse. + * + * @see BranchGroup#pickAllSorted + */ + public SceneGraphPath[] pickAllSorted( PickShape pickShape ) { + if (universe == null) { + throw new IllegalStateException(J3dI18N.getString("Locale4")); + } + + return Picking.pickAllSorted( this, pickShape ); + } + + + /** + * Returns a SceneGraphPath which references the pickable item + * which is closest to the origin of <code>pickShape</code>. + * <br> + * NOTE: If pickShape is of type PickBounds, the return is any + * pickable node below this Locale. + * + * @param pickShape the description of this picking volume or area. + * + * @exception IllegalStateException if this Locale has been + * removed from its VirtualUniverse. + * + * @see BranchGroup#pickClosest + */ + public SceneGraphPath pickClosest( PickShape pickShape ) { + if (universe == null) { + throw new IllegalStateException(J3dI18N.getString("Locale4")); + } + + return Picking.pickClosest( this, pickShape ); + } + + + /** + * Returns a reference to any item that is Pickable below this + * Locale which intersects with <code>pickShape</code>. + * + * @param pickShape the description of this picking volume or area. + * + * @exception IllegalStateException if this Locale has been + * removed from its VirtualUniverse. + * + * @see BranchGroup#pickAny + */ + public SceneGraphPath pickAny( PickShape pickShape ) { + if (universe == null) { + throw new IllegalStateException(J3dI18N.getString("Locale4")); + } + + return Picking.pickAny( this, pickShape ); + } + + + /** + * Cleans up resources associated with this Locale + */ + protected void finalize() { + // free nodeId + if (universe != null && nodeId != null) { + universe.nodeIdFreeList.addElement(nodeId); + nodeId = null; + } + } +} diff --git a/src/classes/share/javax/media/j3d/MRSWLock.java b/src/classes/share/javax/media/j3d/MRSWLock.java new file mode 100644 index 0000000..8c35cb3 --- /dev/null +++ b/src/classes/share/javax/media/j3d/MRSWLock.java @@ -0,0 +1,74 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Use this lock to allow multiple reads/single write synchronization. + * To prevent deadlock a read/writeLock call must match with a read/writeUnlock call. + * Write request has precedence over read request. + */ + +class MRSWLock { + + static boolean debug = false; + + private int readCount; + private boolean write; + private int writeRequested; + private int lockRequested; + + MRSWLock() { + readCount = 0; + write = false; + writeRequested = 0; + lockRequested = 0; + } + + synchronized final void readLock() { + lockRequested++; + while((write == true) || (writeRequested > 0)) { + try { wait(); } catch(InterruptedException e){} + } + lockRequested--; + readCount++; + } + + synchronized final void readUnlock() { + if(readCount>0) + readCount--; + else + if(debug) System.out.println("ReadWriteLock.java : Problem! readCount is >= 0."); + + if(lockRequested>0) + notifyAll(); + } + + synchronized final void writeLock() { + lockRequested++; + writeRequested++; + while((readCount>0)||(write == true)) { + try { wait(); } catch(InterruptedException e){} + } + write = true; + lockRequested--; + writeRequested--; + } + + synchronized final void writeUnlock() { + write = false; + + if(lockRequested>0) + notifyAll(); + } + +} diff --git a/src/classes/share/javax/media/j3d/MasterControl.java b/src/classes/share/javax/media/j3d/MasterControl.java new file mode 100644 index 0000000..5d92ff0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/MasterControl.java @@ -0,0 +1,3702 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * Portions of this code were derived from work done by the Blackdown + * group (www.blackdown.org), who did the initial Linux implementation + * of the Java 3D API. + */ + +package javax.media.j3d; + +import java.util.*; +import java.awt.*; + +class MasterControl { + + /** + * Options for the runMonitor + */ + static final int CHECK_FOR_WORK = 0; + static final int SET_WORK = 1; + static final int RUN_THREADS = 2; + static final int THREAD_DONE = 3; + static final int WAIT_FOR_ALL = 4; + static final int SET_WORK_FOR_REQUEST_RENDERER = 5; + static final int RUN_RENDERER_CLEANUP = 6; + static final int SLEEP = 7; + + // The thread states for MC + static final int SLEEPING = 0; + static final int RUNNING = 1; + static final int WAITING_FOR_THREAD = 2; + static final int WAITING_FOR_THREADS = 3; + static final int WAITING_FOR_CPU = 4; + static final int WAITING_FOR_RENDERER_CLEANUP = 5; + + // The Rendering API's that we currently know about + static final int RENDER_OPENGL_SOLARIS = 0; + static final int RENDER_OPENGL_WIN32 = 1; + static final int RENDER_DIRECT3D = 2; + static final int RENDER_OPENGL_LINUX = 3; + + // Constants used in renderer thread argument + static final Integer REQUESTRENDER = new Integer(Renderer.REQUESTRENDER); + static final Integer RENDER = new Integer(Renderer.RENDER); + static final Integer SWAP = new Integer(Renderer.SWAP); + + // Constants used for request from user threads + static final Integer ACTIVATE_VIEW = new Integer(1); + static final Integer DEACTIVATE_VIEW = new Integer(2); + static final Integer START_VIEW = new Integer(3); + static final Integer STOP_VIEW = new Integer(4); + static final Integer REEVALUATE_CANVAS = new Integer(5); + static final Integer UNREGISTER_VIEW = new Integer(6); + static final Integer PHYSICAL_ENV_CHANGE = new Integer(7); + static final Integer INPUTDEVICE_CHANGE = new Integer(8); + static final Integer EMPTY_UNIVERSE = new Integer(9); + static final Integer START_RENDERER = new Integer(10); + static final Integer STOP_RENDERER = new Integer(11); + static final Integer RENDER_ONCE = new Integer(12); + static final Integer FREE_CONTEXT = new Integer(13); + static final Integer FREE_DRAWING_SURFACE = new Integer(14); + static final Integer FREE_MESSAGE = new Integer(15); + static final Integer RESET_CANVAS = new Integer(16); + static final Integer GETBESTCONFIG = new Integer(17); + static final Integer ISCONFIGSUPPORT = new Integer(18); + static final Integer SET_GRAPHICSCONFIG_FEATURES = new Integer(19); + static final Integer SET_QUERYPROPERTIES = new Integer(20); + static final Integer SET_VIEW = new Integer(21); + + /** + * reference to MasterControl thread + */ + private MasterControlThread mcThread = null; + + /** + * The list of views that are currently registered + */ + private UnorderList views = new UnorderList(1, View.class); + + /** + * the flag to indicate whether the geometry should be locked or not + */ + + private boolean lockGeometry = false; + + /** + * The number of registered views that are active + */ + private int numActiveViews = 0; + + // A freelist for ImageComponentUpdateInfo + private ImageComponentUpdateInfo[] imageUpdateInfoList = + new ImageComponentUpdateInfo[2]; + private int numFreeImageUpdateInfo = 0; + + + /** + * The list of active universes get from View + */ + private UnorderList activeUniverseList = new UnorderList(VirtualUniverse.class); + + /** + * The list of universes register from View + */ + private UnorderList regUniverseList = new UnorderList(VirtualUniverse.class); + + /** + * A lock used for accessing time structures. + */ + private Object timeLock = new Object(); + + + /** + * The current "time" value + */ + private long time = 0; + + /** + * Use to assign threadOpts in Renderer thread. + */ + private long waitTimestamp = 0; + + /** + * The current list of work threads + */ + private UnorderList stateWorkThreads = + new UnorderList(J3dThreadData.class); + private UnorderList renderWorkThreads = + new UnorderList(J3dThreadData.class); + private UnorderList requestRenderWorkThreads = + new UnorderList(J3dThreadData.class); + + /** + * The current list of work threads + */ + private UnorderList renderThreadData = new UnorderList(J3dThreadData.class); + + /** + * The list of input device scheduler thread + */ + private UnorderList inputDeviceThreads = + new UnorderList(1, InputDeviceScheduler.class); + + /** + * A flag that is true when the thread lists need updating + */ + private boolean threadListsChanged; + + + /** + * Markers for the last transform structure update thread + * and the last update thread. + */ + private int lastTransformStructureThread = 0; + private int lastStructureUpdateThread = 0; + + /** + * The current time snapshots + */ + private long currentTime; + + // Only one Timer thread in the system. + TimerThread timerThread; + + /** + * This flag indicates that MC is running + */ + volatile boolean running = true; + + /** + * This flag indicates that MC has work to do + */ + private boolean workToDo = false; + + /** + * This flag indicates that there is work for requestRenderer + */ + private boolean requestRenderWorkToDo = false; + + /** + * The number of THREAD_DONE messages pending + */ + private int threadPending = 0; + private int renderPending = 0; + private int statePending = 0; + + /** + * State variables for work lists + */ + private boolean renderWaiting = false; + private boolean stateWaiting = false; + + /** + * The current state of the MC thread + */ + private int state = SLEEPING; + + // time for sleep in order to met the minimum frame duration + private long sleepTime = 0; + + + /** + * The number of cpu's Java 3D may use + */ + private int cpuLimit; + + /** + * A list of mirror objects to be updated + */ + private UnorderList mirrorObjects = new UnorderList(ObjectUpdate.class); + + /** + * The renderingAttributesStructure for updating node component + * objects + */ + private RenderingAttributesStructure renderingAttributesStructure = + new RenderingAttributesStructure(); + + /** + * The default rendering method + */ + private DefaultRenderMethod defaultRenderMethod = null; + + /** + * The text3D rendering method + */ + private Text3DRenderMethod text3DRenderMethod = null; + + /** + * The vertex array rendering method + */ + private VertexArrayRenderMethod vertexArrayRenderMethod = null; + + /** + * The displayList rendering method + */ + private DisplayListRenderMethod displayListRenderMethod = null; + + /** + * The compressed geometry rendering method + */ + private CompressedGeometryRenderMethod compressedGeometryRenderMethod = null; + + /** + * The oriented shape3D rendering method + */ + private OrientedShape3DRenderMethod orientedShape3DRenderMethod = null; + + /** + * This is the start time upon which alpha's and behaviors + * are synchronized to. + */ + static long systemStartTime = System.currentTimeMillis(); + + // The rendering API we are using + private int renderingAPI = RENDER_OPENGL_SOLARIS; + static boolean isD3DAPI = false; + + // Are we on a Win32 system + static boolean isWin32 = false; + + // The class that describes the low level rendering code + private NativeAPIInfo nativeAPIInfo = null; + + // This is a counter for texture id's, valid id starts from 1 + private int textureIdCount = 0; + + // This is lock for both 2D/3D textureIds; + private Object textureIdLock = new Object(); + + // This is a time stamp used when context is created + private long contextTimeStamp = 0; + + // This is a counter for canvas bit + private int canvasBitCount = 0; + + // This is a counter for rendererBit + private int rendererCount = 0; + + /* + // Flag that indicates whether the JVM is version JDK1.5 or later. + // If so, then the jvm15OrBetter flag is set to true, indicating that + // 1.5 functionality can be used. + // We don't use any JDK 1.5 features yet, so this is a placeholder. + static boolean jvm15OrBetter = false; + */ + + // Flag that indicates whether to shared display context or not + boolean isSharedCtx = false; + boolean sharedCtxOverride = false; + + // Flag that tells us to use NV_register_combiners + boolean useCombiners = false; + + // Flag that indicates whether compile is disabled or not + boolean disableCompile = false; + + // Flag that indicates whether or not compaction occurs + boolean doCompaction = true; + + // Flag that indicates whether separate specular color is disabled or not + boolean disableSeparateSpecularColor = false; + + // Maximum number of texture units + int textureUnitMax = 100; + + // Flag that indicates whether DisplayList is used or not + boolean isDisplayList = true; + + // If this flag is set, then by-ref geometry will not be + // put in display list + boolean buildDisplayListIfPossible = false; + + + // REQUESTCLEANUP messages argument + static Integer REMOVEALLCTXS_CLEANUP = new Integer(1); + static Integer REMOVECTX_CLEANUP = new Integer(2); + static Integer REMOVENOTIFY_CLEANUP = new Integer(3); + static Integer RESETCANVAS_CLEANUP = new Integer(4); + static Integer FREECONTEXT_CLEANUP = new Integer(5); + + // arguments for renderer resource cleanup run + Object rendererCleanupArgs[] = {new Integer(Renderer.REQUESTCLEANUP), + null, null}; + + + // Context creation should obtain this lock, so that + // first_time and all the extension initilialization + // are done in the MT safe manner + Object contextCreationLock = new Object(); + + // Flag that indicates whether to lock the DSI while rendering + boolean doDsiRenderLock = false; + + // Flag that indicates whether J3DGraphics2D uses texturemapping + // instead of drawpixel for composite the buffers + boolean isJ3dG2dDrawPixel = true; + + // flag that indicates whether BackgroundRetained uses texturemapping + // or drawpixel clear the background + boolean isBackgroundTexture = true; + + // Flag that indicates whether the framebuffer is sharing the + // Z-buffer with both the left and right eyes when in stereo mode. + // If this is true, we need to clear the Z-buffer between rendering + // to the left and right eyes. + boolean sharedStereoZBuffer; + + // True to disable all underlying multisampling API so it uses + // the setting in the driver. + boolean implicitAntialiasing = false; + + // False to disable compiled vertex array extensions if support + boolean isCompliedVertexArray = true; + + // False to disable rescale normal if OGL support + boolean isForceNormalized = false; + + // Hashtable that maps a GraphicsDevice to its associated + // Screen3D--this is only used for on-screen Canvas3Ds + Hashtable deviceScreenMap = new Hashtable(); + + // Use to store all requests from user threads. + UnorderList requestObjList = new UnorderList(); + private UnorderList requestTypeList = new UnorderList(Integer.class); + + // Temporary storage to store stop request for requestViewList + private UnorderList tempViewList = new UnorderList(); + private UnorderList renderOnceList = new UnorderList(); + + // This flag is true when there is pending request + // i.e. false when the above requestxxx Lists are all empty. + private boolean pendingRequest = false; + + // Root ThreadGroup for creating Java 3D threads + private static ThreadGroup rootThreadGroup; + + // Thread priority for all Java3D threads + private static int threadPriority; + + static private Object mcThreadLock = new Object(); + + private ArrayList timestampUpdateList = new ArrayList(3); + + private UnorderList freeMessageList = new UnorderList(8); + + long awt; + native long getAWT(); + + // Method to initialize the native J3D library + private native boolean initializeJ3D(boolean disableXinerama); + + // Method to get number of procesor + private native int getNumberOfProcessor(); + + // Methods to set/get system thread concurrency + private native void setThreadConcurrency(int newLevel); + private native int getThreadConcurrency(); + + // Maximum lights supported by the native API + private native int getMaximumLights(); + int maxLights; + + // This is used for D3D only + int resendTexTimestamp = 0; + + // Indicates that the property to disable Xinerama mode was set and + // successfully disabled. + boolean xineramaDisabled = false; + + /** + * Constructs a new MasterControl object. Note that there is + * exatly one MasterControl object, created statically by + * VirtualUniverse. + */ + MasterControl() { + + // Get AWT handle + awt = getAWT(); + + // Get native API information + nativeAPIInfo = new NativeAPIInfo(); + renderingAPI = nativeAPIInfo.getRenderingAPI(); + isD3DAPI = (renderingAPI == RENDER_DIRECT3D); + isWin32 = isD3DAPI || (renderingAPI == RENDER_OPENGL_WIN32); + + if(J3dDebug.devPhase) { + // Check to see whether debug mode is allowed + Boolean j3dDebug = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.debug", "false"); + return new Boolean(str); + } + }); + J3dDebug.debug = j3dDebug.booleanValue(); + + // Get graphic library. + //System.err.println("In Development Phase : \n"); + + if(renderingAPI == RENDER_OPENGL_SOLARIS) + System.err.print("Graphics Library : Solaris OpenGL"); + else if(renderingAPI == RENDER_OPENGL_WIN32) + System.err.print("Graphics Library : Win32 OpenGL"); + else if(renderingAPI == RENDER_DIRECT3D) + System.err.println("Graphics Library : Direct3D"); + + System.err.println(); + + // Get package info. + ClassLoader classLoader = getClass().getClassLoader(); + if (classLoader != null) { + // it is null in case of plugin + J3dDebug.pkgInfo(classLoader, "javax.media.j3d", + "SceneGraphObject"); + } + + if(J3dDebug.debug) { + J3dDebug.pkgInfo(classLoader, "javax.vecmath", "Point3d"); + J3dDebug.pkgInfo(classLoader, "com.sun.j3d.utils.universe", "SimpleUniverse"); + + // Reminder statement. + System.err.println("For production release : Set J3dDebug.devPhase to false.\n"); + System.err.println("MasterControl: J3dDebug.debug = " + J3dDebug.debug); + } + } + + // Check to see whether shared contexts are allowed + if (getRenderingAPI() != RENDER_DIRECT3D) { + Boolean j3dSharedCtx = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.sharedctx"); + if (str == null) { + return Boolean.FALSE; + } else { + sharedCtxOverride = true; + return new Boolean(str); + } + } + }); + isSharedCtx = j3dSharedCtx.booleanValue(); + if (sharedCtxOverride) { + if (isSharedCtx) + System.err.println("Java 3D: shared contexts enabled"); + else + System.err.println("Java 3D: shared contexts disabled"); + } + } + + Boolean j3dDisableCompile = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.disablecompile"); + if (str == null) { + return Boolean.FALSE; + } else { + return Boolean.TRUE; + } + } + }); + disableCompile = j3dDisableCompile.booleanValue(); + if (disableCompile) { + System.err.println("Java 3D: Compile disabled"); + } + + Boolean j3dDoCompaction = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.docompaction"); + if (str == null) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } + }); + doCompaction = j3dDoCompaction.booleanValue(); + if (!doCompaction) { + System.err.println("Java 3D: Disabling compaction."); + } + + Boolean j3dUseCombiners = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.usecombiners"); + if (str == null) { + return Boolean.FALSE; + } else { + return Boolean.TRUE; + } + } + }); + useCombiners = j3dUseCombiners.booleanValue(); + if (useCombiners) { + System.err.println("Java 3D: Using NV_register_combiners if available"); + } + + Boolean j3dDisableSeparateSpecularColor = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty( + "j3d.disableSeparateSpecular"); + if (str == null) { + return Boolean.FALSE; + } else { + return Boolean.TRUE; + } + } + }); + disableSeparateSpecularColor = + j3dDisableSeparateSpecularColor.booleanValue(); + if (disableSeparateSpecularColor) { + System.err.println( + "Java 3D: Separate Specular Color disabled if possible"); + } + + // Get the maximum number of texture units + final int defaultTextureUnitMax = textureUnitMax; + Integer textureUnitLimit = + (Integer) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return Integer.getInteger("j3d.textureUnitMax", + defaultTextureUnitMax); + } + }); + + + textureUnitMax = textureUnitLimit.intValue(); + if (textureUnitMax != defaultTextureUnitMax) { + System.err.println("Java 3D: maximum number of texture units = " + + textureUnitMax); + } + + Boolean j3dDisplayList = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.displaylist", "true"); + return new Boolean(str); + } + }); + + isDisplayList = j3dDisplayList.booleanValue(); + if (!isDisplayList) { + System.err.println("Java 3D: Display List disabled"); + } + + + Boolean j3dimplicitAntialiasing = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.implicitAntialiasing", "false"); + return new Boolean(str); + } + }); + + implicitAntialiasing = j3dimplicitAntialiasing.booleanValue(); + if (implicitAntialiasing) { + System.err.println("Java 3D: Implicit Antialiasing enabled"); + } + + + Boolean j3dcompliedVertexArray = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.compliedVertexArray", "true"); + return new Boolean(str); + } + }); + + isCompliedVertexArray = j3dcompliedVertexArray.booleanValue(); + if (!isCompliedVertexArray) { + System.err.println("Java 3D: Complied vertex array disabled"); + } + + + + Boolean j3dforceNormalized = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.forceNormalized", "false"); + return new Boolean(str); + } + }); + + isForceNormalized = j3dforceNormalized.booleanValue(); + if (isForceNormalized) { + System.err.println("Java 3D: Force Normalized"); + } + + + Boolean j3dOptimizeSpace = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.optimizeForSpace", "true"); + return new Boolean(str); + } + + }); + + + if (!j3dOptimizeSpace.booleanValue()) { + System.err.println("Java 3D: Optimize For Space disabled"); + } + // Build Display list for by-ref geometry and infrequently changing geometry + // ONLY IF (isDisplayList is true and optimizeForSpace if False) + if (isDisplayList && !j3dOptimizeSpace.booleanValue()) { + buildDisplayListIfPossible = true; + } + else { + buildDisplayListIfPossible = false; + } + + // Check to see whether Renderer can run without DSI lock + Boolean j3dRenderLock = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.renderLock", "false"); + return new Boolean(str); + } + }); + doDsiRenderLock = j3dRenderLock.booleanValue(); + // Don't print this out now that the default is false + //if (!doDsiRenderLock) { + // System.err.println("Java 3D: render lock disabled"); + //} + + // Check to see whether J3DGraphics2D uses texturemapping + // or drawpixel for composite the buffers + Boolean j3dG2DRender = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.g2ddrawpixel", "false"); + return new Boolean(str); + } + }); + isJ3dG2dDrawPixel = j3dG2DRender.booleanValue(); + + if(J3dDebug.devPhase) { + if (!isJ3dG2dDrawPixel) { + System.err.println("Java 3D: render Graphics2D DrawPixel disabled"); + } else { + System.err.println("Java 3D: render Graphics2D DrawPixel enabled"); + } + } + + // Check to see whether BackgroundRetained uses texturemapping + // or drawpixel clear the background + if (!isD3D()) { + Boolean j3dBackgroundTexture = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.backgroundtexture", "true"); + return new Boolean(str); + } + }); + isBackgroundTexture = j3dBackgroundTexture.booleanValue(); + + if(J3dDebug.devPhase) { + if (!isBackgroundTexture) { + System.err.println("Java 3D: background texture is disabled"); + } else { + System.err.println("Java 3D: background texture is enabled"); + } + } + } else { + // D3D always use background texture and use + // canvas.clear() instead of canvas.textureclear() in Renderer + isBackgroundTexture = false; + } + + // Check to see if stereo mode is sharing the Z-buffer for both + // eyes. + Boolean j3dSharedStereoZBuffer = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String defaultValue = (isWin32 ? "true" : "false"); + String str = System.getProperty("j3d.sharedstereozbuffer", + defaultValue); + return new Boolean(str); + } + }); + sharedStereoZBuffer = j3dSharedStereoZBuffer.booleanValue(); + j3dSharedStereoZBuffer = null; + + // Get the maximum number of concurrent threads (CPUs) + final int defaultThreadLimit = getNumberOfProcessor()+1; + Integer threadLimit = + (Integer) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return Integer.getInteger("j3d.threadLimit", + defaultThreadLimit); + } + }); + + + cpuLimit = threadLimit.intValue(); + if (cpuLimit < 1) + cpuLimit = 1; + if (J3dDebug.debug || cpuLimit != defaultThreadLimit) { + System.err.println("Java 3D: concurrent threadLimit = " + + cpuLimit); + } + + // Ensure that there are at least enough system threads to + // support all of Java 3D's threads running in parallel + int threadConcurrency = getThreadConcurrency(); + if (J3dDebug.debug) { + System.err.println("System threadConcurrency = " + + threadConcurrency); + } + if (threadConcurrency != -1 && threadConcurrency < (cpuLimit + 1)) { + threadConcurrency = cpuLimit + 1; + if (J3dDebug.debug) { + System.err.println("Setting system threadConcurrency to " + + threadConcurrency); + } + setThreadConcurrency(threadConcurrency); + } + + // Get the input device scheduler sampling time + Integer samplingTime = + (Integer) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return Integer.getInteger("j3d.deviceSampleTime", 0); + } + }); + + if (samplingTime.intValue() > 0) { + InputDeviceScheduler.samplingTime = + samplingTime.intValue(); + System.err.println("Java 3D: Input device sampling time = " + + samplingTime + " ms"); + } + + // See if Xinerama should be disabled for better performance. + Boolean j3dDisableXinerama = + (Boolean) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("j3d.disableXinerama", + "false"); + return new Boolean(str); + } + }); + + boolean disableXinerama = j3dDisableXinerama.booleanValue(); + + // Initialize the native J3D library + if (!initializeJ3D(disableXinerama)) { + if (isGreenThreadUsed()) { + System.err.print(J3dI18N.getString("MasterControl1")); + } + throw new RuntimeException(J3dI18N.getString("MasterControl0")); + } + + if (xineramaDisabled) { + // initializeJ3D() successfully disabled Xinerama. + System.err.println("Java 3D: Xinerama disabled"); + } + else if (disableXinerama) { + // j3d.disableXinerama is true, but attempt failed. + System.err.println("Java 3D: could not disable Xinerama"); + } + + // Get the maximum Lights + maxLights = getMaximumLights(); + + // create the freelists + FreeListManager.createFreeLists(); + } + + static public String getProperty(final String s) { + + return (String) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return System.getProperty(s); + } + }); + } + + boolean isGreenThreadUsed() { + + String javaVMInfo = + (String) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + String str = System.getProperty("java.vm.info"); + return str; + } + }); + + String greenThreadStr = new String("green threads"); + if (javaVMInfo.indexOf(greenThreadStr) == -1) + return false; + else + return true; + } + + + /** + * Method to load the native libraries needed by Java 3D. This is + * called by the static initializer in VirtualUniverse <i>before</i> + * the MasterControl object is created. + */ + static void loadLibraries() { + // This works around a native load library bug + try { + java.awt.Toolkit toolkit = java.awt.Toolkit.getDefaultToolkit(); + toolkit = null; // just making sure GC collects this + } catch (java.awt.AWTError e) { + } + + // Load the JAWT native library + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + System.loadLibrary("jawt"); + return null; + } + }); + + // Load the native J3D library + java.security.AccessController.doPrivileged(new + java.security.PrivilegedAction() { + public Object run() { + + String osName = System.getProperty("os.name"); + /* System.out.println(" os.name is " + osName ); */ + // If it is a Windows OS, we want to support + // dynamic native library selection (ogl, d3d) + if((osName.length() > 8) && + ((osName.substring(0,7)).equals("Windows"))){ + + /* + * TODO : Will support a more flexible dynamic + * selection scheme via the use of Preferences API. + * + */ + String str = System.getProperty("j3d.rend"); + if ((str == null) || (!str.equals("d3d"))) { + /* System.out.println("(1) ogl case : j3d.rend is " + str ); */ + System.loadLibrary("j3dcore-ogl"); + + } + else { + /* System.out.println("(2) d3d case : j3d.rend is " + str); */ + System.loadLibrary("j3dcore-d3d"); + } + } + else { + /* System.out.println("(3) ogl case"); */ + System.loadLibrary("j3dcore-ogl"); + } + return null; + } + }); + } + + + /** + * Invoke from InputDeviceScheduler to create an + * InputDeviceBlockingThread. + */ + InputDeviceBlockingThread getInputDeviceBlockingThread( + final InputDevice device) { + + return (InputDeviceBlockingThread) + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + synchronized (rootThreadGroup) { + Thread thread = new InputDeviceBlockingThread( + rootThreadGroup, device); + thread.setPriority(threadPriority); + return thread; + } + } + } + ); + } + + /** + * Set thread priority to all threads under Java3D thread group. + */ + void setThreadPriority(final int pri) { + synchronized (rootThreadGroup) { + threadPriority = pri; + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + Thread list[] = new + Thread[rootThreadGroup.activeCount()]; + int count = rootThreadGroup.enumerate(list); + for (int i=count-1; i >=0; i--) { + list[i].setPriority(pri); + } + return null; + } + }); + } + } + + + /** + * Return Java3D thread priority + */ + int getThreadPriority() { + return threadPriority; + } + + /** + * This returns the a unused renderer bit + */ + int getRendererBit() { + return (1 << rendererCount++); + } + + /** + * This returns a context creation time stamp + * Note: this has to be called under the contextCreationLock + */ + long getContextTimeStamp() { + return (++contextTimeStamp); + } + + + /** + * This returns the a unused displayListId + */ + Integer getDisplayListId() { + return (Integer) + FreeListManager.getObject(FreeListManager.DISPLAYLIST); + } + + void freeDisplayListId(Integer id) { + FreeListManager.freeObject(FreeListManager.DISPLAYLIST, id); + } + + /** + * This returns the a unused textureId + */ + int getTexture2DId() { + // MasterControl has to handle the ID itself. 2D and 3D ideas must + // never be the same, so the counter has to be in the MasterControl + MemoryFreeList textureIds = + FreeListManager.getFreeList(FreeListManager.TEXTURE2D); + int id; + + synchronized (textureIdLock) { + if (textureIds.size() > 0) { + return ((Integer)FreeListManager. + getObject(FreeListManager.TEXTURE2D)).intValue(); + } else { + return (++textureIdCount); + } + } + } + + int getTexture3DId() { + // MasterControl has to handle the ID itself. 2D and 3D ideas must + // never be the same, so the counter has to be in the MasterControl + MemoryFreeList textureIds = + FreeListManager.getFreeList(FreeListManager.TEXTURE3D); + synchronized (textureIdLock) { + if (textureIds.size > 0) { + return ((Integer)FreeListManager. + getObject(FreeListManager.TEXTURE3D)).intValue(); + } + else return (++textureIdCount); + } + } + + void freeTexture2DId(int id) { + FreeListManager.freeObject(FreeListManager.TEXTURE2D, new Integer(id)); + } + + void freeTexture3DId(int id) { + FreeListManager.freeObject(FreeListManager.TEXTURE3D, new Integer(id)); + } + + int getCanvasBit() { + // Master control need to keep count itself + MemoryFreeList cbId = + FreeListManager.getFreeList(FreeListManager.CANVASBIT); + if (cbId.size() > 0) { + return ((Integer)FreeListManager. + getObject(FreeListManager.CANVASBIT)).intValue(); + } + else { + if (canvasBitCount > 31) { + throw new InternalError(); + } + return (1 << canvasBitCount++); + } + } + + + void freeCanvasBit(int canvasBit) { + FreeListManager.freeObject(FreeListManager.CANVASBIT, + new Integer(canvasBit)); + } + + Transform3D getTransform3D(Transform3D val) { + Transform3D t; + t = (Transform3D) + FreeListManager.getObject(FreeListManager.TRANSFORM3D); + if (val != null) t.set(val); + return t; + } + + void addToTransformFreeList(Transform3D t) { + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t); + } + + + ImageComponentUpdateInfo getFreeImageUpdateInfo() { + ImageComponentUpdateInfo info; + + synchronized (imageUpdateInfoList) { + if (numFreeImageUpdateInfo > 0) { + numFreeImageUpdateInfo--; + info = (ImageComponentUpdateInfo) + imageUpdateInfoList[numFreeImageUpdateInfo]; + } else { + info = new ImageComponentUpdateInfo(); + } + } + return (info); + } + + void addFreeImageUpdateInfo(ImageComponentUpdateInfo info) { + synchronized (imageUpdateInfoList) { + if (imageUpdateInfoList.length == numFreeImageUpdateInfo) { + ImageComponentUpdateInfo[] newFreeList = + new ImageComponentUpdateInfo[numFreeImageUpdateInfo * 2]; + System.arraycopy(imageUpdateInfoList, 0, newFreeList, 0, + numFreeImageUpdateInfo); + newFreeList[numFreeImageUpdateInfo++] = info; + imageUpdateInfoList = newFreeList; + } else { + imageUpdateInfoList[numFreeImageUpdateInfo++] = info; + } + } + } + + void addFreeImageUpdateInfo(ArrayList freeList) { + ImageComponentUpdateInfo info; + + synchronized (imageUpdateInfoList) { + int len = numFreeImageUpdateInfo + freeList.size(); + + if (imageUpdateInfoList.length <= len) { + ImageComponentUpdateInfo[] newFreeList = + new ImageComponentUpdateInfo[len * 2]; + System.arraycopy(imageUpdateInfoList, 0, newFreeList, 0, + numFreeImageUpdateInfo); + imageUpdateInfoList = newFreeList; + } + + for (int i = 0; i < freeList.size(); i++) { + info = (ImageComponentUpdateInfo) freeList.get(i); + if (info != null) { + imageUpdateInfoList[numFreeImageUpdateInfo++] = info; + } + } + } + } + + + /** + * Create a Renderer if it is not already done so. + * This is used for GraphicsConfigTemplate3D passing + * graphics call to RequestRenderer. + */ + Renderer createRenderer(GraphicsConfiguration gc) { + final GraphicsDevice gd = gc.getDevice(); + + Renderer rdr = (Renderer) Screen3D.deviceRendererMap.get(gd); + if (rdr != null) { + return rdr; + } + + + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + Renderer r; + synchronized (rootThreadGroup) { + r = new Renderer(rootThreadGroup); + r.initialize(); + r.setPriority(threadPriority); + Screen3D.deviceRendererMap.put(gd, r); + } + return null; + } + }); + + threadListsChanged = true; + + return (Renderer) Screen3D.deviceRendererMap.get(gd); + } + + /** + * Post the request in queue + */ + void postRequest(Integer type, Object obj) { + + synchronized (mcThreadLock) { + synchronized (requestObjList) { + if (mcThread == null) { + if ((type == ACTIVATE_VIEW) || + (type == GETBESTCONFIG) || + (type == SET_VIEW) || + (type == ISCONFIGSUPPORT) || + (type == SET_QUERYPROPERTIES) || + (type == SET_GRAPHICSCONFIG_FEATURES)) { + createMasterControlThread(); + requestObjList.add(obj); + requestTypeList.add(type); + pendingRequest = true; + } else if (type == EMPTY_UNIVERSE) { + destroyUniverseThreads((VirtualUniverse) obj); + } else if (type == STOP_VIEW) { + View v = (View) obj; + v.stopViewCount = -1; + v.isRunning = false; + } else if (type == STOP_RENDERER) { + if (obj instanceof Canvas3D) { + ((Canvas3D) obj).isRunningStatus = false; + } else { + ((Renderer) obj).userStop = true; + } + } else if (type == UNREGISTER_VIEW) { + ((View) obj).doneUnregister = true; + } else { + requestObjList.add(obj); + requestTypeList.add(type); + pendingRequest = true; + } + } else { + requestObjList.add(obj); + requestTypeList.add(type); + pendingRequest = true; + } + } + } + + setWork(); + } + + + + + /** + * This procedure is invoked when isRunning is false. + * Return true when there is no more pending request so that + * Thread can terminate. Otherwise we have to recreate + * the MC related threads. + */ + boolean mcThreadDone() { + synchronized (mcThreadLock) { + synchronized (requestObjList) { + if (!pendingRequest) { + mcThread = null; + if (renderingAttributesStructure.updateThread != + null) { + renderingAttributesStructure.updateThread.finish(); + renderingAttributesStructure.updateThread = + null; + } + renderingAttributesStructure = new RenderingAttributesStructure(); + if (timerThread != null) { + timerThread.finish(); + timerThread = null; + } + requestObjList.clear(); + requestTypeList.clear(); + return true; + } + running = true; + createMCThreads(); + return false; + } + } + } + + /** + * Returns the native rendering layer we are using + */ + final int getRenderingAPI() { + return renderingAPI; + } + + final boolean isD3D() { + return isD3DAPI; + } + + /** + * This method increments and returns the next time value + * timeLock must get before this procedure is invoked + */ + final long getTime() { + return (time++); + } + + + + /** + * This adds a BHNode to one of the list of BHNodes + */ + void addBHNodeToFreelists(BHNode bH) { + bH.parent = null; + bH.mark = false; + + if (bH.nodeType == BHNode.BH_TYPE_INTERNAL) { + ((BHInternalNode)bH).lChild = null; + ((BHInternalNode)bH).rChild = null; + FreeListManager.freeObject(FreeListManager.BHINTERNAL, bH); + } + else if (bH.nodeType == BHNode.BH_TYPE_LEAF) { + ((BHLeafNode)(bH)).leafIF = null; + FreeListManager.freeObject(FreeListManager.BHLEAF, bH); + } + } + + /** + * This gets a message from the free list. If there isn't any, + * it creates one. + */ + BHNode getBHNode(int type) { + + if (type == BHNode.BH_TYPE_LEAF) { + return (BHNode) FreeListManager.getObject(FreeListManager.BHLEAF); + } + + if (type == BHNode.BH_TYPE_INTERNAL) { + return (BHNode) + FreeListManager.getObject(FreeListManager.BHINTERNAL); + } + return null; + } + + + /** + * This adds a message to the list of messages + */ + final void addMessageToFreelists(J3dMessage m) { + FreeListManager.freeObject(FreeListManager.MESSAGE, m); + } + + /** + * This gets a message from the free list. If there isn't any, + * it creates one. + */ + final J3dMessage getMessage() { + return (J3dMessage) FreeListManager.getObject(FreeListManager.MESSAGE); + } + + + /** + * This takes a given message and parses it out to the structures and + * marks its time value. + */ + void processMessage(J3dMessage message) { + + synchronized (timeLock) { + message.time = getTime(); + sendMessage(message); + } + setWork(); + } + + /** + * This takes an array of messages and parses them out to the structures and + * marks the time value. Make sure, setWork() is done at the very end + * to make sure all the messages will be processed in the same frame + */ + void processMessage(J3dMessage[] messages) { + + synchronized (timeLock) { + long time = getTime(); + + for (int i = 0; i < messages.length; i++) { + messages[i].time = time; + sendMessage(messages[i]); + } + } + setWork(); + } + + + /** + * Create and start the MasterControl Thread. + */ + void createMasterControlThread() { + running = true; + workToDo = false; + state = RUNNING; + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + synchronized (rootThreadGroup) { + mcThread = new + MasterControlThread(rootThreadGroup); + mcThread.setPriority(threadPriority); + } + return null; + } + }); + } + + // assuming the timeLock is already acquired + + /** + * Send a message to another Java 3D thread. + */ + void sendMessage(J3dMessage message) { + + synchronized (message) { + VirtualUniverse u = message.universe; + int targetThreads = message.threads; + + + // System.out.println("============> sendMessage"); + // dumpmsg(message); + if ((targetThreads & J3dThread.UPDATE_RENDERING_ATTRIBUTES) != 0) { + renderingAttributesStructure.addMessage(message); + } + + // GraphicsContext3D send message with universe = null + if (u != null) { + if ((targetThreads & J3dThread.UPDATE_GEOMETRY) != 0) { + u.geometryStructure.addMessage(message); + } + if ((targetThreads & J3dThread.UPDATE_TRANSFORM) != 0) { + u.transformStructure.addMessage(message); + } + if ((targetThreads & J3dThread.UPDATE_BEHAVIOR) != 0) { + u.behaviorStructure.addMessage(message); + } + if ((targetThreads & J3dThread.UPDATE_SOUND) != 0) { + u.soundStructure.addMessage(message); + } + if ((targetThreads & J3dThread.UPDATE_RENDERING_ENVIRONMENT) != 0) { + u.renderingEnvironmentStructure.addMessage(message); + } + } + + if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) { + // Note that we don't check for active view + if (message.view != null && message.view.soundScheduler != null ) { + // This make sure that message won't lost even + // though this view not yet register + message.view.soundScheduler.addMessage(message); + } else { + synchronized (views) { + View v[] = (View []) views.toArray(false); + int i = views.arraySize()-1; + if (u == null) { + while (i>=0) { + v[i--].soundScheduler.addMessage(message); + } + } else { + while (i>=0) { + if (v[i].universe == u) { + v[i].soundScheduler.addMessage(message); + } + i--; + } + } + } + } + } + + if ((targetThreads & J3dThread.UPDATE_RENDER) != 0) { + // Note that we don't check for active view + if (message.view != null && message.view.renderBin != null) { + // This make sure that message won't lost even + // though this view not yet register + message.view.renderBin.addMessage(message); + } else { + synchronized (views) { + View v[] = (View []) views.toArray(false); + int i = i=views.arraySize()-1; + if (u == null) { + while (i>=0) { + v[i--].renderBin.addMessage(message); + } + } + else { + while (i>=0) { + if (v[i].universe == u) { + v[i].renderBin.addMessage(message); + } + i--; + } + } + } + } + } + + if (message.getRefcount() == 0) { + message.clear(); + addMessageToFreelists(message); + } + } + } + + + /** + * Send a message to another Java 3D thread. + * This variant is only call by TimerThread for Input Device Scheduler + * or to redraw all View for RenderThread + */ + void sendRunMessage(int targetThreads) { + + synchronized (timeLock) { + + long time = getTime(); + + if ((targetThreads & J3dThread.INPUT_DEVICE_SCHEDULER) != 0) { + synchronized (inputDeviceThreads) { + InputDeviceScheduler ds[] = (InputDeviceScheduler []) + inputDeviceThreads.toArray(false); + for (int i=inputDeviceThreads.size()-1; i >=0; i--) { + if (ds[i].physicalEnv.activeViewRef > 0) { + ds[i].getThreadData().lastUpdateTime = + time; + } + } + + // timerThread instance in MC will set to null in + // destroyUniverseThreads() so we need to check if + // TimerThread kick in to sendRunMessage() after that. + // It happens because TimerThread is the only thread run + // asychronizously with MasterControl thread. + + if (timerThread != null) { + // Notify TimerThread to wakeup this procedure + // again next time. + timerThread.addInputDeviceSchedCond(); + } + } + } + if ((targetThreads & J3dThread.RENDER_THREAD) != 0) { + synchronized (renderThreadData) { + J3dThreadData[] threads = (J3dThreadData []) + renderThreadData.toArray(false); + int i=renderThreadData.arraySize()-1; + J3dThreadData thr; + while (i>=0) { + thr = threads[i--]; + if ( thr.view.renderBinReady) { + thr.lastUpdateTime = time; + } + } + } + } + } + setWork(); + } + + /** + * Send a message to another Java 3D thread. + * This variant is only call by TimerThread for Sound Scheduler + */ + void sendRunMessage(long waitTime, View view, int targetThreads) { + + synchronized (timeLock) { + + long time = getTime(); + + if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) { + if (view.soundScheduler != null) { + view.soundScheduler.threadData.lastUpdateTime = time; + } + // wakeup this procedure next time + // QUESTION: waitTime calculated some milliseconds BEFORE + // this methods getTime() called - shouldn't actual + // sound Complete time be passed by SoundScheduler + // QUESTION: will this wake up only soundScheduler associated + // with this view?? (since only it's lastUpdateTime is set) + // or all soundSchedulers?? + timerThread.addSoundSchedCond(time+waitTime); + } + } + setWork(); + } + + /** + * Send a message to another Java 3D thread. + * This variant is only called to update Render Thread + */ + void sendRunMessage(View v, int targetThreads) { + + synchronized (timeLock) { + long time = getTime(); + + if ((targetThreads & J3dThread.RENDER_THREAD) != 0) { + synchronized (renderThreadData) { + J3dThreadData[] threads = (J3dThreadData []) + renderThreadData.toArray(false); + int i=renderThreadData.arraySize()-1; + J3dThreadData thr; + while (i>=0) { + thr = threads[i--]; + if (thr.view == v && v.renderBinReady) { + thr.lastUpdateTime = time; + } + } + } + } + } + setWork(); + } + + + /** + * This sends a run message to the given threads. + */ + void sendRunMessage(VirtualUniverse u, int targetThreads) { + // We don't sendRunMessage to update structure except Behavior + + synchronized (timeLock) { + long time = getTime(); + + if ((targetThreads & J3dThread.BEHAVIOR_SCHEDULER) != 0) { + if (u.behaviorScheduler != null) { + u.behaviorScheduler.getThreadData(null, + null).lastUpdateTime = time; + } + } + + if ((targetThreads & J3dThread.UPDATE_BEHAVIOR) != 0) { + u.behaviorStructure.threadData.lastUpdateTime = time; + } + + if ((targetThreads & J3dThread.UPDATE_GEOMETRY) != 0) { + u.geometryStructure.threadData.lastUpdateTime = time; + } + + if ((targetThreads & J3dThread.UPDATE_SOUND) != 0) { + u.soundStructure.threadData.lastUpdateTime = time; + } + + if ((targetThreads & J3dThread.SOUND_SCHEDULER) != 0) { + synchronized (views) { + View v[] = (View []) views.toArray(false); + for (int i= views.arraySize()-1; i >=0; i--) { + if ((v[i].soundScheduler != null) && + (v[i].universe == u)) { + v[i].soundScheduler.threadData.lastUpdateTime = time; + } + } + } + } + + if ((targetThreads & J3dThread.RENDER_THREAD) != 0) { + + synchronized (renderThreadData) { + J3dThreadData[] threads = (J3dThreadData []) + renderThreadData.toArray(false); + int i=renderThreadData.arraySize()-1; + J3dThreadData thr; + while (i>=0) { + thr = threads[i--]; + if (thr.view.universe == u && thr.view.renderBinReady) { + thr.lastUpdateTime = time; + } + } + } + } + } + + setWork(); + } + + + /** + * Return a clone of View, we can't access + * individual element of View after getting the size + * in separate API call without synchronized views. + */ + UnorderList cloneView() { + return (UnorderList) views.clone(); + } + + /** + * Return true if view is already registered with MC + */ + boolean isRegistered(View view) { + return views.contains(view); + } + + /** + * This snapshots the time values to be used for this iteration + */ + private void updateTimeValues() { + int i=0; + J3dThreadData lastThread=null; + J3dThreadData thread=null; + long lastTime = currentTime; + + currentTime = getTime(); + + J3dThreadData threads[] = (J3dThreadData []) + stateWorkThreads.toArray(false); + int size = stateWorkThreads.arraySize(); + + while (i<lastTransformStructureThread) { + thread = threads[i++]; + + if ((thread.lastUpdateTime > thread.lastRunTime) && + !thread.thread.userStop) { + lastThread = thread; + thread.needsRun = true; + thread.threadOpts = J3dThreadData.CONT_THREAD; + thread.lastRunTime = currentTime; + } else { + thread.needsRun = false; + } + } + + if (lastThread != null) { + lastThread.threadOpts = J3dThreadData.WAIT_ALL_THREADS; + lastThread = null; + } + + while (i<lastStructureUpdateThread) { + thread = threads[i++]; + if ((thread.lastUpdateTime > thread.lastRunTime) && + !thread.thread.userStop) { + lastThread = thread; + thread.needsRun = true; + thread.threadOpts = J3dThreadData.CONT_THREAD; + thread.lastRunTime = currentTime; + } else { + thread.needsRun = false; + } + } + if (lastThread != null) { + lastThread.threadOpts = J3dThreadData.WAIT_ALL_THREADS; + lastThread = null; + } + + while (i<size) { + thread = threads[i++]; + if ((thread.lastUpdateTime > thread.lastRunTime) && + !thread.thread.userStop) { + lastThread = thread; + thread.needsRun = true; + thread.threadOpts = J3dThreadData.CONT_THREAD; + thread.lastRunTime = currentTime; + } else { + thread.needsRun = false; + } + } + if (lastThread != null) { + lastThread.threadOpts = J3dThreadData.WAIT_ALL_THREADS; + lastThread = null; + } + + + threads = (J3dThreadData []) renderWorkThreads.toArray(false); + size = renderWorkThreads.arraySize(); + View v = null; + J3dThreadData lastRunThread = null; + waitTimestamp++; + sleepTime = Long.MAX_VALUE; + + boolean threadToRun = false; + boolean needToSetWork = false; + + for (i=0; i<size; i++) { + thread = threads[i]; + if (thread.canvas == null) { // Only for swap thread + ((Object []) thread.threadArgs)[3] = null; + } + if (thread.view != v) { + thread.view.computeCycleTime(); + if (thread.view.sleepTime < sleepTime) { + sleepTime = thread.view.sleepTime; + } + } + if ((thread.lastUpdateTime > thread.lastRunTime) && + !thread.thread.userStop) { + + if (!thread.view.isMinCycleTimeAchieve) { + thread.needsRun = false; + needToSetWork = true; + continue; + } + + if (thread.thread.lastWaitTimestamp == waitTimestamp) { + // This renderer thread is repeated. We must wait + // until all previous renderer threads done before + // allowing this thread to continue. Note that + // lastRunThread can't be null in this case. + waitTimestamp++; + if (thread.view != v) { + // A new View is start + v = thread.view; + threadToRun = true; + lastRunThread.threadOpts = + (J3dThreadData.STOP_TIMER | + J3dThreadData.WAIT_ALL_THREADS); + ((Object []) lastRunThread.threadArgs)[3] = lastRunThread.view; + thread.threadOpts = (J3dThreadData.START_TIMER | + J3dThreadData.CONT_THREAD); + } else { + if ((lastRunThread.threadOpts & + J3dThreadData.START_TIMER) != 0) { + lastRunThread.threadOpts = + (J3dThreadData.START_TIMER | + J3dThreadData.WAIT_ALL_THREADS); + + } else { + lastRunThread.threadOpts = + J3dThreadData.WAIT_ALL_THREADS; + } + thread.threadOpts = J3dThreadData.CONT_THREAD; + + } + } else { + if (thread.view != v) { + v = thread.view; + threadToRun = true; + // Although the renderer thread is not + // repeated. We still need to wait all + // previous renderer threads if new View + // start. + if (lastRunThread != null) { + lastRunThread.threadOpts = + (J3dThreadData.STOP_TIMER | + J3dThreadData.WAIT_ALL_THREADS); + ((Object []) lastRunThread.threadArgs)[3] + = lastRunThread.view; + } + thread.threadOpts = (J3dThreadData.START_TIMER | + J3dThreadData.CONT_THREAD); + } else { + thread.threadOpts = J3dThreadData.CONT_THREAD; + } + } + thread.thread.lastWaitTimestamp = waitTimestamp; + thread.needsRun = true; + thread.lastRunTime = currentTime; + lastRunThread = thread; + } else { + thread.needsRun = false; + } + } + + + if (lastRunThread != null) { + lastRunThread.threadOpts = + (J3dThreadData.STOP_TIMER | + J3dThreadData.WAIT_ALL_THREADS| + J3dThreadData.LAST_STOP_TIMER); + lockGeometry = true; + ((Object []) lastRunThread.threadArgs)[3] = lastRunThread.view; + } else { + lockGeometry = false; + } + + + if (needToSetWork && !threadToRun) { + sleepTime -= (currentTime - lastTime); + if (sleepTime > 0) { + runMonitor(SLEEP, null, null, null, null); + } + // Need to invoke MC to do work + // next time after sleep + setWork(); + } + + } + + private void createUpdateThread(J3dStructure structure) { + final J3dStructure s = structure; + + if (s.updateThread == null) { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + synchronized (rootThreadGroup) { + s.updateThread = new StructureUpdateThread( + rootThreadGroup, s, s.threadType); + s.updateThread.setPriority(threadPriority); + } + return null; + } + }); + s.updateThread.initialize(); + s.threadData.thread = s.updateThread; + // This takes into accout for thread that just destroy and + // create again. In this case the threadData may receive + // message before the thread actually created. We don't want + // the currentTime to overwrite the update time of which + // is set by threadData when get message. + s.threadData.lastUpdateTime = Math.max(currentTime, + s.threadData.lastUpdateTime); + } + } + + private void emptyMessageList(J3dStructure structure, View v) { + if (structure != null) { + if (v == null) { + if (structure.threadData != null) { + structure.threadData.thread = null; + } + + if (structure.updateThread != null) { + structure.updateThread.structure = null; + } + structure.updateThread = null; + } + boolean otherViewExist = false; + if ((v != null) && (v.universe != null)) { + // Check if there is any other View register with the + // same universe + for (int i=views.size()-1; i >= 0; i--) { + if (((View) views.get(i)).universe == v.universe) { + otherViewExist = true; + break; + } + } + } + + + UnorderList mlist = structure.messageList; + // Note that message is add at the end of array + synchronized (mlist) { + int size = mlist.size(); + if (size > 0) { + J3dMessage mess[] = (J3dMessage []) mlist.toArray(false); + J3dMessage m; + int i = 0; + + Object oldRef= null; + while (i < size) { + m = mess[i]; + if ((v == null) || (m.view == v) || + ((m.view == null) && !otherViewExist)) { + if (m.type == J3dMessage.INSERT_NODES) { + // There is another View register request + // immediately following, so no need + // to remove message. + break; + } + // Some other thread may still using this + // message so we should not directly + // add this message to free lists + m.decRefcount(); + mlist.removeOrdered(i); + size--; + } else { + i++; + } + } + } + } + } + } + + private void destroyUpdateThread(J3dStructure structure) { + // If unregisterView message got before EMPTY_UNIVERSE + // message, then updateThread is already set to null. + if (structure.updateThread != null) { + structure.updateThread.finish(); + structure.updateThread.structure = null; + structure.updateThread = null; + } + structure.threadData.thread = null; + structure.clearMessages(); + } + + /** + * This register a View with MasterControl. + * The View has at least one Canvas3D added to a container. + */ + private void registerView(View v) { + final VirtualUniverse univ = v.universe; + + if (views.contains(v) && regUniverseList.contains(univ)) { + return; // already register + } + + if (timerThread == null) { + // This handle the case when MC shutdown and restart in + // a series of pending request + running = true; + createMCThreads(); + } + // If viewId is null, assign one .. + v.assignViewId(); + + // Create thread if not done before + createUpdateThread(univ.behaviorStructure); + createUpdateThread(univ.geometryStructure); + createUpdateThread(univ.soundStructure); + createUpdateThread(univ.renderingEnvironmentStructure); + createUpdateThread(univ.transformStructure); + + // create Behavior scheduler + J3dThreadData threadData = null; + + if (univ.behaviorScheduler == null) { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + synchronized (rootThreadGroup) { + univ.behaviorScheduler = new BehaviorScheduler( + rootThreadGroup, univ); + univ.behaviorScheduler.setPriority(threadPriority); + } + return null; + } + }); + univ.behaviorScheduler.initialize(); + univ.behaviorScheduler.userStop = v.stopBehavior; + threadData = univ.behaviorScheduler.getThreadData(null, null); + threadData.thread = univ.behaviorScheduler; + threadData.threadType = J3dThread.BEHAVIOR_SCHEDULER; + threadData.lastUpdateTime = Math.max(currentTime, + threadData.lastUpdateTime); + } + + createUpdateThread(v.renderBin); + createUpdateThread(v.soundScheduler); + + if (v.physicalEnvironment != null) { + v.physicalEnvironment.addUser(v); + } + // create InputDeviceScheduler + evaluatePhysicalEnv(v); + + regUniverseList.addUnique(univ); + views.addUnique(v); + } + + + + /** + * This unregister a View with MasterControl. + * The View no longer has any Canvas3Ds in a container. + */ + private void unregisterView(View v) { + + if (!views.remove(v)) { + v.active = false; + v.doneUnregister = true; + return; // already unregister + } + + if (v.active) { + viewDeactivate(v); + } + + if(J3dDebug.devPhase) { + J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1, + "MC: Destroy Sound Scheduler and RenderBin Update thread"); + } + + v.soundScheduler.updateThread.finish(); + v.renderBin.updateThread.finish(); + v.soundScheduler.updateThread = null; + v.renderBin.updateThread = null; + + // remove VirtualUniverse related threads if Universe + // is empty + VirtualUniverse univ = v.universe; + int i; + + synchronized (timeLock) { + // The reason we need to sync. with timeLock is because we + // don't want user thread running sendMessage() to + // dispatch it in different structure queue when + // part of the structure list is empty at the same time. + // This will cause inconsistence in the message reference + // count. + emptyMessageList(v.soundScheduler, v); + emptyMessageList(v.renderBin, v); + + if (univ.isEmpty()) { + destroyUniverseThreads(univ); + } else { + emptyMessageList(univ.behaviorStructure, v); + emptyMessageList(univ.geometryStructure, v); + emptyMessageList(univ.soundStructure, v); + emptyMessageList(univ.renderingEnvironmentStructure, v); + emptyMessageList(univ.transformStructure, v); + } + } + + if (v.physicalEnvironment != null) { + v.physicalEnvironment.removeUser(v); + } + + // remove all InputDeviceScheduler if this is the last View + UnorderList list = new UnorderList(1, PhysicalEnvironment.class); + for (Enumeration e = PhysicalEnvironment.physicalEnvMap.keys(); + e.hasMoreElements(); ) { + PhysicalEnvironment phyEnv = (PhysicalEnvironment) e.nextElement(); + InputDeviceScheduler sched = (InputDeviceScheduler) + PhysicalEnvironment.physicalEnvMap.get(phyEnv); + for (i=phyEnv.users.size()-1; i>=0; i--) { + if (views.contains((View) phyEnv.users.get(i))) { + // at least one register view refer to it. + break; + } + } + if (i < 0) { + if(J3dDebug.devPhase) { + J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1, + "MC: Destroy InputDeviceScheduler thread " + + sched); + } + sched.finish(); + phyEnv.inputsched = null; + list.add(phyEnv); + } + } + for (i=list.size()-1; i>=0; i--) { + PhysicalEnvironment.physicalEnvMap.remove(list.get(i)); + } + + + freeContext(v); + + if (views.isEmpty()) { + if(J3dDebug.devPhase) { + J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1, + "MC: Destroy all Renderers"); + } + // remove all Renderers if this is the last View + for (Enumeration e = Screen3D.deviceRendererMap.elements(); + e.hasMoreElements(); ) { + Renderer rdr = (Renderer) e.nextElement(); + Screen3D scr; + + rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP; + runMonitor(RUN_RENDERER_CLEANUP, null, null, null, rdr); + scr = rdr.onScreen; + if (scr != null) { + if (scr.renderer != null) { + rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP; + runMonitor(RUN_RENDERER_CLEANUP, null, null, + null, scr.renderer); + scr.renderer = null; + } + + } + scr = rdr.offScreen; + if (scr != null) { + if (scr.renderer != null) { + rendererCleanupArgs[2] = REMOVEALLCTXS_CLEANUP; + runMonitor(RUN_RENDERER_CLEANUP, null, null, + null, scr.renderer); + scr.renderer = null; + } + } + rdr.onScreen = null; + rdr.offScreen = null; + + } + + // cleanup ThreadData corresponds to the view in renderer + for (Enumeration e = Screen3D.deviceRendererMap.elements(); + e.hasMoreElements(); ) { + Renderer rdr = (Renderer) e.nextElement(); + rdr.cleanup(); + } + // We have to reuse renderer even though MC exit + // see bug 4363279 + // Screen3D.deviceRendererMap.clear(); + + } else { + // cleanup ThreadData corresponds to the view in renderer + for (Enumeration e = Screen3D.deviceRendererMap.elements(); + e.hasMoreElements(); ) { + Renderer rdr = (Renderer) e.nextElement(); + rdr.cleanupView(); + } + } + + + freeMessageList.add(univ); + freeMessageList.add(v); + + evaluateAllCanvases(); + stateWorkThreads.clear(); + renderWorkThreads.clear(); + requestRenderWorkThreads.clear(); + threadListsChanged = true; + + // This notify VirtualUniverse waitForMC() thread to continue + v.doneUnregister = true; + } + + + /** + * This procedure create MC thread that start together with MC. + */ + void createMCThreads() { + + // There is only one renderingAttributesUpdate Thread globally + createUpdateThread(renderingAttributesStructure); + + // Create timer thread + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + synchronized (rootThreadGroup) { + timerThread = new TimerThread(rootThreadGroup); + timerThread.setPriority(threadPriority); + } + return null; + } + }); + timerThread.start(); + } + + /** + * Destroy all VirtualUniverse related threads. + * This procedure may call two times when Locale detach in a + * live viewPlatform. + */ + private void destroyUniverseThreads(VirtualUniverse univ) { + + if (regUniverseList.contains(univ)) { + if (J3dDebug.devPhase) { + J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1, + "MC: Destroy universe threads " + univ); + } + destroyUpdateThread(univ.behaviorStructure); + destroyUpdateThread(univ.geometryStructure); + destroyUpdateThread(univ.soundStructure); + destroyUpdateThread(univ.renderingEnvironmentStructure); + destroyUpdateThread(univ.transformStructure); + univ.behaviorScheduler.finish(); + univ.behaviorScheduler.free(); + univ.behaviorScheduler = null; + univ.initMCStructure(); + activeUniverseList.remove(univ); + regUniverseList.remove(univ); + } else { + emptyMessageList(univ.behaviorStructure, null); + emptyMessageList(univ.geometryStructure, null); + emptyMessageList(univ.soundStructure, null); + emptyMessageList(univ.renderingEnvironmentStructure, null); + emptyMessageList(univ.transformStructure, null); + } + + if (regUniverseList.isEmpty() && views.isEmpty()) { + if(J3dDebug.devPhase) { + J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1, + "MC: Destroy RenderingAttributes Update and Timer threads"); + } + if (renderingAttributesStructure.updateThread != null) { + renderingAttributesStructure.updateThread.finish(); + renderingAttributesStructure.updateThread = null; + } + renderingAttributesStructure.messageList.clear(); + renderingAttributesStructure.objList = new ArrayList(); + renderingAttributesStructure = new RenderingAttributesStructure(); + if (timerThread != null) { + timerThread.finish(); + timerThread = null; + } + + // shouldn't all of these be synchronized ??? + synchronized (VirtualUniverse.mc.deviceScreenMap) { + deviceScreenMap.clear(); + } + FreeListManager.clearList(FreeListManager.MESSAGE); + FreeListManager.clearList(FreeListManager.BHLEAF); + FreeListManager.clearList(FreeListManager.BHINTERNAL); + mirrorObjects.clear(); + // Note: We should not clear the DISPLAYLIST/TEXTURE + // list here because other structure may release them + // later + + FreeListManager.clearList(FreeListManager.CANVASBIT); + canvasBitCount = 0; + renderOnceList.clear(); + timestampUpdateList.clear(); + + FreeListManager.clearList(FreeListManager.TRANSFORM3D); + defaultRenderMethod = null; + text3DRenderMethod = null; + vertexArrayRenderMethod = null; + displayListRenderMethod = null; + compressedGeometryRenderMethod = null; + orientedShape3DRenderMethod = null; + // Terminate MC thread + running = false; + } + } + + /** + * Note that we have to go through all views instead of + * evaluate only the canvas in a single view since each screen + * may share by multiple view + */ + private void evaluateAllCanvases() { + + synchronized (renderThreadData) { + // synchronized to prevent lost message when + // renderThreadData is clear + + // First remove all renderrenderThreadData + renderThreadData.clear(); + + // Second reset canvasCount to zero + View viewArr[] = (View []) views.toArray(false); + for (int i=views.size()-1; i>=0; i--) { + viewArr[i].getCanvasList(true); // force canvas cache update + Screen3D screens[] = viewArr[i].getScreens(); + for (int j=screens.length-1; j>=0; j--) { + screens[j].canvasCount = 0; + } + } + + + // Third create render thread and message thread + for (int i=views.size()-1; i>=0; i--) { + View v = viewArr[i]; + Canvas3D canvasList[][] = v.getCanvasList(false); + if (!v.active) { + continue; + } + + for (int j=canvasList.length-1; j>=0; j--) { + boolean added = false; + + for (int k=canvasList[j].length-1; k>=0; k--) { + Canvas3D cv = canvasList[j][k]; + + final Screen3D screen = cv.screen; + + if (cv.active) { + if (screen.canvasCount++ == 0) { + // Create Renderer, one per screen + if (screen.renderer == null) { + // get the renderer created for the graphics + // device of the screen of the canvas + // No need to synchronized since only + // MC use it. + Renderer rdr = + (Renderer) screen.deviceRendererMap.get( + cv.screen.graphicsDevice); + if (rdr == null) { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + + synchronized (rootThreadGroup) { + screen.renderer + = new Renderer( + rootThreadGroup); + screen.renderer.setPriority(threadPriority); + } + return null; + } + }); + screen.renderer.initialize(); + screen.deviceRendererMap.put( + screen.graphicsDevice, screen.renderer); + } else { + screen.renderer = rdr; + } + } + } + // offScreen canvases will be handled by the + // request renderer, so don't add offScreen canvas + // the render list + if (!cv.offScreen) { + screen.renderer.onScreen = screen; + } else { + screen.renderer.offScreen = screen; + continue; + } + + if (!added) { + // Swap message data thread, one per + // screen only. Note that we don't set + // lastUpdateTime for this thread so + // that it won't run in the first round + J3dThreadData renderData = + screen.renderer.getThreadData(v, null); + renderThreadData.add(renderData); + + // only if renderBin is ready then we + // update the lastUpdateTime to make it run + if (v.renderBinReady) { + renderData.lastUpdateTime = + Math.max(currentTime, + renderData.lastUpdateTime); + } + added = true; + } + // Renderer message data thread + J3dThreadData renderData = + screen.renderer.getThreadData(v, cv); + renderThreadData.add(renderData); + if (v.renderBinReady) { + renderData.lastUpdateTime = + Math.max(currentTime, + renderData.lastUpdateTime); + } + } + } + } + + } + } + + threadListsChanged = true; + } + + private void evaluatePhysicalEnv(View v) { + final PhysicalEnvironment env = v.physicalEnvironment; + + if (env.inputsched == null) { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + synchronized (rootThreadGroup) { + env.inputsched = new InputDeviceScheduler( + rootThreadGroup, + env); + env.inputsched.setPriority(threadPriority); + } + return null; + } + }); + env.inputsched.start(); + PhysicalEnvironment.physicalEnvMap.put(env, env.inputsched); + } + threadListsChanged = true; + } + + final private void addToStateThreads(J3dThreadData threadData) { + if (threadData.thread.active) { + stateWorkThreads.add(threadData); + } + } + + + private void assignNewPrimaryView(VirtualUniverse univ) { + + View currentPrimary = univ.getCurrentView(); + + if (currentPrimary != null) { + currentPrimary.primaryView = false; + } + + View v[] = (View []) views.toArray(false); + int nviews = views.size(); + for (int i=0; i<nviews; i++) { + View view = v[i]; + if (view.active && view.isRunning && + (univ == view.universe)) { + view.primaryView = true; + univ.setCurrentView(view); + return; + } + } + univ.setCurrentView(null); + } + + + /** + * This returns the default RenderMethod + */ + RenderMethod getDefaultRenderMethod() { + if (defaultRenderMethod == null) { + defaultRenderMethod = new DefaultRenderMethod(); + } + return defaultRenderMethod; + } + + /** + * This returns the text3d RenderMethod + */ + RenderMethod getText3DRenderMethod() { + if (text3DRenderMethod == null) { + text3DRenderMethod = new Text3DRenderMethod(); + } + return text3DRenderMethod; + } + + + /** + * This returns the vertexArray RenderMethod + */ + RenderMethod getVertexArrayRenderMethod() { + if (vertexArrayRenderMethod == null) { + vertexArrayRenderMethod = new VertexArrayRenderMethod(); + } + return vertexArrayRenderMethod; + } + + /** + * This returns the displayList RenderMethod + */ + RenderMethod getDisplayListRenderMethod() { + if (displayListRenderMethod == null) { + displayListRenderMethod = new DisplayListRenderMethod(); + } + return displayListRenderMethod; + } + + /** + * This returns the compressed geometry RenderMethod + */ + RenderMethod getCompressedGeometryRenderMethod() { + if (compressedGeometryRenderMethod == null) { + compressedGeometryRenderMethod = + new CompressedGeometryRenderMethod(); + } + return compressedGeometryRenderMethod; + } + + /** + * This returns the oriented shape3d RenderMethod + */ + RenderMethod getOrientedShape3DRenderMethod() { + if (orientedShape3DRenderMethod == null) { + orientedShape3DRenderMethod = new OrientedShape3DRenderMethod(); + } + return orientedShape3DRenderMethod; + } + + /** + * This notifies MasterControl that the given view has been activated + */ + private void viewActivate(View v) { + + VirtualUniverse univ = v.universe; + + if (univ == null) { + return; + } + + if (!views.contains(v) || !regUniverseList.contains(univ)) { + registerView(v); + } else if (v.active) { + evaluateAllCanvases(); + return; + } + + if ((univ.activeViewCount == 0)) { + univ.geometryStructure.resetConditionMet(); + univ.behaviorStructure.resetConditionMet(); + } + + if (v.isRunning) { + numActiveViews++; + univ.activeViewCount++; + renderingAttributesStructure.updateThread.active = true; + univ.transformStructure.updateThread.active = true; + univ.geometryStructure.updateThread.active = true; + univ.soundStructure.updateThread.active = true; + univ.renderingEnvironmentStructure.updateThread.active = true; + } + univ.behaviorScheduler.active = true; + univ.behaviorStructure.updateThread.active = true; + + + activeUniverseList.addUnique(univ); + + if (v.isRunning) { + v.soundScheduler.activate(); + v.renderBin.updateThread.active = true; + } + v.active = true; + + if (v.physicalEnvironment.activeViewRef++ == 0) { + v.physicalEnvironment.inputsched.activate(); + } + + + if (univ.getCurrentView() == null) { + assignNewPrimaryView(univ); + } + + evaluateAllCanvases(); + v.inRenderThreadData = true; + threadListsChanged = true; + // Notify GeometryStructure to query visible atom again + // We should send message instead of just setting + // v.vDirtyMask = View.VISIBILITY_POLICY_DIRTY; + // since RenderBin may not run immediately next time. + // In this case the dirty flag will lost since + // updateViewCache() will reset it to 0. + v.renderBin.reactivateView = true; + } + + /** + * Release context associate with view + */ + private void freeContext(View v) { + Canvas3D[][] canvasList = v.getCanvasList(false); + + for (int j=canvasList.length-1; j>=0; j--) { + for (int k=canvasList[j].length-1; k>=0; k--) { + Canvas3D cv = canvasList[j][k]; + if (!cv.validCanvas) { + if ((cv.screen != null) && + (cv.screen.renderer != null)) { + rendererCleanupArgs[1] = cv; + rendererCleanupArgs[2] = FREECONTEXT_CLEANUP; + runMonitor(RUN_RENDERER_CLEANUP, null, null, null, + cv.screen.renderer); + rendererCleanupArgs[1] = null; + } + } + } + } + } + + /** + * This notifies MasterControl that the given view has been deactivated + */ + private void viewDeactivate(View v) { + + if (!views.contains(v) || !v.active) { + v.active = false; + evaluateAllCanvases(); + return; + } + + VirtualUniverse univ = v.universe; + + if (v.isRunning) { + // if stopView() invoke before, no need to decrement count + --numActiveViews; + --univ.activeViewCount; + } + + if (numActiveViews == 0) { + renderingAttributesStructure.updateThread.active = false; + } + + if (univ.activeViewCount == 0) { + // check if destroyUniverseThread invoked before + if (univ.behaviorScheduler != null) { + univ.behaviorScheduler.deactivate(); + univ.transformStructure.updateThread.active = false; + univ.geometryStructure.updateThread.active = false; + univ.behaviorStructure.updateThread.active = false; + univ.soundStructure.updateThread.active = false; + univ.renderingEnvironmentStructure.updateThread.active + = false; + activeUniverseList.remove(univ); + } + } + + v.soundScheduler.deactivate(); + v.renderBin.updateThread.active = false; + v.active = false; + if (--v.physicalEnvironment.activeViewRef == 0) { + v.physicalEnvironment.inputsched.deactivate(); + } + assignNewPrimaryView(univ); + + + evaluateAllCanvases(); + + freeContext(v); + + v.inRenderThreadData = false; + threadListsChanged = true; + } + + + /** + * This notifies MasterControl to start given view + */ + private void startView(View v) { + + if (!views.contains(v) || v.isRunning || !v.active) { + v.isRunning = true; + return; + } + + numActiveViews++; + renderingAttributesStructure.updateThread.active = true; + + VirtualUniverse univ = v.universe; + + univ.activeViewCount++; + univ.transformStructure.updateThread.active = true; + univ.geometryStructure.updateThread.active = true; + univ.soundStructure.updateThread.active = true; + univ.renderingEnvironmentStructure.updateThread.active = true; + v.renderBin.updateThread.active = true; + v.soundScheduler.activate(); + v.isRunning = true; + if (univ.getCurrentView() == null) { + assignNewPrimaryView(univ); + } + threadListsChanged = true; + } + + + /** + * This notifies MasterControl to stop given view + */ + private void stopView(View v) { + if (!views.contains(v) || !v.isRunning || !v.active) { + v.isRunning = false; + return; + } + + if (--numActiveViews == 0) { + renderingAttributesStructure.updateThread.active = false; + } + VirtualUniverse univ = v.universe; + + if (--univ.activeViewCount == 0) { + univ.transformStructure.updateThread.active = false; + univ.geometryStructure.updateThread.active = false; + univ.renderingEnvironmentStructure.updateThread.active = false; + univ.soundStructure.updateThread.active = false; + } + + v.renderBin.updateThread.active = false; + v.soundScheduler.deactivate(); + v.isRunning = false; + assignNewPrimaryView(univ); + threadListsChanged = true; + } + + // Call from user thread + void addInputDeviceScheduler(InputDeviceScheduler ds) { + synchronized (inputDeviceThreads) { + inputDeviceThreads.add(ds); + if (inputDeviceThreads.size() == 1) { + timerThread.addInputDeviceSchedCond(); + } + } + postRequest(INPUTDEVICE_CHANGE, null); + } + + // Call from user thread + void removeInputDeviceScheduler(InputDeviceScheduler ds) { + inputDeviceThreads.remove(ds); + postRequest(INPUTDEVICE_CHANGE, null); + } + + /** + * Add an object to the mirror object list + */ + void addMirrorObject(ObjectUpdate o) { + mirrorObjects.add(o); + } + + /** + * This updates any mirror objects. It is called when threads + * are done. + */ + void updateMirrorObjects() { + ObjectUpdate objs[] = (ObjectUpdate []) mirrorObjects.toArray(false); + int sz = mirrorObjects.arraySize(); + + for (int i = 0; i< sz; i++) { + objs[i].updateObject(); + } + mirrorObjects.clear(); + } + + + /** + * This fun little method does all the hard work of setting up the + * work thread list. + */ + private void updateWorkThreads() { + + stateWorkThreads.clear(); + renderWorkThreads.clear(); + requestRenderWorkThreads.clear(); + + // First the global rendering attributes structure update + if (numActiveViews > 0) { + addToStateThreads(renderingAttributesStructure.getUpdateThreadData()); + } + + // Next, each of the transform structure updates + VirtualUniverse universes[] = (VirtualUniverse []) + activeUniverseList.toArray(false); + VirtualUniverse univ; + int i; + int size = activeUniverseList.arraySize(); + + for (i=size-1; i>=0; i--) { + addToStateThreads(universes[i].transformStructure.getUpdateThreadData()); + } + lastTransformStructureThread = stateWorkThreads.size(); + + // Next, the GeometryStructure, BehaviorStructure, + // RenderingEnvironmentStructure, and SoundStructure + for (i=size-1; i>=0; i--) { + univ = universes[i]; + addToStateThreads(univ.geometryStructure.getUpdateThreadData()); + addToStateThreads(univ.behaviorStructure.getUpdateThreadData()); + addToStateThreads(univ.renderingEnvironmentStructure.getUpdateThreadData()); + addToStateThreads(univ.soundStructure.getUpdateThreadData()); + } + + lastStructureUpdateThread = stateWorkThreads.size(); + + // Next, the BehaviorSchedulers + for (i=size-1; i>=0; i--) { + addToStateThreads(universes[i].behaviorScheduler. + getThreadData(null, null)); + } + + + // Now InputDeviceScheduler + + InputDeviceScheduler ds[] = (InputDeviceScheduler []) + inputDeviceThreads.toArray(true); + for (i=inputDeviceThreads.size()-1; i >=0; i--) { + J3dThreadData threadData = ds[i].getThreadData(); + threadData.thread.active = true; + addToStateThreads(threadData); + } + + // Now the RenderBins and SoundSchedulers + View viewArr[] = (View []) views.toArray(false); + J3dThreadData thread; + + for (i=views.size()-1; i>=0; i--) { + View v = viewArr[i]; + if (v.active && v.isRunning) { + addToStateThreads(v.renderBin.getUpdateThreadData()); + addToStateThreads(v.soundScheduler.getUpdateThreadData()); + Canvas3D canvasList[][] = v.getCanvasList(false); + int longestScreenList = v.getLongestScreenList(); + Object args[] = null; + // renderer render + for (int j=0; j<longestScreenList; j++) { + for (int k=0; k < canvasList.length; k++) { + if (j < canvasList[k].length) { + Canvas3D cv = canvasList[k][j]; + if (cv.active && cv.isRunningStatus && !cv.offScreen) { + if (cv.screen.renderer == null) { + continue; + } + thread = cv.screen.renderer.getThreadData(v, cv); + renderWorkThreads.add(thread); + args = (Object []) thread.threadArgs; + args[0] = RENDER; + args[1] = cv; + args[2] = v; + } + } + } + } + + // renderer swap + for (int j=0; j<canvasList.length; j++) { + for (int k=0; k < canvasList[j].length; k++) { + Canvas3D cv = canvasList[j][k]; + // create swap thread only if there is at + // least one active canvas + if (cv.active && cv.isRunningStatus && !cv.offScreen) { + if (cv.screen.renderer == null) { + // Should not happen + continue; + } + thread = cv.screen.renderer.getThreadData(v, null); + renderWorkThreads.add(thread); + args = (Object []) thread.threadArgs; + args[0] = SWAP; + args[1] = v; + args[2] = canvasList[j]; + break; + } + } + } + } + } + + thread = null; + + for (Enumeration e = Screen3D.deviceRendererMap.elements(); + e.hasMoreElements(); ) { + Renderer rdr = (Renderer) e.nextElement(); + thread = rdr.getThreadData(null, null); + requestRenderWorkThreads.add(thread); + thread.threadOpts = J3dThreadData.CONT_THREAD; + ((Object[]) thread.threadArgs)[0] = REQUESTRENDER; + } + + if (thread != null) { + thread.threadOpts |= J3dThreadData.WAIT_ALL_THREADS; + } + + threadListsChanged = false; + + // dumpWorkThreads(); + } + + + void dumpWorkThreads() { + System.err.println("-----------------------------"); + System.err.println("MasterControl/dumpWorkThreads"); + + J3dThreadData threads[]; + int size = 0; + + for (int k=0; k<3; k++) { + switch (k) { + case 0: + threads = (J3dThreadData []) stateWorkThreads.toArray(false); + size = stateWorkThreads.arraySize(); + break; + case 1: + threads = (J3dThreadData []) renderWorkThreads.toArray(false); + size = renderWorkThreads.arraySize(); + break; + default: + threads = (J3dThreadData []) requestRenderWorkThreads.toArray(false); + size = requestRenderWorkThreads.arraySize(); + break; + } + + for (int i=0; i<size; i++) { + J3dThreadData thread = threads[i]; + System.err.println("Thread " + i + ": " + thread.thread); + System.err.println("\tOps: " + thread.threadOpts); + if (thread.threadArgs != null) { + Object[] args = (Object[]) thread.threadArgs; + System.err.print("\tArgs: "); + for (int j=0; j<args.length; j++) { + System.err.print(args[j] + " "); + } + } + System.err.println(""); + } + } + System.err.println("-----------------------------"); + } + + + /** + * A convienence wrapper function for various parts of the system + * to force MC to run. + */ + final void setWork() { + runMonitor(SET_WORK, null, null, null, null); + } + + final void setWorkForRequestRenderer() { + runMonitor(SET_WORK_FOR_REQUEST_RENDERER, null, null, null, null); + } + + /** + * Call from GraphicsConfigTemplate to evaluate current + * capabilities using Renderer thread to invoke native + * graphics library functions. This avoid MT-safe problem + * when using thread directly invoke graphics functions. + */ + void sendRenderMessage(GraphicsConfiguration gc, + Object arg, Integer mtype) { + Renderer rdr = createRenderer(gc); + J3dMessage renderMessage = VirtualUniverse.mc.getMessage(); + renderMessage.threads = J3dThread.RENDER_THREAD; + renderMessage.type = J3dMessage.RENDER_IMMEDIATE; + renderMessage.universe = null; + renderMessage.view = null; + renderMessage.args[0] = null; + renderMessage.args[1] = arg; + renderMessage.args[2] = mtype; + rdr.rendererStructure.addMessage(renderMessage); + VirtualUniverse.mc.setWorkForRequestRenderer(); + } + + /** + * This is the MasterControl work method for Java 3D + */ + void doWork() { + runMonitor(CHECK_FOR_WORK, null, null, null, null); + + if (pendingRequest) { + synchronized (timeLock) { + synchronized (requestObjList) { + handlePendingRequest(); + } + } + } + + if (!running) { + return; + } + + if (threadListsChanged) { // Check for new Threads + updateWorkThreads(); + } + + synchronized (timeLock) { + // This is neccesary to prevent updating + // thread.lastUpdateTime from user thread + // in sendMessage() or sendRunMessage() + updateTimeValues(); + } + + //This is temporary until the view model is updated + View v[] = (View []) views.toArray(false); + for (int i=views.size()-1; i>=0; i--) { + if (v[i].active) { + v[i].updateViewCache(); + // update OrientedShape3D + if ((v[i].viewCache.vcDirtyMask != 0 && + !v[i].renderBin.orientedRAs.isEmpty()) || + (v[i].renderBin.cachedDirtyOrientedRAs != null && + !v[i].renderBin.cachedDirtyOrientedRAs.isEmpty())) { + v[i].renderBin.updateOrientedRAs(); + } + } + } + + runMonitor(RUN_THREADS, stateWorkThreads, renderWorkThreads, + requestRenderWorkThreads, null); + + if (renderOnceList.size() > 0) { + clearRenderOnceList(); + } + + manageMemory(); + + } + + private void handlePendingRequest() { + + Object objs[]; + Integer types[]; + int size; + boolean rendererRun = false; + + objs = requestObjList.toArray(false); + types = (Integer []) requestTypeList.toArray(false); + size = requestObjList.size(); + + for (int i=0; i < size; i++) { + // need to process request in order + Integer type = types[i]; + Object o = objs[i]; + if (type == RESET_CANVAS) { + Canvas3D cv = (Canvas3D) o; + if ((cv.screen != null) && + (cv.screen.renderer != null)) { + rendererCleanupArgs[1] = o; + rendererCleanupArgs[2] = RESETCANVAS_CLEANUP; + runMonitor(RUN_RENDERER_CLEANUP, null, null, null, + cv.screen.renderer); + rendererCleanupArgs[1] = null; + } + cv.reset(); + cv.view = null; + cv.computeViewCache(); + } + else if (type == ACTIVATE_VIEW) { + viewActivate((View) o); + } + else if (type == DEACTIVATE_VIEW) { + viewDeactivate((View) o); + } else if (type == REEVALUATE_CANVAS) { + evaluateAllCanvases(); + } else if (type == INPUTDEVICE_CHANGE) { + inputDeviceThreads.clearMirror(); + threadListsChanged = true; + } else if (type == START_VIEW) { + startView((View) o); + } else if (type == STOP_VIEW) { + View v = (View) o; + // Collision takes 3 rounds to finish its request + if (++v.stopViewCount > 4) { + v.stopViewCount = -1; // reset counter + stopView(v); + } else { + tempViewList.add(v); + } + } else if (type == UNREGISTER_VIEW) { + unregisterView((View) o); + } else if (type == PHYSICAL_ENV_CHANGE) { + evaluatePhysicalEnv((View) o); + } else if (type == EMPTY_UNIVERSE) { + if (views.isEmpty()) { + destroyUniverseThreads((VirtualUniverse) o); + threadListsChanged = true; + } + } else if (type == START_RENDERER) { + ((Canvas3D) o).isRunningStatus = true; + threadListsChanged = true; + } else if (type == STOP_RENDERER) { + if (o instanceof Canvas3D) { + ((Canvas3D) o).isRunningStatus = false; + } else { + ((Renderer) o).userStop = true; + } + threadListsChanged = true; + } else if (type == RENDER_ONCE) { + View v = (View) o; + // temporary start View for renderonce + // it will stop afterwards + startView(v); + renderOnceList.add(v); + sendRunMessage(v, J3dThread.UPDATE_RENDER); + threadListsChanged = true; + rendererRun = true; + } else if (type == FREE_CONTEXT) { + Canvas3D cv = (Canvas3D ) ((Object []) o)[0]; + if ((cv.screen != null) && + (cv.screen.renderer != null)) { + rendererCleanupArgs[1] = o; + rendererCleanupArgs[2] = REMOVECTX_CLEANUP; + runMonitor(RUN_RENDERER_CLEANUP, null, null, null, + cv.screen.renderer); + rendererCleanupArgs[1] = null; + } + rendererRun = true; + } else if (type == FREE_DRAWING_SURFACE) { + DrawingSurfaceObjectAWT.freeDrawingSurface(o); + } else if (type == GETBESTCONFIG) { + GraphicsConfiguration gc = ((GraphicsConfiguration []) + ((GraphicsConfigTemplate3D) o).testCfg)[0]; + sendRenderMessage(gc, o, type); + rendererRun = true; + } else if (type == ISCONFIGSUPPORT) { + GraphicsConfiguration gc = (GraphicsConfiguration) + ((GraphicsConfigTemplate3D) o).testCfg; + sendRenderMessage(gc, o, type); + rendererRun = true; + } else if ((type == SET_GRAPHICSCONFIG_FEATURES) || + (type == SET_QUERYPROPERTIES)) { + GraphicsConfiguration gc = (GraphicsConfiguration) + ((Canvas3D) o).graphicsConfiguration; + sendRenderMessage(gc, o, type); + rendererRun = true; + } else if (type == SET_VIEW) { + Canvas3D cv = (Canvas3D) o; + cv.view = cv.pendingView; + cv.computeViewCache(); + } + } + + // Do it only after all universe/View is register + for (int i=0; i < size; i++) { + Integer type = types[i]; + if (type == FREE_MESSAGE) { + if (objs[i] instanceof VirtualUniverse) { + VirtualUniverse u = (VirtualUniverse) objs[i]; + if (!regUniverseList.contains(u)) { + emptyMessageList(u.behaviorStructure, null); + emptyMessageList(u.geometryStructure, null); + emptyMessageList(u.soundStructure, null); + emptyMessageList(u.renderingEnvironmentStructure, null); + } + } else if (objs[i] instanceof View) { + View v = (View) objs[i]; + if (!views.contains(v)) { + emptyMessageList(v.soundScheduler, v); + emptyMessageList(v.renderBin, v); + if (v.resetUnivCount == v.universeCount) { + v.reset(); + v.universe = null; + if (running == false) { + // MC is about to terminate + + /* + // Don't free list cause there may + // have some other thread returning ID + // after it. + FreeListManager.clearList(FreeListManager.DISPLAYLIST); + FreeListManager.clearList(FreeListManager.TEXTURE2D); + FreeListManager.clearList(FreeListManager.TEXTURE3D); + + synchronized (textureIdLock) { + textureIdCount = 0; + } + */ + } + } + } + } + + } + + } + requestObjList.clear(); + requestTypeList.clear(); + + size = tempViewList.size(); + if (size > 0) { + if (running) { + for (int i=0; i < size; i++) { + requestTypeList.add(STOP_VIEW); + requestObjList.add(tempViewList.get(i)); + } + setWork(); + } else { // MC will shutdown + for (int i=0; i < size; i++) { + View v = (View) tempViewList.get(i); + v.stopViewCount = -1; + v.isRunning = false; + } + } + tempViewList.clear(); + pendingRequest = true; + } else { + pendingRequest = rendererRun || (requestObjList.size() > 0); + + } + + size = freeMessageList.size(); + if (size > 0) { + for (int i=0; i < size; i++) { + requestTypeList.add(FREE_MESSAGE); + requestObjList.add(freeMessageList.get(i)); + } + pendingRequest = true; + freeMessageList.clear(); + } + if (!running && (renderOnceList.size() > 0)) { + clearRenderOnceList(); + } + + if (pendingRequest) { + setWork(); + } + + if (rendererRun) { + running = true; + } + + } + + private void clearRenderOnceList() { + for (int i=renderOnceList.size()-1; i>=0; i--) { + View v = (View) renderOnceList.get(i); + v.renderOnceFinish = true; + // stop after render once + stopView(v); + } + renderOnceList.clear(); + threadListsChanged = true; + + } + + synchronized void runMonitor(int action, + UnorderList stateThreadList, + UnorderList renderThreadList, + UnorderList requestRenderThreadList, + J3dThread nthread) { + + switch (action) { + case RUN_THREADS: + int currentStateThread = 0; + int currentRenderThread = 0; + int currentRequestRenderThread = 0; + View view; + boolean done; + J3dThreadData thread; + J3dThreadData renderThreads[] = (J3dThreadData []) + renderThreadList.toArray(false); + J3dThreadData stateThreads[] = (J3dThreadData []) + stateThreadList.toArray(false); + J3dThreadData requestRenderThreads[] = (J3dThreadData []) + requestRenderThreadList.toArray(false); + int renderThreadSize = renderThreadList.arraySize(); + int stateThreadSize = stateThreadList.arraySize(); + int requestRenderThreadSize = requestRenderThreadList.arraySize(); + + done = false; + + //lock all the needed geometry and image component + View[] allView = (View []) views.toArray(false); + View currentV; + int i; + + if (lockGeometry) + { + for( i = views.arraySize()-1; i >= 0; i--) { + currentV = allView[i]; + currentV.renderBin.lockGeometry(); + } + } + + + while (!done) { + // First try a RenderThread + while (!renderWaiting && + currentRenderThread != renderThreadSize) { + thread = renderThreads[currentRenderThread++]; + if (!thread.needsRun) { + continue; + } + if ((thread.threadOpts & J3dThreadData.START_TIMER) != 0) { + view = (View)((Object[])thread.threadArgs)[2]; + view.frameNumber++; + view.startTime = System.currentTimeMillis(); + } + + + renderPending++; + + if (cpuLimit == 1) { + thread.thread.args = (Object[])thread.threadArgs; + thread.thread.doWork(currentTime); + } else { + threadPending++; + thread.thread.runMonitor(J3dThread.RUN, + currentTime, + (Object[])thread.threadArgs); + } + + if ((thread.threadOpts & J3dThreadData.STOP_TIMER) != 0) { + view = (View)((Object[])thread.threadArgs)[3]; + timestampUpdateList.add(view); + } + + if ((thread.threadOpts & J3dThreadData.LAST_STOP_TIMER) != 0) { + // release lock on locked geometry and image component + for( i = 0; i < views.arraySize(); i++) { + currentV = allView[i]; + currentV.renderBin.releaseGeometry(); + } + } + + if ((cpuLimit != 1) && + (thread.threadOpts & + J3dThreadData.WAIT_ALL_THREADS) != 0) { + + renderWaiting = true; + } + + + if ((cpuLimit != 1) && (cpuLimit <= threadPending)) { + state = WAITING_FOR_CPU; + try { + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + state = RUNNING; + } + + } + // Now try state threads + while (!stateWaiting && + currentStateThread != stateThreadSize) { + thread = stateThreads[currentStateThread++]; + + if (!thread.needsRun) { + continue; + } + + statePending++; + + if (cpuLimit == 1) { + thread.thread.args = (Object[])thread.threadArgs; + thread.thread.doWork(currentTime); + } else { + threadPending++; + thread.thread.runMonitor(J3dThread.RUN, + currentTime, + (Object[])thread.threadArgs); + } + if (cpuLimit != 1 && (thread.threadOpts & + J3dThreadData.WAIT_ALL_THREADS) != 0) { + stateWaiting = true; + } + + if ((cpuLimit != 1) && (cpuLimit <= threadPending)) { + // Fix bug 4686766 - always allow + // renderer thread to continue if not finish + // geomLock can release for Behavior thread to + // continue. + if (currentRenderThread == renderThreadSize) { + state = WAITING_FOR_CPU; + try { + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + state = RUNNING; + } else { + // Run renderer thread next time + break; + } + + } + } + + // Now try requestRender threads + if (!renderWaiting && + (currentRenderThread == renderThreadSize)) { + currentRequestRenderThread = 0; + while (!renderWaiting && + (currentRequestRenderThread != + requestRenderThreadSize)) { + + thread = + requestRenderThreads[currentRequestRenderThread++]; + + renderPending++; + + if (cpuLimit == 1) { + thread.thread.args = (Object[])thread.threadArgs; + thread.thread.doWork(currentTime); + } else { + threadPending++; + thread.thread.runMonitor(J3dThread.RUN, + currentTime, + (Object[])thread.threadArgs); + } + if (cpuLimit != 1 && (thread.threadOpts & + J3dThreadData.WAIT_ALL_THREADS) != 0) { + renderWaiting = true; + } + if (cpuLimit != 1 && cpuLimit <= threadPending) { + state = WAITING_FOR_CPU; + try { + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + state = RUNNING; + } + } + } + + if (cpuLimit != 1) { + if ((renderWaiting && + (currentStateThread == stateThreadSize)) || + (stateWaiting && + currentRenderThread == renderThreadSize) || + (renderWaiting && stateWaiting)) { + if (!requestRenderWorkToDo) { + state = WAITING_FOR_THREADS; + try { + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + state = RUNNING; + } + requestRenderWorkToDo = false; + } + } + + if ((currentStateThread == stateThreadSize) && + (currentRenderThread == renderThreadSize) && + (currentRequestRenderThread == requestRenderThreadSize) && + (threadPending == 0)) { + for (int k=timestampUpdateList.size()-1; k >=0; + k--) { + View v = (View) timestampUpdateList.get(k); + v.setFrameTimingValues(); + v.universe.behaviorStructure.incElapsedFrames(); + } + timestampUpdateList.clear(); + updateMirrorObjects(); + done = true; + } + } + break; + case THREAD_DONE: + if (state != WAITING_FOR_RENDERER_CLEANUP) { + threadPending--; + if (nthread.type == J3dThread.RENDER_THREAD) { + View v = (View) nthread.args[3]; + if (v != null) { // STOP_TIMER + v.stopTime = System.currentTimeMillis(); + } + + if (--renderPending == 0) { + renderWaiting = false; + } + } else { + if (--statePending == 0) { + stateWaiting = false; + } + } + if (state == WAITING_FOR_CPU || state == WAITING_FOR_THREADS) { + notify(); + } + } else { + notify(); + state = RUNNING; + } + break; + case WAIT_FOR_ALL: + while (threadPending != 0) { + state = WAITING_FOR_THREADS; + try { + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + } + break; + case CHECK_FOR_WORK: + if (!workToDo) { + state = SLEEPING; + try { + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + state = RUNNING; + } + workToDo = false; + break; + case SET_WORK: + workToDo = true; + if (state == SLEEPING) { + notify(); + } + break; + case SET_WORK_FOR_REQUEST_RENDERER: + requestRenderWorkToDo = true; + if (state == WAITING_FOR_CPU || state == WAITING_FOR_THREADS || + state == SLEEPING) { + workToDo = true; + notify(); + } + break; + case RUN_RENDERER_CLEANUP: + nthread.runMonitor(J3dThread.RUN, currentTime, + rendererCleanupArgs); + state = WAITING_FOR_RENDERER_CLEANUP; + try { + wait(); + } catch (InterruptedException e) { + System.err.println(e); + } + break; + case SLEEP: + state = SLEEPING; + try { + wait(sleepTime); + } catch (InterruptedException e) { + System.err.println(e); + } + } + } + + // Static initializer + static { + /* + // Determine whether the JVM is version JDK1.5 or later. + // TODO: replace this with code that checks for the existence + // of a class or method that is defined in 1.5, but not in 1.4 + String versionString = + (String) java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + return System.getProperty("java.version"); + } + }); + jvm15OrBetter = !(versionString.startsWith("1.4") || + versionString.startsWith("1.3") || + versionString.startsWith("1.2") || + versionString.startsWith("1.1")); + */ + + // create ThreadGroup + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction() { + public Object run() { + ThreadGroup parent; + Thread thread = Thread.currentThread(); + threadPriority = thread.getPriority(); + rootThreadGroup = thread.getThreadGroup(); + while ((parent = rootThreadGroup.getParent()) != null) { + rootThreadGroup = parent; + } + rootThreadGroup = new ThreadGroup(rootThreadGroup, + "Java3D"); + // use the default maximum group priority + return null; + } + }); + } + + + static String mtype[] = { + "-INSERT_NODES ", + "-REMOVE_NODES ", + "-RUN ", + "-TRANSFORM_CHANGED ", + "-UPDATE_VIEW ", + "-STOP_THREAD ", + "-COLORINGATTRIBUTES_CHANGED ", + "-LINEATTRIBUTES_CHANGED ", + "-POINTATTRIBUTES_CHANGED ", + "-POLYGONATTRIBUTES_CHANGED ", + + "-RENDERINGATTRIBUTES_CHANGED ", + "-TEXTUREATTRIBUTES_CHANGED ", + "-TRANSPARENCYATTRIBUTES_CHANGED ", + "-MATERIAL_CHANGED ", + "-TEXCOORDGENERATION_CHANGED ", + "-TEXTURE_CHANGED ", + "-MORPH_CHANGED ", + "-GEOMETRY_CHANGED ", + "-APPEARANCE_CHANGED ", + "-LIGHT_CHANGED ", + + "-BACKGROUND_CHANGED ", + "-CLIP_CHANGED ", + "-FOG_CHANGED ", + "-BOUNDINGLEAF_CHANGED ", + "-SHAPE3D_CHANGED ", + "-TEXT3D_TRANSFORM_CHANGED ", + "-TEXT3D_DATA_CHANGED ", + "-SWITCH_CHANGED ", + "-COND_MET ", + "-BEHAVIOR_ENABLE ", + + "-BEHAVIOR_DISABLE ", + "-INSERT_RENDERATOMS ", + "-ORDERED_GROUP_INSERTED ", + "-ORDERED_GROUP_REMOVED ", + "-COLLISION_BOUND_CHANGED ", + "-REGION_BOUND_CHANGED ", + "-MODELCLIP_CHANGED ", + "-BOUNDS_AUTO_COMPUTE_CHANGED ", + + "-SOUND_ATTRIB_CHANGED ", + "-AURALATTRIBUTES_CHANGED ", + "-SOUNDSCAPE_CHANGED ", + "-ALTERNATEAPPEARANCE_CHANGED ", + "-RENDER_OFFSCREEN ", + "-RENDER_RETAINED ", + "-RENDER_IMMEDIATE ", + "-SOUND_STATE_CHANGED ", + "-ORIENTEDSHAPE3D_CHANGED ", + "-TEXTURE_UNIT_STATE_CHANGED ", + "-UPDATE_VIEWPLATFORM ", + "-BEHAVIOR_ACTIVATE ", + "-GEOMETRYARRAY_CHANGED ", + "-MEDIA_CONTAINER_CHANGED ", + "-RESIZE_CANVAS ", + "-TOGGLE_CANVAS ", + "-IMAGE_COMPONENT_CHANGED ", + "-SCHEDULING_INTERVAL_CHANGED ", + "-VIEWSPECIFICGROUP_CHANGED ", + "-VIEWSPECIFICGROUP_INIT ", + "-VIEWSPECIFICGROUP_CLEAR ", + "-ORDERED_GROUP_TABLE_CHANGED"}; + + + static void dumpThreads(int threads) { + //dump Threads type + if ((threads & J3dThread.BEHAVIOR_SCHEDULER) != 0) + System.out.println(" BEHAVIOR_SCHEDULER"); + if ((threads & J3dThread.SOUND_SCHEDULER) != 0) + System.out.println(" SOUND_SCHEDULER"); + if ((threads & J3dThread.INPUT_DEVICE_SCHEDULER) != 0) + System.out.println(" INPUT_DEVICE_SCHEDULER"); + + if ((threads & J3dThread.RENDER_THREAD) != 0) + System.out.println(" RENDER_THREAD"); + + if ((threads & J3dThread.UPDATE_GEOMETRY) != 0) + System.out.println(" UPDATE_GEOMETRY"); + if ((threads & J3dThread.UPDATE_RENDER) != 0) + System.out.println(" UPDATE_RENDER"); + if ((threads & J3dThread.UPDATE_BEHAVIOR) != 0) + System.out.println(" UPDATE_BEHAVIOR"); + if ((threads & J3dThread.UPDATE_SOUND) != 0) + System.out.println(" UPDATE_SOUND"); + if ((threads & J3dThread.UPDATE_RENDERING_ATTRIBUTES) != 0) + System.out.println(" UPDATE_RENDERING_ATTRIBUTES"); + if ((threads & J3dThread.UPDATE_RENDERING_ENVIRONMENT) != 0) + System.out.println(" UPDATE_RENDERING_ENVIRONMENT"); + if ((threads & J3dThread.UPDATE_TRANSFORM) != 0) + System.out.println(" UPDATE_TRANSFORM"); + } + + static void dumpmsg(J3dMessage m) { + //dump message type + System.out.println(mtype[m.type]); + + dumpThreads(m.threads); + } + + + int frameCount = 0; + private int frameCountCutoff = 100; + + private void manageMemory() { + if (++frameCount > frameCountCutoff) { + FreeListManager.manageLists(); + frameCount = 0; + } + } + + /** + * Yields the current thread, by sleeping for a small amount of + * time. Unlike <code>Thread.yield()</code>, this method + * guarantees that the current thread will yield to another thread + * waiting to run. It also ensures that the other threads will + * run for at least a small amount of time before the current + * thread runs again. + */ + static final void threadYield() { + // Note that we can't just use Thread.yield(), since it + // doesn't guarantee that it will actually yield the thread + // (and, in fact, it appears to be a no-op under Windows). So + // we will sleep for 1 msec instead. Since most threads use + // wait/notify, and only use this when they are waiting for + // another thread to finish something, this shouldn't be a + // performance concern. + + //Thread.yield(); + try { + Thread.sleep(1); + } + catch (InterruptedException e) { + // Do nothing, since we really don't care how long (or + // even whether) we sleep + } + } +} diff --git a/src/classes/share/javax/media/j3d/MasterControlThread.java b/src/classes/share/javax/media/j3d/MasterControlThread.java new file mode 100644 index 0000000..7a0e869 --- /dev/null +++ b/src/classes/share/javax/media/j3d/MasterControlThread.java @@ -0,0 +1,48 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Master control thread. The MasterControlThread object and thread + * are created dynamically whenever needed. Once created, the thread + * runs until all other threads are terminated. Then the master + * control thread terminates. There is never more than one + * MasterControl object or thread in existence at any one time. + */ +class MasterControlThread extends Thread { + + MasterControlThread(ThreadGroup threadGroup) { + super(threadGroup, "J3D-MasterControl"); + VirtualUniverse.mc.createMCThreads(); + this.start(); + } + + public void run() { + + do { + while (VirtualUniverse.mc.running) { + VirtualUniverse.mc.doWork(); + + // NOTE: no need to call Thread.yield(), since we will + // call wait() if there is no work to do (yield seems + // to be a no-op on Windows anyway) + } + } while (!VirtualUniverse.mc.mcThreadDone()); + + if(J3dDebug.devPhase) { + J3dDebug.doDebug(J3dDebug.masterControl, J3dDebug.LEVEL_1, + "MC: MasterControl Thread Terminate"); + } + + } +} diff --git a/src/classes/share/javax/media/j3d/Material.java b/src/classes/share/javax/media/j3d/Material.java new file mode 100644 index 0000000..16b8ac1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Material.java @@ -0,0 +1,689 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; + +/** + * The Material object defines the appearance of an object under + * illumination. + * If the Material object in an Appearance object is <code>null</code>, + * lighting is disabled for all nodes that use that Appearance object. + * <P> + * The properties that can be set for a Material object are: + * <P><UL> + * <LI>Ambient color - the ambient RGB color reflected off the surface + * of the material. The range of values is 0.0 to 1.0. The default ambient + * color is (0.2, 0.2, 0.2).<p></LI> + * <LI>Diffuse color - the RGB color of the material when illuminated. + * The range of values is 0.0 to 1.0. The default diffuse color is + * (1.0, 1.0, 1.0).<p></LI> + * <LI>Specular color - the RGB specular color of the material (highlights). + * The range of values is 0.0 to 1.0. The default specular color + * is (1.0, 1.0, 1.0).<p></LI> + * <LI>Emissive color - the RGB color of the light the material emits, if + * any. The range of values is 0.0 to 1.0. The default emissive + * color is (0.0, 0.0, 0.0).<p></LI> + * <LI>Shininess - the material's shininess, in the range [1.0, 128.0] + * with 1.0 being not shiny and 128.0 being very shiny. Values outside + * this range are clamped. The default value for the material's + * shininess is 64.<p></LI> + * <LI>Color target - the material color target for per-vertex colors, + * one of: AMBIENT, EMISSIVE, DIFFUSE, SPECULAR, or AMBIENT_AND_DIFFUSE. + * The default target is DIFFUSE.<p></LI> + * </UL> + * + * The Material object also enables or disables lighting. + */ +public class Material extends NodeComponent { + + /** + * For material object, specifies that Material allows reading + * individual component field information. + */ + public static final int + ALLOW_COMPONENT_READ = CapabilityBits.MATERIAL_ALLOW_COMPONENT_READ; + + /** + * For material object, specifies that Material allows reading + * individual component field information. + */ + public static final int + ALLOW_COMPONENT_WRITE = CapabilityBits.MATERIAL_ALLOW_COMPONENT_WRITE; + + /** + * Specifies that per-vertex colors replace the ambient material color. + * @see #setColorTarget + * + * @since Java 3D 1.3 + */ + public static final int AMBIENT = 0; + + /** + * Specifies that per-vertex colors replace the emissive material color. + * @see #setColorTarget + * + * @since Java 3D 1.3 + */ + public static final int EMISSIVE = 1; + + /** + * Specifies that per-vertex colors replace the diffuse material color. + * This is the default target. + * @see #setColorTarget + * + * @since Java 3D 1.3 + */ + public static final int DIFFUSE = 2; + + /** + * Specifies that per-vertex colors replace the specular material color. + * @see #setColorTarget + * + * @since Java 3D 1.3 + */ + public static final int SPECULAR = 3; + + /** + * Specifies that per-vertex colors replace both the ambient and the + * diffuse material color. + * @see #setColorTarget + * + * @since Java 3D 1.3 + */ + public static final int AMBIENT_AND_DIFFUSE = 4; + + + /** + * Constructs and initializes a Material object using default parameters. + * The default values are as follows: + * <ul> + * lighting enable : true<br> + * ambient color : (0.2, 0.2, 0.2)<br> + * emmisive color : (0.0, 0.0, 0.0)<br> + * diffuse color : (1.0, 1.0, 1.0)<br> + * specular color : (1.0, 1.0, 1.0)<br> + * shininess : 64<br> + * color target : DIFFUSE + * </ul> + */ + public Material() { + // Just use the defaults + } + + /** + * Constructs and initializes a new material object using the specified + * parameters. Lighting is enabled by default. + * @param ambientColor the material's ambient color + * @param emissiveColor the material's emissive color + * @param diffuseColor the material's diffuse color when illuminated by a + * light + * @param specularColor the material's specular color when illuminated + * to generate a highlight + * @param shininess the material's shininess in the + * range [1.0, 128.0] with 1.0 being not shiny and 128.0 being very shiny. + * Values outside this range are clamped. + */ + public Material(Color3f ambientColor, + Color3f emissiveColor, + Color3f diffuseColor, + Color3f specularColor, + float shininess) { + ((MaterialRetained)this.retained).createMaterial(ambientColor, + emissiveColor, diffuseColor, specularColor, + shininess); + } + + /** + * Creates a retained mode MaterialRetained object that this + * Material component object will point to. + */ + void createRetained() { + this.retained = new MaterialRetained(); + this.retained.setSource(this); + } + + /** + * Sets this material's ambient color. + * This specifies how much ambient light is reflected by + * the surface. + * The ambient color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is either AMBIENT or AMBIENT_AND_DIFFUSE, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's ambient color in the lighting equation. + * + * @param color the material's ambient color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setAmbientColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + if (isLive()) { + ((MaterialRetained)this.retained).setAmbientColor(color); + } + else { + ((MaterialRetained)this.retained).initAmbientColor(color); + } + } + + /** + * Sets this material's ambient color. + * This specifies how much ambient light is reflected by + * the surface. + * The ambient color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is either AMBIENT or AMBIENT_AND_DIFFUSE, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's ambient color in the lighting equation. + * + * @param r the new ambient color's red component + * @param g the new ambient color's green component + * @param b the new ambient color's blue component + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setAmbientColor(float r, float g, float b) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + if (isLive()) { + ((MaterialRetained)this.retained).setAmbientColor(r,g,b); + } + else { + ((MaterialRetained)this.retained).initAmbientColor(r,g,b); + } + } + + /** + * Retrieves this material's ambient color. + * @param color that will contain the material's ambient color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getAmbientColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Material2")); + + ((MaterialRetained)this.retained).getAmbientColor(color); + } + + /** + * Sets this material's emissive color. + * This is the color of light, if any, that the material emits. + * The emissive color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is EMISSIVE, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's emissive color in the lighting equation. + * + * @param color the new emissive color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setEmissiveColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + if (isLive()) + ((MaterialRetained)this.retained).setEmissiveColor(color); + else + ((MaterialRetained)this.retained).initEmissiveColor(color); + + + + + } + + /** + * Sets this material's emissive color. + * This is the color of light, if any, that the material emits. + * The emissive color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is EMISSIVE, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's emissive color in the lighting equation. + * + * @param r the new emissive color's red component + * @param g the new emissive color's green component + * @param b the new emissive color's blue component + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setEmissiveColor(float r, float g, float b) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + + if (isLive()) + ((MaterialRetained)this.retained).setEmissiveColor(r,g,b); + else + ((MaterialRetained)this.retained).initEmissiveColor(r,g,b); + } + + /** + * Retrieves this material's emissive color and stores it in the + * argument provided. + * @param color the vector that will receive this material's emissive color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getEmissiveColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Material2")); + + ((MaterialRetained)this.retained).getEmissiveColor(color); + } + + /** + * Sets this material's diffuse color. + * This is the color of the material when illuminated by a light source. + * The diffuse color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is either DIFFUSE or AMBIENT_AND_DIFFUSE, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's diffuse color in the lighting equation. + * + * @param color the new diffuse color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setDiffuseColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + + if (isLive()) + ((MaterialRetained)this.retained).setDiffuseColor(color); + else + ((MaterialRetained)this.retained).initDiffuseColor(color); + } + + /** + * Sets this material's diffuse color. + * This is the color of the material when illuminated by a light source. + * The diffuse color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is either DIFFUSE or AMBIENT_AND_DIFFUSE, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's diffuse color in the lighting equation. + * + * @param r the new diffuse color's red component + * @param g the new diffuse color's green component + * @param b the new diffuse color's blue component + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setDiffuseColor(float r, float g, float b) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + + if (isLive()) + ((MaterialRetained)this.retained).setDiffuseColor(r,g,b); + else + ((MaterialRetained)this.retained).initDiffuseColor(r,g,b); + } + + /** + * Sets this material's diffuse color plus alpha. + * This is the color of the material when illuminated by a light source. + * The diffuse color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is either DIFFUSE or AMBIENT_AND_DIFFUSE, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's diffuse color in the lighting equation. + * + * @param r the new diffuse color's red component + * @param g the new diffuse color's green component + * @param b the new diffuse color's blue component + * @param a the alpha component used to set transparency + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setDiffuseColor(float r, float g, float b, float a) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + + if (isLive()) + ((MaterialRetained)this.retained).setDiffuseColor(r,g,b,a); + else + ((MaterialRetained)this.retained).initDiffuseColor(r,g,b,a); + } + + /** + * Retrieves this material's diffuse color. + * @param color the vector that will receive this material's diffuse color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDiffuseColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Material2")); + + ((MaterialRetained)this.retained).getDiffuseColor(color); + } + + /** + * Sets this material's specular color. + * This is the specular highlight color of the material. + * The specular color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is SPECULAR, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's specular color in the lighting equation. + * + * @param color the new specular color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setSpecularColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + + if (isLive()) + ((MaterialRetained)this.retained).setSpecularColor(color); + else + ((MaterialRetained)this.retained).initSpecularColor(color); + } + + /** + * Sets this material's specular color. + * This is the specular highlight color of the material. + * The specular color in this Material object may be overridden by + * per-vertex colors in some cases. If vertex colors are present + * in the geometry, and lighting is enabled, and the colorTarget + * is SPECULAR, and vertex colors are + * not being ignored, then the vertex colors are used in place of + * this Material's specular color in the lighting equation. + * + * @param r the new specular color's red component + * @param g the new specular color's green component + * @param b the new specular color's blue component + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see #setColorTarget + */ + public void setSpecularColor(float r, float g, float b) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + + if (isLive()) + ((MaterialRetained)this.retained).setSpecularColor(r,g,b); + else + ((MaterialRetained)this.retained).initSpecularColor(r,g,b); + } + + /** + * Retrieves this material's specular color. + * @param color the vector that will receive this material's specular color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getSpecularColor(Color3f color) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Material2")); + + ((MaterialRetained)this.retained).getSpecularColor(color); + } + + /** + * Sets this material's shininess. + * This specifies a material specular scattering exponent, or + * shininess. It takes a floating point number in the range [1.0, 128.0] + * with 1.0 being not shiny and 128.0 being very shiny. + * Values outside this range are clamped. + * @param shininess the material's shininess + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setShininess(float shininess) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material0")); + + if (isLive()) + ((MaterialRetained)this.retained).setShininess(shininess); + else + ((MaterialRetained)this.retained).initShininess(shininess); + } + + /** + * Retrieves this material's shininess. + * @return the material's shininess + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getShininess() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Material2")); + + return ((MaterialRetained)this.retained).getShininess(); + } + + /** + * Enables or disables lighting for this appearance component object. + * @param state true or false to enable or disable lighting + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setLightingEnable(boolean state) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material15")); + if (isLive()) + ((MaterialRetained)this.retained).setLightingEnable(state); + else + ((MaterialRetained)this.retained).initLightingEnable(state); + } + + /** + * Retrieves the state of the lighting enable flag. + * @return true if lighting is enabled, false if lighting is disabled + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getLightingEnable() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Material16")); + return ((MaterialRetained)this.retained).getLightingEnable(); + } + + /** + * Sets the color target for per-vertex colors. When lighting is + * enabled and per-vertex colors are present (and not ignored) in + * the geometry for a given Shape3D node, those per-vertex colors + * are used in place of the specified material color(s) for this + * Material object. The color target is ignored when lighting is + * disabled or when per-vertex colors are not used. + * The ColorInterpolator behavior also uses the color target to + * determine which color in the associated Material is modified. + * The default target is DIFFUSE. + * + * @param colorTarget one of: AMBIENT, EMISSIVE, DIFFUSE, SPECULAR, or + * AMBIENT_AND_DIFFUSE. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see RenderingAttributes#setIgnoreVertexColors + * @see ColorInterpolator + * + * @since Java 3D 1.3 + */ + public void setColorTarget(int colorTarget) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Material3")); + + if (isLive()) + ((MaterialRetained)this.retained).setColorTarget(colorTarget); + else + ((MaterialRetained)this.retained).initColorTarget(colorTarget); + } + + /** + * Retrieves the current color target for this material. + * + * @return one of: AMBIENT, EMISSIVE, DIFFUSE, SPECULAR, or + * AMBIENT_AND_DIFFUSE. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getColorTarget() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COMPONENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Material4")); + + return ((MaterialRetained)this.retained).getColorTarget(); + } + + /** + * Returns a String representation of this Materials values. + * If the scene graph is live only those values with their + * Capability read bit set will be displayed. + */ + public String toString() { + StringBuffer str=new StringBuffer("Material Object:"); + Color3f color=new Color3f(); + try { + getAmbientColor(color); + str.append("AmbientColor="+color); + } catch (CapabilityNotSetException e) {str.append("AmbientColor=N/A");} + try { + getEmissiveColor(color); + str.append(" EmissiveColor="+color); + } catch (CapabilityNotSetException ex) {str.append(" EmissiveColor=N/A");} + try { + getDiffuseColor(color); + str.append(" DiffuseColor="+color); + } catch (CapabilityNotSetException exc) {str.append(" DiffuseColor=N/A");} + try { + getSpecularColor(color); + str.append(" SpecularColor="+color); + } catch (CapabilityNotSetException exce) {str.append(" SpecularColor=N/A");} + try { + float f=getShininess(); + str.append(" Shininess="+f); + } catch (CapabilityNotSetException excep) {str.append(" Shininess=N/A");} + try { + boolean b=getLightingEnable(); + str.append(" LightingEnable="+b); + } catch (CapabilityNotSetException except) {str.append(" LightingEnable=N/A");} + try { + int i=getColorTarget(); + str.append(" ColorTarget="+i); + } catch (CapabilityNotSetException except) {str.append(" ColorTarget=N/A");} + return new String(str); + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + Material m = new Material(); + m.duplicateNodeComponent(this); + return m; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, + forceDuplicate); + + MaterialRetained mat = (MaterialRetained) + originalNodeComponent.retained; + MaterialRetained rt = (MaterialRetained) retained; + + Color3f c = new Color3f(); + mat.getAmbientColor(c); + + rt.initAmbientColor(c); + mat.getEmissiveColor(c); + rt.initEmissiveColor(c); + mat.getDiffuseColor(c); + rt.initDiffuseColor(c); + mat.getSpecularColor(c); + rt.initSpecularColor(c); + rt.initShininess(mat.getShininess()); + rt.initLightingEnable(mat.getLightingEnable()); + rt.initColorTarget(mat.getColorTarget()); + } + +} diff --git a/src/classes/share/javax/media/j3d/MaterialRetained.java b/src/classes/share/javax/media/j3d/MaterialRetained.java new file mode 100644 index 0000000..2df0e3f --- /dev/null +++ b/src/classes/share/javax/media/j3d/MaterialRetained.java @@ -0,0 +1,555 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; +import java.util.ArrayList; + +/** + * The MaterialRetained object defines the appearance of an object under + * illumination. + */ +class MaterialRetained extends NodeComponentRetained { + // Initialize default values for all class variables + Color3f ambientColor = new Color3f(0.2f, 0.2f, 0.2f); + Color3f emissiveColor = new Color3f(0.0f, 0.0f, 0.0f); + Color3f diffuseColor = new Color3f(1.0f, 1.0f, 1.0f); + Color3f specularColor = new Color3f(1.0f, 1.0f, 1.0f); + float shininess = 64.0f; + int colorTarget = Material.DIFFUSE; + + // Lighting enable switch for this material object + boolean lightingEnable = true; + + // A list of pre-defined bits to indicate which component + // in this Material object changed. + static final int AMBIENT_COLOR_CHANGED = 0x01; + + static final int EMISSIVE_COLOR_CHANGED = 0x02; + + static final int DIFFUSE_COLOR_CHANGED = 0x04; + + static final int SPECULAR_COLOR_CHANGED = 0x08; + + static final int SHININESS_CHANGED = 0x10; + + static final int ENABLE_CHANGED = 0x20; + + static final int COLORTARGET_CHANGED = 0x40; + + /** + * Constructs and initializes a new material object using the specified + * parameters. + * @param ambientColor the material's ambient color + * @param emissiveColor the material's emissive color + * @param diffuseColor the material's diffuse color when illuminated by a + * light + * @param specularColor the material's specular color when illuminated + * to generate a highlight + * @param shininess the material's shininess in the + * range [1.0, 128.0] with 1.0 being not shiny and 128.0 being very shiny + */ + void createMaterial(Color3f aColor, + Color3f eColor, + Color3f dColor, + Color3f sColor, + float shine) + { + ambientColor.set(aColor); + emissiveColor.set(eColor); + diffuseColor.set(dColor); + specularColor.set(sColor); + shininess = shine; + } + + /** Initializes this material's ambient color + * This specifies how much ambient light is reflected by + * the surface. The ambient light color is the product of this + * color and the material diffuseColor. + * @param color the material's ambient color + */ + final void initAmbientColor(Color3f color) { + this.ambientColor.set(color); + } + + /** + * Sets this material's ambient color and sends a message notifying + * the interested structures of the change. + * This specifies how much ambient light is reflected by + * the surface. The ambient light color is the product of this + * color and the material diffuseColor. + * @param color the material's ambient color + */ + final void setAmbientColor(Color3f color) { + initAmbientColor(color); + sendMessage(AMBIENT_COLOR_CHANGED, new Color3f(color)); + } + + /** + * Sets this material's ambient color + * @param r the new ambient color's red component + * @param g the new ambient color's green component + * @param b the new ambient color's blue component + */ + final void initAmbientColor(float r, float g, float b) { + this.ambientColor.set(r, g, b); + } + + + /** + * Sets this material's ambient color and sends a message notifying + * the interested structures of the change. + * @param r the new ambient color's red component + * @param g the new ambient color's green component + * @param b the new ambient color's blue component + */ + final void setAmbientColor(float r, float g, float b) { + initAmbientColor(r, g, b); + sendMessage(AMBIENT_COLOR_CHANGED, new Color3f(r, g, b)); + } + + /** + * Retrieves this material's ambient color. + * @return the material's ambient color + */ + final void getAmbientColor(Color3f color) { + color.set(this.ambientColor); + } + + /** + * Sets this material's emissive color + * This is the color of light, if any, that the material emits. + * @param color the new emissive color + */ + final void initEmissiveColor(Color3f color) { + this.emissiveColor.set(color); + } + + /** + * Sets this material's emissive color and sends a message notifying + * the interested structures of the change. + * This is the color of light, if any, that the material emits. + * @param color the new emissive color + */ + final void setEmissiveColor(Color3f color) { + initEmissiveColor(color); + sendMessage(EMISSIVE_COLOR_CHANGED, new Color3f(color)); + } + + /** + * Sets this material's emissive color. + * This is the color of light, if any, that the material emits. + * @param r the new emissive color's red component + * @param g the new emissive color's green component + * @param b the new emissive color's blue component + */ + final void initEmissiveColor(float r, float g, float b) { + this.emissiveColor.set(r, g, b); + } + + /** + * Sets this material's emissive color and sends a message notifying + * the interested structures of the change. + * This is the color of light, if any, that the material emits. + * @param r the new emissive color's red component + * @param g the new emissive color's green component + * @param b the new emissive color's blue component + */ + final void setEmissiveColor(float r, float g, float b) { + initEmissiveColor(r, g, b); + sendMessage(EMISSIVE_COLOR_CHANGED, new Color3f(r, g, b)); + } + + /** + * Retrieves this material's emissive color and stores it in the + * argument provided. + * @param color the vector that will receive this material's emissive color + */ + final void getEmissiveColor(Color3f color) { + color.set(this.emissiveColor); + } + + /** + * Sets this material's diffuse color. + * This is the color of the material when illuminated by a light source. + * @param color the new diffuse color + */ + final void initDiffuseColor(Color3f color) { + this.diffuseColor.set(color); + } + + /** + * Sets this material's diffuse color and sends a message notifying + * the interested structures of the change. + * This is the color of the material when illuminated by a light source. + * @param color the new diffuse color + */ + final void setDiffuseColor(Color3f color) { + initDiffuseColor(color); + sendMessage(DIFFUSE_COLOR_CHANGED, new Color3f(color)); + } + + /** + * Sets this material's diffuse color. + * @param r the new diffuse color's red component + * @param g the new diffuse color's green component + * @param b the new diffuse color's blue component + */ + final void initDiffuseColor(float r, float g, float b) { + this.diffuseColor.set(r, g, b); + } + + /** + * Sets this material's diffuse color and sends a message notifying + * the interested structures of the change. + * @param r the new diffuse color's red component + * @param g the new diffuse color's green component + * @param b the new diffuse color's blue component + */ + final void setDiffuseColor(float r, float g, float b) { + initDiffuseColor(r, g, b); + sendMessage(DIFFUSE_COLOR_CHANGED, new Color3f(r, g, b)); + } + + /** + * Sets this material's diffuse color plus alpha. + * This is the color of the material when illuminated by a light source. + * @param r the new diffuse color's red component + * @param g the new diffuse color's green component + * @param b the new diffuse color's blue component + * @param a the alpha component used to set transparency + */ + final void initDiffuseColor(float r, float g, float b, float a) { + this.diffuseColor.set(r, g, b); + } + + /** + * Sets this material's diffuse color plus alpha and sends + * a message notifying the interested structures of the change. + * This is the color of the material when illuminated by a light source. + * @param r the new diffuse color's red component + * @param g the new diffuse color's green component + * @param b the new diffuse color's blue component + * @param a the alpha component used to set transparency + */ + final void setDiffuseColor(float r, float g, float b, float a) { + initDiffuseColor(r, g, b); + sendMessage(DIFFUSE_COLOR_CHANGED, new Color3f(r, g, b)); + } + + /** + * Retrieves this material's diffuse color. + * @param color the vector that will receive this material's diffuse color + */ + final void getDiffuseColor(Color3f color) { + color.set(this.diffuseColor); + } + + /** + * Sets this material's specular color. + * This is the specular highlight color of the material. + * @param color the new specular color + */ + final void initSpecularColor(Color3f color) { + this.specularColor.set(color); + } + + /** + * Sets this material's specular color and sends a message notifying + * the interested structures of the change. + * This is the specular highlight color of the material. + * @param color the new specular color + */ + final void setSpecularColor(Color3f color) { + initSpecularColor(color); + sendMessage(SPECULAR_COLOR_CHANGED, new Color3f(color)); + } + + /** + * Sets this material's specular color. + * This is the specular highlight color of the material. + * @param r the new specular color's red component + * @param g the new specular color's green component + * @param b the new specular color's blue component + */ + final void initSpecularColor(float r, float g, float b) { + this.specularColor.set(r, g, b); + } + + + /** + * Sets this material's specular color and sends a message notifying + * the interested structures of the change. + * This is the specular highlight color of the material. + * @param r the new specular color's red component + * @param g the new specular color's green component + * @param b the new specular color's blue component + */ + final void setSpecularColor(float r, float g, float b) { + initSpecularColor(r, g, b); + sendMessage(SPECULAR_COLOR_CHANGED, new Color3f(r, g, b)); + } + + /** + * Retrieves this material's specular color. + * @param color the vector that will receive this material's specular color + */ + final void getSpecularColor(Color3f color) { + color.set(this.specularColor); + } + + /** + * Sets this material's shininess. + * This specifies a material specular exponent, or shininess. + * It takes a floating point number in the range [1.0, 128.0] + * with 1.0 being not shiny and 128.0 being very shiny. + * @param shininess the material's shininess + */ + final void initShininess(float shininess) { + // Clamp shininess value + if (shininess < 1.0f) + this.shininess = 1.0f; + else if (shininess > 128.0f) + this.shininess = 128.0f; + else + this.shininess = shininess; + + } + + /** + * Sets this material's shininess and sends a message notifying + * the interested structures of the change. + * This specifies a material specular exponent, or shininess. + * It takes a floating point number in the range [1.0, 128.0] + * with 1.0 being not shiny and 128.0 being very shiny. + * @param shininess the material's shininess + */ + final void setShininess(float shininess) { + initShininess(shininess); + sendMessage(SHININESS_CHANGED, new Float(this.shininess)); + } + + /** + * Retrieves this material's shininess. + * @return the material's shininess + */ + final float getShininess() { + return this.shininess; + } + + /** + * Enables or disables lighting for this appearance component object. + * @param state true or false to enable or disable lighting + */ + void initLightingEnable(boolean state) { + lightingEnable = state; + } + + /** + * Enables or disables lighting for this appearance component object + * and sends a message notifying + * the interested structures of the change. + * @param state true or false to enable or disable lighting + */ + void setLightingEnable(boolean state) { + initLightingEnable(state); + sendMessage(ENABLE_CHANGED, + (state ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Retrieves the state of the lighting enable flag. + * @return true if lighting is enabled, false if lighting is disabled + */ + boolean getLightingEnable() { + return lightingEnable; + } + + void initColorTarget(int colorTarget) { + this.colorTarget = colorTarget; + } + + final void setColorTarget(int colorTarget) { + initColorTarget(colorTarget); + sendMessage(COLORTARGET_CHANGED, new Integer(colorTarget)); + } + + final int getColorTarget() { + return colorTarget; + } + + synchronized void createMirrorObject() { + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror = this; + } else { + MaterialRetained mirrorMat = new MaterialRetained(); + mirrorMat.set(this); + mirrorMat.source = source; + mirror = mirrorMat; + } + } else { + ((MaterialRetained) mirror).set(this); + } + } + + + /** + * Updates the native context. + */ + native void updateNative(long ctx, + float red, float green, float blue, float alpha, + float ared, float agreen, float ablue, + float ered, float egreen, float eblue, + float dred, float dgreen, float dblue, + float sred, float sgreen, float sblue, + float shininess, int colorTarget, boolean enable); + + /** + * Updates the native context. + */ + void updateNative(long ctx, + float red, float green, float blue, float alpha, + boolean enableLighting) { + updateNative(ctx, red, green, blue, alpha, + ambientColor.x, ambientColor.y, ambientColor.z, + emissiveColor.x, emissiveColor.y, emissiveColor.z, + diffuseColor.x, diffuseColor.y, diffuseColor.z, + specularColor.x, specularColor.y, specularColor.z, + shininess, colorTarget, enableLighting); + } + + + /** + * Creates a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + MaterialRetained mirrorMaterial = (MaterialRetained)mirror; + mirrorMaterial.set(this); + } + + /** + * Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + MaterialRetained mirrorMaterial = (MaterialRetained)mirror; + if ((component & AMBIENT_COLOR_CHANGED) != 0) { + mirrorMaterial.ambientColor = (Color3f)value; + } + else if ((component & EMISSIVE_COLOR_CHANGED) != 0) { + mirrorMaterial.emissiveColor = (Color3f)value; + } + else if ((component & DIFFUSE_COLOR_CHANGED) != 0) { + mirrorMaterial.diffuseColor = (Color3f)value; + } + else if ((component & SPECULAR_COLOR_CHANGED) != 0) { + mirrorMaterial.specularColor = (Color3f)value; + } + else if ((component & SHININESS_CHANGED) != 0) { + mirrorMaterial.shininess = ((Float)value).floatValue(); + } + else if ((component & ENABLE_CHANGED) != 0) { + mirrorMaterial.lightingEnable = ((Boolean)value).booleanValue(); + } + else if ((component & COLORTARGET_CHANGED) != 0) { + mirrorMaterial.colorTarget = ((Integer)value).intValue(); + } + + } + + + boolean equivalent(MaterialRetained m) { + return ((m != null) && + lightingEnable == m.lightingEnable && + diffuseColor.equals(m.diffuseColor) && + emissiveColor.equals(m.emissiveColor) && + specularColor.equals(m.specularColor) && + ambientColor.equals(m.ambientColor) && + colorTarget == m.colorTarget && + shininess == m.shininess); + } + + + // This functions clones the retained side only and is used + // internally + protected Object clone() { + MaterialRetained mr = (MaterialRetained)super.clone(); + // color can't share the same reference + mr.ambientColor = new Color3f(ambientColor); + mr.emissiveColor = new Color3f(emissiveColor); + mr.diffuseColor = new Color3f(diffuseColor); + mr.specularColor = new Color3f(specularColor); + // other attributes are copy by clone() automatically + return mr; + } + + protected void set(MaterialRetained mat) { + super.set(mat); + + // duplicate any referenced data + ambientColor.set(mat.ambientColor); + emissiveColor.set(mat.emissiveColor); + diffuseColor.set(mat.diffuseColor); + specularColor.set(mat.specularColor); + shininess = mat.shininess; + lightingEnable = mat.lightingEnable; + colorTarget = mat.colorTarget; + } + + + final void sendMessage(int attrMask, Object attr) { + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.MATERIAL_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + int size = univList.size(); + for(int i=0; i<size; i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.MATERIAL_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + VirtualUniverse.mc.processMessage(createMessage); + } + + } + + void handleFrequencyChange(int bit) { + if (bit == Material.ALLOW_COMPONENT_WRITE) { + setFrequencyChangeMask(Material.ALLOW_COMPONENT_WRITE, 0x1); + } + } +} + diff --git a/src/classes/share/javax/media/j3d/MediaContainer.java b/src/classes/share/javax/media/j3d/MediaContainer.java new file mode 100644 index 0000000..fcba207 --- /dev/null +++ b/src/classes/share/javax/media/j3d/MediaContainer.java @@ -0,0 +1,320 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.net.URL; +import java.io.InputStream; + +/** + * The MediaContainer object defines all sound data: cached state flag, and + * associated sound media. Currently this references the sound media in + * one of three forms: URL String, URL object, or InputStream object. + * In future releases media data will include references to Java Media + * Player objects. + * Only one type of sound media data specified using + * <code>setURLString</code>, <code>setURLObject</code>, + * or <code>setInputStream</code> may be + * non-null (or they may all be null). An attempt to set more + * than one of these attributes to a non-null reference will + * result in an exception being thrown. If all sound media data + * references are null, there is no sound associated with this + * MediaContainer and Sound nodes referencing this object cannot + * be played. + */ +public class MediaContainer extends NodeComponent { + /** + * For MediaContainer component objects, specifies that this object + * allows the reading of its cached flag. + */ + public static final int + ALLOW_CACHE_READ = CapabilityBits.MEDIA_CONTAINER_ALLOW_CACHE_READ; + + /** + * For MediaContainer component objects, specifies that this object + * allows the writing of its cached flag. + */ + public static final int + ALLOW_CACHE_WRITE = CapabilityBits.MEDIA_CONTAINER_ALLOW_CACHE_WRITE; + + /** + * For MediaContainer component objects, specifies that this object + * allows the reading of it's sound data. + */ + public static final int + ALLOW_URL_READ = CapabilityBits.MEDIA_CONTAINER_ALLOW_URL_READ; + + /** + * For MediaContainer component objects, specifies that this object + * allows the writing of it's URL path. + */ + public static final int + ALLOW_URL_WRITE = CapabilityBits.MEDIA_CONTAINER_ALLOW_URL_WRITE; + + /** + * Constructs a MediaContainer object with default parameters. + * The default values are as follows: + * <ul> + * URL String data : null<br> + * URL object data : null<br> + * InputStream data : null<br> + * cache enable : true<br> + * </ul> + */ + public MediaContainer() { + // Just use default values + } + + /** + * Constructs and initializes a MediaContainer object using specified + * parameters. + * @param path string of URL path containing sound data + * @exception SoundException if the URL is not valid or cannot be opened + */ + public MediaContainer(String path) { + ((MediaContainerRetained)this.retained).setURLString(path); + } + + /** + * Constructs and initializes a MediaContainer object using specified + * parameters. + * @param url URL path containing sound data + * @exception SoundException if the URL is not valid or cannot be opened + */ + public MediaContainer(URL url) { + ((MediaContainerRetained)this.retained).setURLObject(url); + } + + /** + * Constructs and initializes a MediaContainer object using specified + * parameters. + * @param stream input stream containing sound data + * + * @since Java 3D 1.2 + */ + public MediaContainer(InputStream stream) { + ((MediaContainerRetained)this.retained).setInputStream(stream); + } + + /** + * Creates the retained mode MediaContainerRetained object that this + * component object will point to. + */ + void createRetained() { + this.retained = new MediaContainerRetained(); + this.retained.setSource(this); + } + + /** + * Set Cache Enable state flag. + * Allows the writing of sound data explicitly into the MediaContainer + * rather than just referencing a JavaMedia container. + * @param flag boolean denoting if sound data is cached in this instance + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setCacheEnable(boolean flag) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CACHE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer1")); + + ((MediaContainerRetained)this.retained).setCacheEnable(flag); + } + + /** + * Retrieve Cache Enable state flag. + * @return flag denoting is sound data is non-cached or cached + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getCacheEnable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CACHE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer2")); + + return ((MediaContainerRetained)this.retained).getCacheEnable(); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setURLString</code> + */ + public void setURL(String path) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_URL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer3")); + } + + ((MediaContainerRetained)this.retained).setURLString(path); + } + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>setURLObject</code> + */ + public void setURL(URL url) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_URL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer3")); + ((MediaContainerRetained)this.retained).setURLObject(url); + } + + /** + * Set URL String. + * @param path string of URL containing sound data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception SoundException if the URL is not valid or cannot be opened + * @exception IllegalArgumentException if the specified sound data is + * non-null and any other sound data reference is also non-null. + * @since Java 3D 1.2 + */ + public void setURLString(String path) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_URL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer3")); + } + ((MediaContainerRetained)this.retained).setURLString(path); + } + + /** + * Set URL Object. + * @param url URL object containing sound data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception SoundException if the URL is not valid or cannot be opened + * @exception IllegalArgumentException if the specified sound data is + * non-null and any other sound data reference is also non-null. + * @since Java 3D 1.2 + */ + public void setURLObject(URL url) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_URL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer3")); + ((MediaContainerRetained)this.retained).setURLObject(url); + } + + /** + * Set Input Stream. + * @param stream input stream object containing sound data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception SoundException if InputStream is bad + * @exception IllegalArgumentException if the specified sound data is + * non-null and any other sound data reference is also non-null. + * @since Java 3D 1.2 + */ + public void setInputStream(InputStream stream) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_URL_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer3")); + ((MediaContainerRetained)this.retained).setInputStream(stream); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>getURLString</code> + */ + public String getURL() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_URL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer4")); + return ((MediaContainerRetained)this.retained).getURLString(); + } + + /** + * Retrieve URL String. + * @return string of URL containing sound data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.2 + */ + public String getURLString() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_URL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer4")); + return ((MediaContainerRetained)this.retained).getURLString(); + } + + /** + * Retrieve URL Object. + * @return URL containing sound data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.2 + */ + public URL getURLObject() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_URL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer4")); + return ((MediaContainerRetained)this.retained).getURLObject(); + } + + /** + * Retrieve Input Stream. + * @return reference to input stream containing sound data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.2 + */ + public InputStream getInputStream() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_URL_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("MediaContainer4")); + return ((MediaContainerRetained)this.retained).getInputStream(); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced with + * <code>cloneNodeComponent(boolean forceDuplicate)</code> + */ + public NodeComponent cloneNodeComponent() { + MediaContainer mc = new MediaContainer(); + mc.duplicateNodeComponent(this); + return mc; + } + + + /** + * Copies all MediaContainer information from + * <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>cloneNodeComponent</code> method and <code>duplicateNodeComponent</code> + * method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNodeComponent the original node component to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node component's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + MediaContainerRetained mc = (MediaContainerRetained) + originalNodeComponent.retained; + MediaContainerRetained rt = (MediaContainerRetained) retained; + rt.setCacheEnable(mc.getCacheEnable()); + rt.setURLString(mc.getURLString(), false); + rt.setURLObject(mc.getURLObject(), false); + rt.setInputStream(mc.getInputStream(), false); + } +} diff --git a/src/classes/share/javax/media/j3d/MediaContainerRetained.java b/src/classes/share/javax/media/j3d/MediaContainerRetained.java new file mode 100644 index 0000000..f63ae95 --- /dev/null +++ b/src/classes/share/javax/media/j3d/MediaContainerRetained.java @@ -0,0 +1,196 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.net.URL; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.io.File; +import java.security.*; +import java.io.InputStream; + +/** + * The MediaContainerRetained object defines all rendering state that can + * be set as a component object of a retained Soundscape node. + */ +class MediaContainerRetained extends NodeComponentRetained { + /** + * Gain Scale Factor applied to source with this attribute + */ + boolean cached = true; + + /** + * URL string that references the sound data + */ + URL url = null; + String urlString = null; + InputStream inputStream = null; + + + /** + * Set Cached flag + * @param state flag denoting sound data is cached by app within node + */ + void setCacheEnable(boolean state) { + this.cached = state; + // changing this AFTER sound data attached to node is ignored + // notifyUsers(); + } + + /** + * Retrieve Attrribute Gain (amplitude) + * @return gain amplitude scale factor + */ + boolean getCacheEnable() { + return this.cached; + } + + /** + * Set URL object that references the sound data + * @param url URL object that references the sound data + */ + void setURLObject(URL url) { + setURLObject(url, true); + } + + /** + * Set URL object that references the sound data + * @param url URL object that references the sound data + * @param forceLoad ensures that message about change is sent to scheduler + */ + void setURLObject(URL url, boolean forceLoad) { + // can NOT set URL object field unless the other related fields are null + if (url != null) { + if (urlString != null || inputStream != null) + throw new IllegalArgumentException(J3dI18N.getString("MediaContainer5")); + // Test if url object is valid by openning it + try { + InputStream stream; + stream = url.openStream(); + stream.close(); + } + catch (Exception e) { + throw new SoundException(javax.media.j3d.J3dI18N.getString("MediaContainer0")); + } + } + this.url = url; + // notifyUsers(); + // avoid re-loading SAME MediaContainer when duplicateAttrib calls + if (forceLoad) + dispatchMessage(); + } + + /** + * Set URL path that references the sound data + * @param path string of URL that references the sound data + */ + void setURLString(String path) { + setURLString(path, true); + } + + /** + * Set URL path that references the sound data + * @param path string of URL that references the sound data + * @param forceLoad ensures that message about change is sent to scheduler + */ + void setURLString(String path, boolean forceLoad) { + // can NOT set string field unless the other related fields are null + if (path != null) { + if (this.url != null || inputStream != null) + throw new IllegalArgumentException(J3dI18N.getString("MediaContainer5")); + // Test if path string is valid URL by trying to generate a URL + // and then openning it + try { + URL url = new URL(path); + InputStream stream; + stream = url.openStream(); + stream.close(); + } + catch (Exception e) { + throw new SoundException(javax.media.j3d.J3dI18N.getString("MediaContainer0")); + } + } + this.urlString = path; + // notifyUsers(); + // avoid re-loading SAME MediaContainer when duplicateAttrib calls + if (forceLoad) + dispatchMessage(); + } + + /** + * Set input stream reference to sound data + * @param stream InputStream that references the sound data + * @param forceLoad ensures that message about change is sent to scheduler + */ + void setInputStream(InputStream stream) { + setInputStream(stream, true); + } + + /** + * Set input stream reference to sound data + * @param stream InputStream that references the sound data + */ + void setInputStream(InputStream stream, boolean forceLoad) { + // %%% TODO AudioDevice not intellegent enough to process InputStreams yet + // can NOT set stream field unless the other related fields are null + if (stream != null) { + if (url != null || urlString != null) + throw new IllegalArgumentException(J3dI18N.getString("MediaContainer5")); + } + this.inputStream = stream; + // notifyUsers(); + // avoid re-loading SAME MediaContainer when duplicateAttrib calls + if (forceLoad) + dispatchMessage(); + } + + /** + * Retrieve URL String + * @return URL string that references the sound data + */ + String getURLString() { + return this.urlString; + } + + /** + * Retrieve URL objects + * @return URL object that references the sound data + */ + URL getURLObject() { + return this.url; + } + + /** + * Retrieve InputData + * @return InputString that references the sound data + */ + InputStream getInputStream() { + return this.inputStream; + } + + /** + * Dispatch a message about a media container change + */ + void dispatchMessage() { + // Send message including a integer argumentD + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.SOUND_SCHEDULER; + createMessage.type = J3dMessage.MEDIA_CONTAINER_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(SoundRetained.SOUND_DATA_DIRTY_BIT); + createMessage.args[2]= new Integer(users.size()); + createMessage.args[3] = users; + VirtualUniverse.mc.processMessage(createMessage); + } +} diff --git a/src/classes/share/javax/media/j3d/MemoryFreeList.java b/src/classes/share/javax/media/j3d/MemoryFreeList.java new file mode 100644 index 0000000..7b8abd0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/MemoryFreeList.java @@ -0,0 +1,264 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +/** + * Class for storing various free lists. This class must be + * synchronized because different threads may try to access the lists. + */ +class MemoryFreeList { + + // never go smaller than the initial capacity + ArrayList elementData = null; + int size = 0; + int currBlockSize = 10; + Object[] currBlock = null; + int currBlockIndex = 0; + int spaceUsed = 0; + int numBlocks = 0; + int capacity = 0; + int minBlockSize = 0; + boolean justShrunk = false; + int initcap = 10; + + // the minimum size since the last shrink + int minSize = 0; + + Class c = null; + + MemoryFreeList(String className) { + this(className, 10); + } + + MemoryFreeList(String className, int initialCapacity) { + if (initialCapacity < 0) { + throw new IllegalArgumentException ("Illegal Capacity: " + + initialCapacity); + } + + try { + c = Class.forName(className); + } + catch (Exception e) { + System.out.println(e); + } + + initcap = initialCapacity; + currBlockSize = initialCapacity; + minBlockSize = currBlockSize; + elementData = new ArrayList(); + // add the first block of memory to the arraylist + currBlock = new Object[currBlockSize]; + elementData.add(currBlock); + numBlocks++; + capacity += currBlockSize; + } + + /* + MemoryFreeList(String className, Collection collection) { + try { + c = Class.forName(className); + } + catch (Exception e) { +// System.out.println(e); + } + + size = collection.size(); + initcap = size; + currBlockSize = size; + minBlockSize = currBlockSize; + elementData = new ArrayList(); + currBlock = new Object[currBlockSize]; + collection.toArray(currBlock); + elementData.add(currBlock); + numBlocks++; + capacity += currBlockSize; + spaceUsed = size; + } + */ + + synchronized int size() { + return size; + } + + + synchronized boolean add(Object o) { + if (justShrunk) { + // empty some space out in the current block instead of + // adding this message + if ((currBlockSize/2) < spaceUsed) { + size -= (spaceUsed - (currBlockSize/2)); + spaceUsed = (currBlockSize/2); + Arrays.fill(currBlock, spaceUsed, currBlockSize-1, null); + } + justShrunk = false; + return false; + } + else { + ensureCapacity(size+1); + + // check to see if the whole block is used and if so, reset the + // current block +// System.out.println("spaceUsed = " + spaceUsed + " currBlockSize = " + +// currBlockSize + " currBlockIndex = " + +// currBlockIndex + " currBlock = " + currBlock); + if ((currBlockIndex == -1) || (spaceUsed >= currBlockSize)) { + currBlockIndex++; + currBlock = (Object[])elementData.get(currBlockIndex); + currBlockSize = currBlock.length; + spaceUsed = 0; + } + int index = spaceUsed++; + currBlock[index] = o; + size++; + + return true; + } + } + + protected synchronized Object removeLastElement() { +// System.out.println("removeLastElement: size = " + size); + int index = --spaceUsed; +// System.out.println("index = " + index); + Object elm = currBlock[index]; + currBlock[index] = null; + size--; + + // see if this block is empty now, and if it is set the previous + // block to the current block + if (spaceUsed == 0) { + currBlockIndex--; + if (currBlockIndex < 0) { + currBlock = null; + currBlockSize = 0; + } + else { + currBlock = (Object[])elementData.get(currBlockIndex); + currBlockSize = currBlock.length; + } + spaceUsed = currBlockSize; + } + + return elm; + } + + + synchronized void shrink() { +// System.out.println("shrink size = " + size + " minSize = " + +// minSize); + if ((minSize > minBlockSize) && (numBlocks > 1)) { + justShrunk = true; + +// System.out.println("removing a block"); +// Runtime r = Runtime.getRuntime(); +// r.gc(); +// System.out.println("numBlocks = " + numBlocks + " size = " + size); +// System.out.println("free memory before shrink: " + r.freeMemory()); + + // remove the last block + Object[] block = (Object[])elementData.remove(numBlocks-1); + numBlocks--; + capacity -= block.length; + + // we only need to do this if the block removed was the current + // block. otherwise we just removed a null block. + if (numBlocks == currBlockIndex) { + size -= spaceUsed; + // set the current block to the last one + currBlockIndex = numBlocks-1; + currBlock = (Object[])elementData.get(currBlockIndex); + currBlockSize = currBlock.length; + + spaceUsed = currBlockSize; + + } + +// r.gc(); +// System.out.println("free memory after shrink: " + r.freeMemory()); +// System.out.println("numBlocks = " + numBlocks + " size = " + size); + } + else { + justShrunk = false; + } + minSize = size; + } + + synchronized void ensureCapacity(int minCapacity) { +// System.out.println("ensureCapacity: size = " + size + " capacity: " + +// elementData.length); +// System.out.println("minCapacity = " + minCapacity + " capacity = " +// + capacity); + + if (minCapacity > capacity) { +// System.out.println("adding a block: numBlocks = " + numBlocks); + int lastBlockSize = + ((Object[])elementData.get(numBlocks-1)).length; + int prevBlockSize = 0; + if (numBlocks > 1) { + prevBlockSize = + ((Object[])elementData.get(numBlocks-2)).length; + } + currBlockSize = lastBlockSize + prevBlockSize; + currBlock = new Object[currBlockSize]; + elementData.add(currBlock); + numBlocks++; + currBlockIndex++; + capacity += currBlockSize; + // there is nothing used in this block yet + spaceUsed = 0; + } + } + + synchronized void rangeCheck(int index) { + if (index >= size || index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + + ", Size: " + size); + } + } + + public synchronized void clear() { +// System.out.println("clear"); + elementData.clear(); + + // put an empty block in + currBlockSize = initcap; + minBlockSize = currBlockSize; + currBlock = new Object[currBlockSize]; + elementData.add(currBlock); + numBlocks = 1; + capacity = currBlockSize; + spaceUsed = 0; + size = 0; + currBlockIndex = 0; + justShrunk = false; + } + + synchronized Object getObject() { + if (size > 0) { + return removeLastElement(); + } + else { + try { + return c.newInstance(); + } + catch (Exception e) { + System.out.println(e); + return null; + } + } + } + +} + diff --git a/src/classes/share/javax/media/j3d/ModelClip.java b/src/classes/share/javax/media/j3d/ModelClip.java new file mode 100644 index 0000000..7dd0a40 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ModelClip.java @@ -0,0 +1,702 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Enumeration; + +/** + * The ModelClip leaf node defines a set of 6 arbitrary clipping + * planes in the virtual universe. The planes are specified in the + * local coordinate system of this node, and may be individually + * enabled or disabled. This node also specifies a region of + * influence in which this set of planes is active. + *<p> + * A ModelClip node also contains a list of Group nodes that specifies the + * hierarchical scope of this ModelClip. If the scope list is empty, then + * the ModelClip node has universe scope: all nodes within the region of + * influence are affected by this ModelClip node. If the scope list is + * non-empty, then only those Leaf nodes under the Group nodes in the + * scope list are affected by this ModelClip node (subject to the + * influencing bounds). + * <p> + * If the regions of influence of multiple ModelClip nodes overlap, the + * Java 3D system will choose a single set of model clip planes for those + * objects that lie in the intersection. This is done in an + * implementation-dependent manner, but in general, the ModelClip node that + * is "closest" to the object is chosen. + * <p> + * The individual planes specify a half-space defined by the equation: + * <ul> + * Ax + By + Cz + D <= 0 + * </ul> + * where A, B, C, D are the parameters that specify the plane. The + * parameters are passed in the x, y, z, and w fields, respectively, + * of a Vector4d object. The intersection of the set of half-spaces + * corresponding to the enabled planes in this ModelClip node defines + * a region in which points are accepted. Points in this acceptance + * region will be rendered (subject to view clipping and other + * attributes). Points that are not in the acceptance region will not + * be rendered. + * + * @since Java 3D 1.2 + */ + +public class ModelClip extends Leaf { + /** + * Specifies that the ModelClip node allows read access to its influencing + * bounds and bounding leaf at runtime. + */ + public static final int ALLOW_INFLUENCING_BOUNDS_READ = + CapabilityBits.MODEL_CLIP_ALLOW_INFLUENCING_BOUNDS_READ; + + /** + * Specifies that the ModelClip node allows write access to its influencing + * bounds and bounding leaf at runtime. + */ + public static final int ALLOW_INFLUENCING_BOUNDS_WRITE = + CapabilityBits.MODEL_CLIP_ALLOW_INFLUENCING_BOUNDS_WRITE; + + /** + * Specifies that the ModelClip node allows read access to its planes + * at runtime. + */ + public static final int ALLOW_PLANE_READ = + CapabilityBits.MODEL_CLIP_ALLOW_PLANE_READ; + + /** + * Specifies that the ModelClip node allows write access to its planes + * at runtime. + */ + public static final int ALLOW_PLANE_WRITE = + CapabilityBits.MODEL_CLIP_ALLOW_PLANE_WRITE; + + /** + * Specifies that the ModelClip node allows read access to its enable + * flags at runtime. + */ + public static final int ALLOW_ENABLE_READ = + CapabilityBits.MODEL_CLIP_ALLOW_ENABLE_READ; + + /** + * Specifies that the ModelClip node allows write access to its enable + * flags at runtime. + */ + public static final int ALLOW_ENABLE_WRITE = + CapabilityBits.MODEL_CLIP_ALLOW_ENABLE_WRITE; + + /** + * Specifies that this ModelClip node allows read access to its scope + * information at runtime. + */ + public static final int ALLOW_SCOPE_READ = + CapabilityBits.MODEL_CLIP_ALLOW_SCOPE_READ; + + /** + * Specifies that this ModelClip node allows write access to its scope + * information at runtime. + */ + public static final int ALLOW_SCOPE_WRITE = + CapabilityBits.MODEL_CLIP_ALLOW_SCOPE_WRITE; + + + /** + * Constructs a ModelClip node with default parameters. The default + * values are as follows: + * <ul> + * planes[0] : x <= 1 (1,0,0,-1)<br> + * planes[1] : -x <= 1 (-1,0,0,-1)<br> + * planes[2] : y <= 1 (0,1,0,-1)<br> + * planes[3] : -y <= 1 (0,-1,0,-1)<br> + * planes[4] : z <= 1 (0,0,1,-1)<br> + * planes[5] : -z <= 1 (0,0,-1,-1)<br> + * enables : all planes enabled<br> + * scope : empty (universe scope)<br> + * influencing bounds : null<br> + * influencing bounding leaf : null<br> + * </ul> + */ + public ModelClip() { + // Just use the defaults + } + + + /** + * Constructs a ModelClip node using the specified planes. The individual + * planes are copied into this node. All planes are enabled. + * @param planes an array of 6 model clipping planes + */ + public ModelClip(Vector4d[] planes) { + ((ModelClipRetained)this.retained).initPlanes(planes); + } + + + /** + * Constructs a ModelClip node using the specified planes and enable + * flags. The individual + * planes and enable flags are copied into this node. + * @param planes an array of 6 model clipping planes + * @param enables an array of 6 enable flags + */ + public ModelClip(Vector4d[] planes, boolean[] enables) { + ((ModelClipRetained)this.retained).initPlanes(planes); + ((ModelClipRetained)this.retained).initEnables(enables); + } + + + /** + * Set the ModelClip node's influencing region to the specified bounds. + * This is used when the influencing bounding leaf is set to null. + * @param region the bounds that contains the new influencing + * region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInfluencingBounds(Bounds region) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_INFLUENCING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip0")); + + if (isLive()) + ((ModelClipRetained)this.retained).setInfluencingBounds(region); + else + ((ModelClipRetained)this.retained).initInfluencingBounds(region); + } + + + /** + * Retrieves the ModelClip node's influencing bounds. + * @return this node's influencing bounds information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getInfluencingBounds() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_INFLUENCING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip1")); + + return ((ModelClipRetained)this.retained).getInfluencingBounds(); + } + + + /** + * Set the ModelClip node's influencing region to the specified + * bounding leaf. + * When set to a value other than null, this overrides the influencing + * bounds object. + * @param region the bounding leaf node used to specify the + * new influencing region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInfluencingBoundingLeaf(BoundingLeaf region) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_INFLUENCING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip13")); + + if (isLive()) + ((ModelClipRetained)this.retained).setInfluencingBoundingLeaf(region); + else + ((ModelClipRetained)this.retained).initInfluencingBoundingLeaf(region); + } + + + /** + * Retrieves the ModelClip node's influencing bounding leaf. + * @return this node's influencing bounding leaf information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BoundingLeaf getInfluencingBoundingLeaf() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_INFLUENCING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip14")); + + return ((ModelClipRetained)this.retained).getInfluencingBoundingLeaf(); + } + + + /** + * Replaces the node at the specified index in this ModelClip node's + * list of scopes with the specified Group node. + * By default, ModelClip nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be stored at the specified index. + * @param index the index of the Group node to be replaced. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void setScope(Group scope, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip7")); + + if (isLive()) + ((ModelClipRetained)this.retained).setScope(scope, index); + else + ((ModelClipRetained)this.retained).initScope(scope, index); + } + + + /** + * Retrieves the Group node at the specified index from this ModelClip node's + * list of scopes. + * @param index the index of the Group node to be returned. + * @return the Group node at the specified index. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Group getScope(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip8")); + + return ((ModelClipRetained)this.retained).getScope(index); + } + + + /** + * Inserts the specified Group node into this ModelClip node's + * list of scopes at the specified index. + * By default, ModelClip nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be inserted at the specified index. + * @param index the index at which the Group node is inserted. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void insertScope(Group scope, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip9")); + + if (isLive()) + ((ModelClipRetained)this.retained).insertScope(scope, index); + else + ((ModelClipRetained)this.retained).initInsertScope(scope, index); + } + + + /** + * Removes the node at the specified index from this ModelClip node's + * list of scopes. If this operation causes the list of scopes to + * become empty, then this ModelClip will have universe scope: all nodes + * within the region of influence will be affected by this ModelClip node. + * @param index the index of the Group node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the group node at the + * specified index is part of a compiled scene graph + */ + public void removeScope(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip10")); + + if (isLive()) + ((ModelClipRetained)this.retained).removeScope(index); + else + ((ModelClipRetained)this.retained).initRemoveScope(index); + } + + + /** + * Returns an enumeration of this ModelClip node's list of scopes. + * @return an Enumeration object containing all nodes in this ModelClip node's + * list of scopes. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Enumeration getAllScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip11")); + + return (Enumeration) ((ModelClipRetained)this.retained).getAllScopes(); + } + + + /** + * Appends the specified Group node to this ModelClip node's list of scopes. + * By default, ModelClip nodes are scoped only by their influencing + * bounds. This allows them to be further scoped by a list of + * nodes in the hierarchy. + * @param scope the Group node to be appended. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + */ + public void addScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip12")); + + if (isLive()) + ((ModelClipRetained)this.retained).addScope(scope); + else + ((ModelClipRetained)this.retained).initAddScope(scope); + } + + + /** + * Returns the number of nodes in this ModelClip node's list of scopes. + * If this number is 0, then the list of scopes is empty and this + * ModelClip node has universe scope: all nodes within the region of + * influence are affected by this ModelClip node. + * @return the number of nodes in this ModelClip node's list of scopes. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int numScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip11")); + + return ((ModelClipRetained)this.retained).numScopes(); + } + + + /** + * Retrieves the index of the specified Group node in this + * ModelClip node's list of scopes. + * + * @param scope the Group node to be looked up. + * @return the index of the specified Group node; + * returns -1 if the object is not in the list. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int indexOfScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip8")); + return ((ModelClipRetained)this.retained).indexOfScope(scope); + } + + + /** + * Removes the specified Group node from this ModelClip + * node's list of scopes. If the specified object is not in the + * list, the list is not modified. If this operation causes the + * list of scopes to become empty, then this ModelClip + * will have universe scope: all nodes within the region of + * influence will be affected by this ModelClip node. + * + * @param scope the Group node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if the specified group node + * is part of a compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeScope(Group scope) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip10")); + if (isLive()) + ((ModelClipRetained)this.retained).removeScope(scope); + else + ((ModelClipRetained)this.retained).initRemoveScope(scope); + } + + + /** + * Removes all Group nodes from this ModelClip node's + * list of scopes. The ModelClip node will then have + * universe scope: all nodes within the region of influence will + * be affected by this ModelClip node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if any group node in this + * node's list of scopes is part of a compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeAllScopes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCOPE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip10")); + if (isLive()) + ((ModelClipRetained)this.retained).removeAllScopes(); + else + ((ModelClipRetained)this.retained).initRemoveAllScopes(); + } + + + /** + * Sets the clipping planes of this ModelClip node to the + * specified planes. + * The individual planes are copied into this node. + * @param planes an array of 6 model clipping planes + */ + public void setPlanes(Vector4d[] planes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_PLANE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip2")); + + if (isLive()) + ((ModelClipRetained)this.retained).setPlanes(planes); + else + ((ModelClipRetained)this.retained).initPlanes(planes); + } + + + /** + * Retrieves the clipping planes from this ModelClip node. + * The individual planes are copied into the specified planes, which + * must be allocated by the caller. The array must be large + * enough to hold all of the vectors. + * @param planes an array of 6 vectors that will receive the model + * clipping planes from this node + */ + public void getPlanes(Vector4d[] planes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_PLANE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip3")); + + ((ModelClipRetained)this.retained).getPlanes(planes); + } + + + /** + * Sets the specified clipping plane of this ModelClip node. + * The specified plane is copied into this node. + * @param planeNum specifies which model clipping plane (0-5) is replaced + * @param plane new model clipping plane + */ + public void setPlane(int planeNum, Vector4d plane) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_PLANE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip2")); + + if (isLive()) + ((ModelClipRetained)this.retained).setPlane(planeNum, plane); + else + ((ModelClipRetained)this.retained).initPlane(planeNum, plane); + + } + + + /** + * Retrieves the specified clipping plane from this ModelClip node. + * The plane is copied into the specified plane, which + * must be allocated by the caller. + * @param planeNum specifies which model clipping plane (0-5) is retrieved + * @param plane a vector that will receive the specified model + * clipping plane from this node + */ + public void getPlane(int planeNum, Vector4d plane) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_PLANE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip3")); + + ((ModelClipRetained)this.retained).getPlane(planeNum, plane); + } + + + /** + * Sets the per-plane enable flags of this ModelClip node to the + * specified values. + * @param enables an array of 6 enable flags + */ + public void setEnables(boolean[] enables) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ENABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip4")); + + if (isLive()) + ((ModelClipRetained)this.retained).setEnables(enables); + else + ((ModelClipRetained)this.retained).initEnables(enables); + } + + + /** + * Retrieves the per-plane enable flags from this ModelClip node. + * The enable flags are copied into the specified array. + * The array must be large enough to hold all of the enables. + * @param enables an array of 6 booleans that will receive the + * enable flags from this node + */ + public void getEnables(boolean[] enables) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ENABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip5")); + + ((ModelClipRetained)this.retained).getEnables(enables); + } + + + /** + * Sets the specified enable flag of this ModelClip node. + * @param planeNum specifies which enable flag (0-5) is set + * @param enable new enable flag + */ + public void setEnable(int planeNum, boolean enable) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ENABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip4")); + + if (isLive()) + ((ModelClipRetained)this.retained).setEnable(planeNum, enable); + else + ((ModelClipRetained)this.retained).initEnable(planeNum, enable); + } + + + /** + * Retrieves the specified enable flag from this ModelClip node. + * @param planeNum specifies which enable flag (0-5) is retrieved + * @return the specified enable flag + */ + public boolean getEnable(int planeNum) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ENABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ModelClip5")); + + return ((ModelClipRetained)this.retained).getEnable(planeNum); + } + + /** + * Creates the retained mode ModelClipRetained object that + * this ModelClip node will point to. + */ + void createRetained() { + this.retained = new ModelClipRetained(); + this.retained.setSource(this); + } + + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + ModelClip c = new ModelClip(); + c.duplicateNode(this, forceDuplicate); + return c; + } + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + ModelClipRetained rt = (ModelClipRetained) retained; + BoundingLeaf bl = rt.getInfluencingBoundingLeaf(); + + // check for influencingBoundingLeaf + if (bl != null) { + Object o = referenceTable.getNewObjectReference( bl); + rt.initInfluencingBoundingLeaf((BoundingLeaf) o); + } + + int num = rt.numScopes(); + for (int i=0; i < num; i++) { + rt.initScope((Group) referenceTable. + getNewObjectReference(rt.getScope(i)), i); + } + } + + + /** + * Copies all Clip information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + ModelClipRetained attr = (ModelClipRetained) + originalNode.retained; + ModelClipRetained rt = (ModelClipRetained) retained; + + Vector4d plane = new Vector4d(); + + for (int i=5; i >=0; i--) { + attr.getPlane(i, plane); + rt.initPlane(i, plane); + rt.initEnable(i, attr.getEnable(i)); + } + rt.initInfluencingBounds(attr.getInfluencingBounds()); + + Enumeration elm = attr.getAllScopes(); + while (elm.hasMoreElements()) { + // this reference will set correctly in updateNodeReferences() callback + rt.initAddScope((Group) elm.nextElement()); + } + + // correct value will set in updateNodeReferences + rt.initInfluencingBoundingLeaf(attr.getInfluencingBoundingLeaf()); + } +} diff --git a/src/classes/share/javax/media/j3d/ModelClipRetained.java b/src/classes/share/javax/media/j3d/ModelClipRetained.java new file mode 100644 index 0000000..0e6c571 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ModelClipRetained.java @@ -0,0 +1,1055 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; +import java.util.Vector; +import java.util.ArrayList; +import javax.vecmath.*; + +/** + * The ModelClip retained object. + */ +class ModelClipRetained extends LeafRetained { + + // Statics used when something in the fog changes + static final int PLANE_CHANGED = 0x0001; + static final int PLANES_CHANGED = 0x0002; + static final int ENABLE_CHANGED = 0x0004; + static final int ENABLES_CHANGED = 0x0008; + static final int BOUNDS_CHANGED = 0x0010; + static final int BOUNDINGLEAF_CHANGED = 0x0020; + static final int SCOPE_CHANGED = 0x0040; + static final int INIT_MIRROR = 0x0080; + static final int CLEAR_MIRROR = 0x0100; + static final int LAST_DEFINED_BIT = 0x0100; + + /** + * The clip planes and the enable bits + */ + Vector4d[] planes = new Vector4d[6]; + boolean[] enables = new boolean[6]; + + Vector4d[] xformPlanes = new Vector4d[6]; + + // enableFlag is true if one of the enables is true + // only used by mirror object + boolean enableFlag = false; + + /** + * The Boundary object defining the model clip's region of influencing + */ + Bounds regionOfInfluence = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * The transformed value of the influencingRegion. + */ + Bounds region = null; + + /** + * Vector of GroupRetained nodes that scopes this model clip. + */ + Vector scopes = new Vector(); + + //Boolean to indicate if this object is scoped (only used for mirror objects + boolean isScoped = false; + + // The object that contains the dynamic HashKey - a string type object + // Used in scoping + HashKey tempKey = new HashKey(250); + + // This is true when this model clip is referenced in an immediate mode context + boolean inImmCtx = false; + + // The mirror copy of this modelClip + ModelClipRetained mirrorModelClip = null; + + // A reference to the scene graph model clip + ModelClipRetained sgModelClip = null; + + // Target threads to be notified when model clip changes + final static int targetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + + /** + * The EnvironmentSets which reference this model clip. + * Note that multiple RenderBin update thread may access + * this shared environmentSets simultaneously. + * So we use UnorderList which sync. all the operations. + */ + UnorderList environmentSets = new UnorderList(1, EnvironmentSet.class); + + // Is true, if the mirror clip is viewScoped + boolean isViewScoped = false; + + /** + * Constructs and initializes model clip planes + */ + ModelClipRetained() { + + // planes contains the negate default values + planes[0] = new Vector4d( 1.0, 0.0, 0.0,-1.0); + planes[1] = new Vector4d(-1.0, 0.0, 0.0,-1.0); + planes[2] = new Vector4d( 0.0, 1.0, 0.0,-1.0); + planes[3] = new Vector4d( 0.0,-1.0, 0.0,-1.0); + planes[4] = new Vector4d( 0.0, 0.0, 1.0,-1.0); + planes[5] = new Vector4d( 0.0, 0.0, -1.0,-1.0); + + for (int i = 0; i < 6; i++) + xformPlanes[i] = new Vector4d(planes[i]); + + enables[0] = enables[1] = enables[2] = enables[3] = + enables[4] = enables[5] = true; + } + + /** + * Initializes planes before the object is live + */ + void initPlanes(Vector4d[] planes) { + + if (staticTransform != null) { + Transform3D xform = staticTransform.getNormalTransform(); + for (int i = 0; i < 6; i++) { + this.planes[i].set(planes[i]); + xform.transform(this.planes[i], this.xformPlanes[i]); + } + } else { + for (int i = 0; i < 6; i++) { + this.planes[i].set(planes[i]); + this.xformPlanes[i].set(this.planes[i]); + } + } + } + + /** + * Sets the clip planes and send a message + */ + void setPlanes(Vector4d[] planes) { + Vector4d[] pl = new Vector4d[6]; + initPlanes(planes); + + for (int i = 0; i < 6; i++) { + pl[i] = new Vector4d(this.xformPlanes[i]); + } + + sendMessage(PLANES_CHANGED, pl, null); + } + + /** + * Initializes planes before the object is live + */ + void initPlane(int planeNum, Vector4d plane) { + if (planeNum < 0 || planeNum > 5) + throw new IllegalArgumentException(J3dI18N.getString("ModelClip6")); + + if (staticTransform != null) { + Transform3D xform = staticTransform.getNormalTransform(); + this.planes[planeNum].set(plane); + xform.transform(this.planes[planeNum], this.xformPlanes[planeNum]); + } else { + this.planes[planeNum].set(plane); + this.xformPlanes[planeNum].set(plane); + } + } + + /** + * Sets the clip planes and send a message + */ + void setPlane(int planeNum, Vector4d plane) { + initPlane(planeNum, plane); + sendMessage(PLANE_CHANGED, + new Integer(planeNum), + new Vector4d(this.xformPlanes[planeNum])); + } + + /** + * Gets planes + */ + void getPlanes(Vector4d[] planes){ + + for (int i = 0; i < 6; i++) { + planes[i].set(this.planes[i]); + } + } + + /** + * Gets the specified clipping plane + */ + void getPlane(int planeNum, Vector4d plane) { + if (planeNum < 0 || planeNum > 5) + throw new IllegalArgumentException(J3dI18N.getString("ModelClip6")); + plane.set(this.planes[planeNum]); + } + + /** + * Initializes planes before the object is live + */ + void initEnables(boolean[] enables) { + this.enables[0] = enables[0]; + this.enables[1] = enables[1]; + this.enables[2] = enables[2]; + this.enables[3] = enables[3]; + this.enables[4] = enables[4]; + this.enables[5] = enables[5]; + } + + /** + * Sets the clip planes and send a message + */ + void setEnables(boolean[] enables) { + Boolean[] en = new Boolean[6]; + + initEnables(enables); + en[0] = (enables[0] ? Boolean.TRUE: Boolean.FALSE); + en[1] = (enables[1] ? Boolean.TRUE: Boolean.FALSE); + en[2] = (enables[2] ? Boolean.TRUE: Boolean.FALSE); + en[3] = (enables[3] ? Boolean.TRUE: Boolean.FALSE); + en[4] = (enables[4] ? Boolean.TRUE: Boolean.FALSE); + en[5] = (enables[5] ? Boolean.TRUE: Boolean.FALSE); + + sendMessage(ENABLES_CHANGED, en, null); + } + + /** + * Initializes planes before the object is live + */ + void initEnable(int planeNum, boolean enable) { + if (planeNum < 0 || planeNum > 5) + throw new IllegalArgumentException(J3dI18N.getString("ModelClip6")); + this.enables[planeNum] = enable; + } + + /** + * Sets the clip planes and send a message + */ + void setEnable(int planeNum, boolean enable) { + initEnable(planeNum, enable); + sendMessage(ENABLE_CHANGED, + new Integer(planeNum), + (enable ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Gets enables + */ + void getEnables(boolean[] enables) { + enables[0] = this.enables[0]; + enables[1] = this.enables[1]; + enables[2] = this.enables[2]; + enables[3] = this.enables[3]; + enables[4] = this.enables[4]; + enables[5] = this.enables[5]; + } + + /** + * Gets the specified enable + */ + boolean getEnable(int planeNum) { + if (planeNum < 0 || planeNum > 5) + throw new IllegalArgumentException(J3dI18N.getString("ModelClip6")); + return (this.enables[planeNum]); + } + + /** + * Set the Model Clip's region of influencing + */ + void initInfluencingBounds(Bounds region) { + if (region != null) { + this.regionOfInfluence = (Bounds) region.clone(); + if (staticTransform != null) { + regionOfInfluence.transform(staticTransform.transform); + } + } else { + this.regionOfInfluence = null; + } + } + + /** + * Set the Model Clip's region of influencing and send message + */ + void setInfluencingBounds(Bounds region) { + initInfluencingBounds(region); + sendMessage(BOUNDS_CHANGED, + (region != null ? (Bounds) region.clone(): null), + null); + } + + /** + * Get the Model Clip's region of influencing. + */ + Bounds getInfluencingBounds() { + Bounds b = null; + + if (regionOfInfluence != null) { + b = (Bounds) regionOfInfluence.clone(); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + b.transform(invTransform); + } + } + return b; + } + + /** + * Set the Model Clip's region of influencing to the specified Leaf node. + */ + void initInfluencingBoundingLeaf(BoundingLeaf region) { + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + } else { + boundingLeaf = null; + } + } + + /** + * Set the Model Clip's region of influencing to the specified Leaf node. + */ + void setInfluencingBoundingLeaf(BoundingLeaf region) { + if (boundingLeaf != null) + boundingLeaf.mirrorBoundingLeaf.removeUser(mirrorModelClip); + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + boundingLeaf.mirrorBoundingLeaf.addUser(mirrorModelClip); + } else { + boundingLeaf = null; + } + + sendMessage(BOUNDINGLEAF_CHANGED, + (boundingLeaf != null ? + boundingLeaf.mirrorBoundingLeaf : null), + null); + } + + + /** + * Get the Model Clip's region of influencing. + */ + BoundingLeaf getInfluencingBoundingLeaf() { + return (boundingLeaf != null ? + (BoundingLeaf)boundingLeaf.source : null); + } + + /** + * Replaces the specified scope with the scope provided. + * @param scope the new scope + * @param index which scope to replace + */ + void initScope(Group scope, int index) { + scopes.setElementAt((GroupRetained)(scope.retained), index); + } + + /** + * Replaces the specified scope with the scope provided. + * @param scope the new scope + * @param index which scope to replace + */ + void setScope(Group scope, int index) { + + ArrayList addScopeList = new ArrayList(); + ArrayList removeScopeList = new ArrayList(); + GroupRetained group; + Object[] scopeInfo = new Object[3]; + + group = (GroupRetained) scopes.get(index); + tempKey.reset(); + group.removeAllNodesForScopedModelClip(mirrorModelClip, removeScopeList, tempKey); + + group = (GroupRetained)scope.retained; + initScope(scope, index); + tempKey.reset(); + // If its a group, then add the scope to the group, if + // its a shape, then keep a list to be added during + // updateMirrorObject + group.addAllNodesForScopedModelClip(mirrorModelClip,addScopeList, tempKey); + scopeInfo[0] = addScopeList; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE:Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo, null); + } + + /** + * Inserts the specified scope at specified index + * @param scope the new scope + * @param index position to insert new scope at + */ + void initInsertScope(Node scope, int index) { + GroupRetained group = (GroupRetained)scope.retained; + group.setMclipScope(); + scopes.insertElementAt((GroupRetained)(scope.retained), index); + } + + /** + * Inserts the specified scope at specified index and sends + * a message + * @param scope the new scope + * @param index position to insert new scope at + */ + void insertScope(Node scope, int index) { + Object[] scopeInfo = new Object[3]; + ArrayList addScopeList = new ArrayList(); + + initInsertScope(scope, index); + GroupRetained group = (GroupRetained)scope.retained; + tempKey.reset(); + group.addAllNodesForScopedModelClip(mirrorModelClip,addScopeList, tempKey); + scopeInfo[0] = addScopeList; + scopeInfo[1] = null; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo, null); + } + + + void initRemoveScope(int index) { + GroupRetained group = (GroupRetained)scopes.elementAt(index); + group.removeMclipScope(); + scopes.removeElementAt(index); + + } + + void removeScope(int index) { + + Object[] scopeInfo = new Object[3]; + ArrayList removeScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scopes.elementAt(index); + + initRemoveScope(index); + tempKey.reset(); + group.removeAllNodesForScopedModelClip(mirrorModelClip, removeScopeList, tempKey); + + scopeInfo[0] = null; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo, null); + } + + /** + * Removes the specified Group node from this ModelClip's list of + * scopes if the specified node is not found in the list of scoped + * nodes, method returns quietly. + * + * @param Group node to be removed + */ + void removeScope(Group node) { + int ind = indexOfScope(node); + if(ind >= 0) + removeScope(ind); + } + + void initRemoveScope(Group node) { + int ind = indexOfScope(node); + if(ind >= 0) + initRemoveScope(ind); + } + + /** + * Removes all the Group nodes from the ModelClip's scope + * list. The ModelClip reverts to universal scope. + */ + void removeAllScopes() { + Object[] scopeInfo = new Object[3]; + ArrayList removeScopeList = new ArrayList(); + int n = scopes.size(); + for(int index = n-1; index >= 0; index--) { + GroupRetained group = (GroupRetained)scopes.elementAt(index); + initRemoveScope(index); + tempKey.reset(); + group.removeAllNodesForScopedModelClip(mirrorModelClip, removeScopeList, tempKey); + } + + scopeInfo[0] = null; + scopeInfo[1] = removeScopeList; + scopeInfo[2] = (Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo, null); + } + + + void initRemoveAllScopes() { + int n = scopes.size(); + for(int i = n-1; i >= 0; i--) { + initRemoveScope(i); + } + } + + /** + * Returns the scope specified by the index. + * @param index which scope to return + * @return the scoperen at location index + */ + Group getScope(int index) { + return (Group)(((GroupRetained)(scopes.elementAt(index))).source); + } + + /** + * Returns an enumeration object of the scoperen. + * @return an enumeration object of the scoperen + */ + Enumeration getAllScopes() { + Enumeration elm = scopes.elements(); + Vector v = new Vector(scopes.size()); + while (elm.hasMoreElements()) { + v.add( ((GroupRetained) elm.nextElement()).source); + } + return v.elements(); + } + + /** + * Appends the specified scope to this node's list of scopes before + * the fog is alive + * @param scope the scope to add to this node's list of scopes + */ + void initAddScope(Group scope) { + GroupRetained group = (GroupRetained)scope.retained; + scopes.addElement((GroupRetained)(scope.retained)); + group.setMclipScope(); + } + + /** + * Appends the specified scope to this node's list of scopes. + * @param scope the scope to add to this node's list of scopes + */ + void addScope(Group scope) { + + Object[] scopeInfo = new Object[3]; + ArrayList addScopeList = new ArrayList(); + GroupRetained group = (GroupRetained)scope.retained; + + initAddScope(scope); + tempKey.reset(); + group.addAllNodesForScopedModelClip(mirrorModelClip,addScopeList, tempKey); + scopeInfo[0] = addScopeList; + scopeInfo[1] = null; + scopeInfo[2] = (scopes.size() > 0 ? Boolean.TRUE: Boolean.FALSE); + sendMessage(SCOPE_CHANGED, scopeInfo, null); + } + + /** + * Returns a count of this nodes' scopes. + * @return the number of scopes descendant from this node + */ + int numScopes() { + return scopes.size(); + } + + /** + * Returns the index of the specified Group node within the ModelClip's list of scoped + * Group nodes + * @param Group node whose index is desired + * @return index of this node + */ + int indexOfScope(Group node) { + if(node != null) + return scopes.indexOf((GroupRetained)node.retained); + else + return scopes.indexOf(null); + } + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return (inImmCtx); + } + + + /** + * This method and its native counterpart update the native context + * model clip planes. + */ + native void update(long ctx, int planeNum, boolean enableFlag, + double A, double B, double C, double D); + + void update(Canvas3D cv, int enableMask) { + cv.setModelViewMatrix(cv.ctx, + cv.vworldToEc.mat, + getLastLocalToVworld()); + update(cv.ctx, enableMask, getLastLocalToVworld()); + } + + void update(long ctx, int enableMask, Transform3D trans) { + if (!VirtualUniverse.mc.isD3D()) { + for (int i = 0; i < 6; i ++) { + update(ctx, i, ((enableMask & (1 << i)) != 0), + xformPlanes[i].x, xformPlanes[i].y, + xformPlanes[i].z, xformPlanes[i].w); + } + return; + } + + // For D3D we need to transform the plane equations from local to + // world coordinate. + Transform3D invtrans = new Transform3D(trans); + + // can't call getNormalTransform() since it will cache + // normalTransform and may return previous result next time. + invtrans.invert(); + invtrans.transpose(); + + for (int i=0; i < 6; i++) { + if ((enableMask & (1 << i)) != 0) { + + Vector4d vec = new Vector4d(xformPlanes[i].x, xformPlanes[i].y, + xformPlanes[i].z, xformPlanes[i].w); + vec.normalize(); + invtrans.transform(vec); + update(ctx, i, true, vec.x, vec.y, vec.z, vec.w); + + } else { + update(ctx, i, false, 0, 0, 0, 0); + } + } + } + + void initMirrorObject(Object[] args) { + Shape3DRetained shape; + Object[] scopeInfo = (Object[]) args[2]; + Boolean scoped = (Boolean)scopeInfo[0]; + ArrayList shapeList = (ArrayList)scopeInfo[1]; + BoundingLeafRetained bl=(BoundingLeafRetained)((Object[])args[4])[0]; + Bounds bnds = (Bounds)((Object[])args[4])[1]; + + for (int i = 0; i < shapeList.size(); i++) { + shape = ((GeometryAtom)shapeList.get(i)).source; + shape.addModelClip(mirrorModelClip); + } + mirrorModelClip.isScoped = scoped.booleanValue(); + + if (bl != null) { + mirrorModelClip.boundingLeaf = bl.mirrorBoundingLeaf; + mirrorModelClip.region = boundingLeaf.transformedRegion; + } else { + mirrorModelClip.boundingLeaf = null; + mirrorModelClip.region = null; + } + + if (bnds != null) { + mirrorModelClip.regionOfInfluence = bnds; + if (mirrorModelClip.region == null) { + mirrorModelClip.region = (Bounds)regionOfInfluence.clone(); + mirrorModelClip.region.transform(regionOfInfluence, getLastLocalToVworld()); + } + } + else { + mirrorModelClip.regionOfInfluence = null; + } + boolean[] ens = (boolean[])((Object[])args[4])[2]; + + for (int i = 0; i < ens.length; i++) { + mirrorModelClip.enables[i] = ens[i]; + } + mirrorModelClip.enableFlag = mirrorModelClip.enables[0] | + mirrorModelClip.enables[1] | + mirrorModelClip.enables[2] | + mirrorModelClip.enables[3] | + mirrorModelClip.enables[4] | + mirrorModelClip.enables[5] ; + + } + + + + void updateMirrorObject(Object[] objs) { + int component = ((Integer)objs[1]).intValue(); + if ((component & PLANES_CHANGED) != 0) { + Vector4d[] pl = ((Vector4d[]) objs[2]); + + for (int i = 0; i < 6; i++) { + mirrorModelClip.xformPlanes[i].set(pl[i]); + } + } + else if ((component & PLANE_CHANGED) != 0) { + int planeNum = ((Integer)objs[2]).intValue(); + + mirrorModelClip.xformPlanes[planeNum].set((Vector4d)objs[3]); + } + else if ((component & INIT_MIRROR) != 0) { + Vector4d[] pl = (Vector4d[]) objs[3]; + for (int i = 0; i < 6; i++) { + mirrorModelClip.xformPlanes[i].set(pl[i]); + } + } + } + + // The update Object function. + void updateImmediateMirrorObject(Object[] objs) { + int component = ((Integer)objs[1]).intValue(); + Transform3D trans; + + + if ((component & BOUNDINGLEAF_CHANGED) != 0) { + mirrorModelClip.boundingLeaf = (BoundingLeafRetained)objs[2]; + if (objs[2] != null) { + mirrorModelClip.region = + (Bounds)mirrorModelClip.boundingLeaf.transformedRegion; + } + else { + if (mirrorModelClip.regionOfInfluence != null) { + mirrorModelClip.region = + ((Bounds)mirrorModelClip.regionOfInfluence).copy(mirrorModelClip.region); + mirrorModelClip.region.transform(mirrorModelClip.regionOfInfluence, + getCurrentLocalToVworld()); + } + else { + mirrorModelClip.region = null; + } + + } + } + + if ((component & BOUNDS_CHANGED) != 0) { + mirrorModelClip.regionOfInfluence = (Bounds) objs[2]; + if (mirrorModelClip.boundingLeaf == null) { + if (objs[2] != null) { + mirrorModelClip.region = + ((Bounds)mirrorModelClip.regionOfInfluence).copy(mirrorModelClip.region); + + mirrorModelClip.region.transform(mirrorModelClip.regionOfInfluence, + getCurrentLocalToVworld()); + } + else { + mirrorModelClip.region = null; + } + } + } + + if ((component & SCOPE_CHANGED) != 0) { + Object[] scopeList = (Object[])objs[2]; + ArrayList addList = (ArrayList)scopeList[0]; + ArrayList removeList = (ArrayList)scopeList[1]; + boolean isScoped = ((Boolean)scopeList[2]).booleanValue(); + + if (addList != null) { + mirrorModelClip.isScoped = isScoped; + for (int i = 0; i < addList.size(); i++) { + Shape3DRetained obj = ((GeometryAtom)addList.get(i)).source; + obj.addModelClip(mirrorModelClip); + } + } + + if (removeList != null) { + mirrorModelClip.isScoped = isScoped; + for (int i = 0; i < removeList.size(); i++) { + Shape3DRetained obj = ((GeometryAtom)removeList.get(i)).source; + obj.removeModelClip(mirrorModelClip); + } + } + } + + if ((component & ENABLES_CHANGED) != 0) { + Boolean[] en = ((Boolean[]) objs[2]); + + mirrorModelClip.enables[0] = en[0].booleanValue(); + mirrorModelClip.enables[1] = en[1].booleanValue(); + mirrorModelClip.enables[2] = en[2].booleanValue(); + mirrorModelClip.enables[3] = en[3].booleanValue(); + mirrorModelClip.enables[4] = en[4].booleanValue(); + mirrorModelClip.enables[5] = en[5].booleanValue(); + mirrorModelClip.enableFlag = mirrorModelClip.enables[0] | + mirrorModelClip.enables[1] | + mirrorModelClip.enables[2] | + mirrorModelClip.enables[3] | + mirrorModelClip.enables[4] | + mirrorModelClip.enables[5] ; + } else if ((component & ENABLE_CHANGED) != 0) { + int planeNum = ((Integer)objs[2]).intValue(); + + mirrorModelClip.enables[planeNum] = ((Boolean)objs[3]).booleanValue(); + mirrorModelClip.enableFlag = mirrorModelClip.enables[0] | + mirrorModelClip.enables[1] | + mirrorModelClip.enables[2] | + mirrorModelClip.enables[3] | + mirrorModelClip.enables[4] | + mirrorModelClip.enables[5] ; + } + } + + + /** Note: This routine will only be called on + * the mirror object - will update the object's + * cached region and transformed region + */ + void updateBoundingLeaf() { + if (boundingLeaf != null && boundingLeaf.switchState.currentSwitchOn) { + region = boundingLeaf.transformedRegion; + } else { + if (regionOfInfluence != null) { + region = regionOfInfluence.copy(region); + region.transform(regionOfInfluence, getCurrentLocalToVworld()); + } else { + region = null; + } + } + } + + + void setLive(SetLiveState s) { + GroupRetained group; + + super.doSetLive(s); + + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("ModelClipRetained1")); + } + + // Create the mirror object + + + if (mirrorModelClip == null) { + mirrorModelClip = (ModelClipRetained)this.clone(); + mirrorModelClip.boundingLeaf = null; + mirrorModelClip.sgModelClip = this; + } + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(mirrorModelClip); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(mirrorModelClip); + } + + // If bounding leaf is not null, add the mirror object as a user + // so that any changes to the bounding leaf will be received + if (boundingLeaf != null) { + boundingLeaf.mirrorBoundingLeaf.addUser(mirrorModelClip); + } + // process switch leaf + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(mirrorModelClip, Targets.ENV_TARGETS); + } + mirrorModelClip.switchState = (SwitchState)s.switchStates.get(0); + + // add this model clip to the transform target + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(mirrorModelClip, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + super.markAsLive(); + + + // Initialize the mirror object, this needs to be done, when + // renderBin is not accessing any of the fields + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.MODELCLIP_CHANGED; + createMessage.args[0] = this; + // a snapshot of all attributes that needs to be initialized + // in the mirror object + createMessage.args[1]= new Integer(INIT_MIRROR); + ArrayList addScopeList = new ArrayList(); + for (int i = 0; i < scopes.size(); i++) { + group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.addAllNodesForScopedModelClip(mirrorModelClip, addScopeList, tempKey); + } + Object[] scopeInfo = new Object[2]; + scopeInfo[0] = ((scopes.size() > 0) ? Boolean.TRUE:Boolean.FALSE); + scopeInfo[1] = addScopeList; + createMessage.args[2] = scopeInfo; + createMessage.args[3] = xformPlanes.clone(); + + Object[] obj = new Object[3]; + obj[0] = boundingLeaf; + obj[1] = (regionOfInfluence != null?regionOfInfluence.clone():null); + obj[2] = enables.clone(); + createMessage.args[4] = obj; + VirtualUniverse.mc.processMessage(createMessage); + + + } + + + /** + * This clearLive routine first calls the superclass's method, then + * it removes itself to the list of model clip + */ + void clearLive(SetLiveState s) { + + super.clearLive(s); + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER; + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(mirrorModelClip, Targets.ENV_TARGETS); + } + // Remove this mirror light as users of the bounding leaf + if (mirrorModelClip.boundingLeaf != null) + mirrorModelClip.boundingLeaf.removeUser(mirrorModelClip); + + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(mirrorModelClip); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(mirrorModelClip); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(mirrorModelClip, Targets.ENV_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + + + if (scopes.size() > 0) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT; + createMessage.universe = universe; + createMessage.type = J3dMessage.MODELCLIP_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(CLEAR_MIRROR); + ArrayList removeScopeList = new ArrayList(); + for (int i = 0; i < scopes.size(); i++) { + GroupRetained group = (GroupRetained)scopes.get(i); + tempKey.reset(); + group.removeAllNodesForScopedModelClip(mirrorModelClip, removeScopeList, tempKey); + } + createMessage.args[2] = removeScopeList; + VirtualUniverse.mc.processMessage(createMessage); + } + } + + // This is called on the parent object + void clearMirrorObject(Object[] args) { + Shape3DRetained shape; + ArrayList shapeList = (ArrayList)args[2]; + ArrayList removeScopeList = new ArrayList(); + + for (int i = 0; i < shapeList.size(); i++) { + shape = ((GeometryAtom)shapeList.get(i)).source; + shape.removeModelClip(mirrorModelClip); + } + + mirrorModelClip.isScoped = false; + + } + + + // Clone the retained side only, internal use only + protected Object clone() { + ModelClipRetained mc = (ModelClipRetained)super.clone(); + + mc.planes = new Vector4d[6]; + for (int i = 0; i < 6; i++) { + mc.planes[i] = new Vector4d(this.planes[i]); + mc.xformPlanes[i] = new Vector4d(this.xformPlanes[i]); + } + + mc.enables = new boolean[6]; + getEnables(mc.enables); + + // Derive the enables flag + mc.enableFlag = (mc.enables[0] | + mc.enables[1] | + mc.enables[2] | + mc.enables[3] | + mc.enables[4] | + mc.enables[5] ); + + mc.inImmCtx = false; + mc.region = null; + mc.sgModelClip = null; + mc.mirrorModelClip = null; + mc.environmentSets = new UnorderList(1, EnvironmentSet.class); + + if (regionOfInfluence != null) { + mc.regionOfInfluence = (Bounds) regionOfInfluence.clone(); + } + + return mc; + } + + + // Called on mirror object + void updateImmediateTransformChange() { + // If bounding leaf is null, tranform the bounds object + if (boundingLeaf == null) { + if (regionOfInfluence != null) { + region = regionOfInfluence.copy(region); + region.transform(regionOfInfluence, + sgModelClip.getCurrentLocalToVworld()); + } + + } + } + + + void printPlane(int index, String string) + { + System.err.println(string + " : < " + planes[index].toString() + + " > " + enables[index]); + } + + void printPlanes(String string, Vector4d[] planes) + { + System.err.println(string); + printPlane(0, "[0]"); + printPlane(1, "[1]"); + printPlane(2, "[2]"); + printPlane(3, "[3]"); + printPlane(4, "[4]"); + printPlane(5, "[5]"); + } + + + void printEnables(String string, boolean[] enables) + { + System.err.println(string); + System.err.println("[0] : < " + enables[0] + " >"); + System.err.println("[1] : < " + enables[1] + " >"); + System.err.println("[2] : < " + enables[2] + " >"); + System.err.println("[3] : < " + enables[3] + " >"); + System.err.println("[4] : < " + enables[4] + " >"); + System.err.println("[5] : < " + enables[5] + " >"); + } + + final void sendMessage(int attrMask, Object attr1, Object attr2) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.MODELCLIP_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr1; + createMessage.args[3] = attr2; + VirtualUniverse.mc.processMessage(createMessage); + } + + void mergeTransform(TransformGroupRetained staticTransform) { + super.mergeTransform(staticTransform); + + if (regionOfInfluence != null) { + regionOfInfluence.transform(staticTransform.transform); + } + + Transform3D xform = staticTransform.getNormalTransform(); + for (int i = 0; i < 6; i++) { + xform.transform(planes[i], xformPlanes[i]); + } + } + void getMirrorObjects(ArrayList leafList, HashKey key) { + leafList.add(mirrorModelClip); + } +} diff --git a/src/classes/share/javax/media/j3d/Morph.java b/src/classes/share/javax/media/j3d/Morph.java new file mode 100644 index 0000000..4661c85 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Morph.java @@ -0,0 +1,654 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Hashtable; +import javax.vecmath.*; + +/** + * The Morph leaf node permits an application to morph between + * multiple GeometryArrays. The Morph node contains a single + * Appearance node, an array of GeometryArray objects, and an array of + * corresponding weights. The Morph node combines these GeometryArrays + * into an aggregate shape based on each GeometryArray's corresponding + * weight. Typically, Behavior nodes will modify the weights to + * achieve various morphing effects. + * + * <p> + * The following restrictions apply to each GeometryArray object + * in the specified array of GeometryArray objects: + * + * <ul> + * <li> + * All <i>N</i> geometry arrays must be of the + * same type (that is, the same subclass of GeometryArray). + * </li> + * + * <p> + * <li> + * The vertexFormat, texCoordSetCount, and validVertexCount must be + * the same for all <i>N</i> geometry arrays. + * </li> + * + * <p> + * <li> + * The texCoordSetMap array must be identical (element-by-element) for + * all <i>N</i> geometry arrays. + * </li> + * + * <p> + * <li> + * For IndexedGeometryArray objects, the validIndexCount must be the same + * for all <i>N</i> geometry arrays. + * </li> + * + * <p> + * <li> + * For GeometryStripArray objects, the stripVertexCounts array must + * be identical (element-by-element) for all <i>N</i> geometry arrays. + * </li> + * + * <p> + * <li> + * For IndexedGeometryStripArray objects, the stripIndexCounts array must + * be identical (element-by-element) for all <i>N</i> geometry arrays. + * </li> + * + * <p> + * <li> + * For indexed geometry by-reference, the array lengths of each + * enabled vertex component (coord, color, normal, texcoord) + * must be the same for all <i>N</i> geometry arrays. + * </li> + * </ul> + * + * <p> + * For IndexedGeometryArray objects, the vertex arrays are morphed + * <i>before</i> the indexes are applied. Only the indexes in the + * first geometry array (geometry[0]) are used when rendering the + * geometry. + */ + +public class Morph extends Leaf { + + /** + * Specifies that the node allows read access to its geometry information. + */ + public static final int + ALLOW_GEOMETRY_ARRAY_READ = CapabilityBits.MORPH_ALLOW_GEOMETRY_ARRAY_READ; + + /** + * Specifies that the node allows write access to its geometry information. + */ + public static final int + ALLOW_GEOMETRY_ARRAY_WRITE = CapabilityBits.MORPH_ALLOW_GEOMETRY_ARRAY_WRITE; + + /** + * Specifies that the node allows read access to its appearance information. + */ + public static final int + ALLOW_APPEARANCE_READ = CapabilityBits.MORPH_ALLOW_APPEARANCE_READ; + + /** + * Specifies that the node allows write access to its appearance information. + */ + public static final int + ALLOW_APPEARANCE_WRITE = CapabilityBits.MORPH_ALLOW_APPEARANCE_WRITE; + + /** + * Specifies that the node allows read access to its morph + * weight vector. + */ + public static final int + ALLOW_WEIGHTS_READ = CapabilityBits.MORPH_ALLOW_WEIGHTS_READ; + + /** + * Specifies that the node allows write access to its morph + * weight vector. + */ + public static final int + ALLOW_WEIGHTS_WRITE = CapabilityBits.MORPH_ALLOW_WEIGHTS_WRITE; + + /** + * Specifies that the node allows reading its collision Bounds. + */ + public static final int + ALLOW_COLLISION_BOUNDS_READ = CapabilityBits.MORPH_ALLOW_COLLISION_BOUNDS_READ; + + /** + * Specifies the node allows writing its collision Bounds. + */ + public static final int + ALLOW_COLLISION_BOUNDS_WRITE = CapabilityBits.MORPH_ALLOW_COLLISION_BOUNDS_WRITE; + + /** + * Specifies that this node allows reading its appearance override + * enable flag. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_APPEARANCE_OVERRIDE_READ = + CapabilityBits.MORPH_ALLOW_APPEARANCE_OVERRIDE_READ; + + /** + * Specifies that this node allows writing its appearance override + * enable flag. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_APPEARANCE_OVERRIDE_WRITE = + CapabilityBits.MORPH_ALLOW_APPEARANCE_OVERRIDE_WRITE; + + + // non public default constructor + Morph() { + } + + /** + * Constructs and initializes a Morph node with the specified array + * of GeometryArray objects. Default values are used for all other + * parameters as follows: + * <ul> + * appearance : null<br> + * weights : [1, 0, 0, 0, ...]<br> + * collision bounds : null<br> + * appearance override enable : false<br> + * </ul><P> + * A null appearance object specifies that default values are used + * for all appearance attributes. + * + * @param geometryArrays the geometry components of the morph; + * a null or zero-length array of GeometryArray objects is + * permitted, and specifies that no geometry is drawn. In this case, + * the array of weights is initialized to a zero-length array. + * @exception IllegalArgumentException if any of the specified + * geometry array objects differ from each other in any of the + * following ways: + * <ul> + * <li>Type of geometry array object (subclass of GeometryArray)</li> + * <li>vertexFormat</li> + * <li>texCoordSetCount</li> + * <li>texCoordSetMap</li> + * <li>validVertexCount</li> + * <li>validIndexCount, for IndexedGeometryArray objects</li> + * <li>stripVertexCounts array, for GeometryStripArray objects</li> + * <li>stripIndexCounts array, for IndexedGeometryStripArray objects</li> + * <li>the array lengths of each enabled vertex component + * (coord, color, normal, texcoord), + * for indexed geometry by-reference</li> + * </ul> + */ + public Morph(GeometryArray geometryArrays[]) { + ((MorphRetained)retained).setGeometryArrays(geometryArrays); + } + + /** + * Constructs and initializes a Morph node with the specified array + * of GeometryArray objects and the specified appearance object. + * @param geometryArrays the geometry components of the Morph node + * a null or zero-length array of GeometryArray objects is + * permitted, and specifies that no geometry is drawn. In this case, + * the array of weights is initialized to a zero-length array. + * @param appearance the appearance component of the Morph node + * @exception IllegalArgumentException if any of the specified + * geometry array objects differ from each other in any of the + * following ways: + * <ul> + * <li>Type of geometry array object (subclass of GeometryArray)</li> + * <li>vertexFormat</li> + * <li>texCoordSetCount</li> + * <li>texCoordSetMap</li> + * <li>validVertexCount</li> + * <li>validIndexCount, for IndexedGeometryArray objects</li> + * <li>stripVertexCounts array, for GeometryStripArray objects</li> + * <li>stripIndexCounts array, for IndexedGeometryStripArray objects</li> + * <li>the array lengths of each enabled vertex component + * (coord, color, normal, texcoord), + * for indexed geometry by-reference</li> + * </ul> + */ + public Morph(GeometryArray geometryArrays[], Appearance appearance) { + ((MorphRetained)retained).setGeometryArrays(geometryArrays); + ((MorphRetained)this.retained).setAppearance(appearance); + } + + /** + * Creates the retained mode MorphRetained object that this + * Morph object will point to. + */ + void createRetained() { + retained = new MorphRetained(); + retained.setSource(this); + } + + /** + * Sets the collision bounds of a node. + * @param bounds the collision bounding object for a node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setCollisionBounds(Bounds bounds) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLLISION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph0")); + + ((MorphRetained)this.retained).setCollisionBounds(bounds); + } + + /** + * Returns the collision bounding object of this node. + * @return the node's collision bounding object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getCollisionBounds() { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLLISION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph1")); + + return ((MorphRetained)this.retained).getCollisionBounds(); + } + + + /** + * Sets the geometryArrays component of the Morph node. + * + * If the current array of GeometryArrays in this Morph object is + * non-null with a length greater than 0, the specified array of + * GeometryArrays must be the same length as the current array. + * If the current array of GeometryArrays in this Morph object is + * null or has a length of 0, and the specified array of + * GeometryArrays is non-null with a length greater than 0, the + * length of the incoming array defines the number of the geometry + * objects that will be morphed. In this case, the weights array + * is allocated to be of the same length as the geometry array; + * the first element (weights[0]) is initialized to 1.0 and all of + * the other weights are initialized to 0.0. + * + * @param geometryArrays the new geometryArrays component + * for the Morph node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * <p> + * + * @exception IllegalArgumentException if the length of the + * specified array of geometry arrays is not equal to the length + * of this Morph node's current array of geometry arrays (and the + * current array's length is non-zero), or if any of the specified + * geometry array objects differ from each other in any of the + * following ways: + * <ul> + * <li>Type of geometry array object (subclass of GeometryArray)</li> + * <li>vertexFormat</li> + * <li>texCoordSetCount</li> + * <li>texCoordSetMap</li> + * <li>validVertexCount</li> + * <li>validIndexCount, for IndexedGeometryArray objects</li> + * <li>stripVertexCounts array, for GeometryStripArray objects</li> + * <li>stripIndexCounts array, for IndexedGeometryStripArray objects</li> + * <li>the array lengths of each enabled vertex component + * (coord, color, normal, texcoord), + * for indexed geometry by-reference</li> + * </ul> + */ + public void setGeometryArrays(GeometryArray geometryArrays[]) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_GEOMETRY_ARRAY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph2")); + + ((MorphRetained)this.retained).setGeometryArrays(geometryArrays); + } + + /** + * Retrieves the geometryArray component of this Morph node. + * @param index the index of GeometryArray to be returned + * @return the geometryArray component of this Morph node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public GeometryArray getGeometryArray(int index) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_GEOMETRY_ARRAY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph3")); + + return ((MorphRetained)this.retained).getGeometryArray(index); + } + + /** + * Sets the appearance component of this Morph node. A null + * appearance component specifies that default values are used for all + * appearance attributes. + * @param appearance the new appearance component for this Morph node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAppearance(Appearance appearance) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPEARANCE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph4")); + + ((MorphRetained)this.retained).setAppearance(appearance); + } + + /** + * Retrieves the appearance component of this morph node. + * @return the appearance component of this morph node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Appearance getAppearance() { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPEARANCE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph5")); + + return ((MorphRetained)this.retained).getAppearance(); + } + + + /** + * Checks whether the geometry in this morph node intersects with + * the specified pickShape. + * + * @param path the SceneGraphPath to this morph node + * @param pickShape the PickShape to be intersected + * + * @return true if the pick shape intersects this node; false + * otherwise. + * + * @exception IllegalArgumentException if pickShape is a PickPoint. + * Java 3D doesn't have spatial information of the surface. + * Use PickBounds with BoundingSphere and a small radius, instead. + * + * @exception CapabilityNotSetException if the Geometry.ALLOW_INTERSECT + * capability bit is not set in all of the Geometry objects + * referred to by this morph node. + */ + public boolean intersect(SceneGraphPath path, PickShape pickShape) { + return intersect(path, pickShape, null); + } + + + /** + * Checks whether the geometry in this morph node intersects with + * the specified pickRay. + * + * @param path the SceneGraphPath to this morph node + * @param pickRay the PickRay to be intersected + * @param dist the closest distance of the intersection + * + * @return true if the pick shape intersects this node; false + * otherwise. If true, dist contains the closest distance of + * intersection. + * + * @exception CapabilityNotSetException if the Geometry.ALLOW_INTERSECT + * capability bit is not set in all of the Geometry objects + * referred to by this morph node. + */ + public boolean intersect(SceneGraphPath path, + PickRay pickRay, + double[] dist) { + + if (isLiveOrCompiled()) { + checkForAllowIntersect(); + } + + return ((MorphRetained)this.retained).intersect(path, pickRay, dist); + } + + + /** + * Checks whether the geometry in this morph node intersects with + * the specified pickShape. + * + * @param path the SceneGraphPath to this morph node + * @param pickShape the PickShape to be intersected + * @param dist the closest distance of the intersection + * + * @return true if the pick shape intersects this node; false + * otherwise. If true, dist contains the closest distance of + * intersection. + * + * @exception IllegalArgumentException if pickShape is a PickPoint. + * Java 3D doesn't have spatial information of the surface. + * Use PickBounds with BoundingSphere and a small radius, instead. + * + * @exception CapabilityNotSetException if the Geometry.ALLOW_INTERSECT + * capability bit is not set in all of the Geometry objects + * referred to by this morph node. + * + * @since Java 3D 1.3 + */ + public boolean intersect(SceneGraphPath path, + PickShape pickShape, + double[] dist) { + + if (isLiveOrCompiled()) { + checkForAllowIntersect(); + } + + if (pickShape instanceof PickPoint) { + throw new IllegalArgumentException(J3dI18N.getString("Morph10")); + } + + return ((MorphRetained)this.retained).intersect(path, pickShape, dist); + } + + + /** + * Sets this Morph node's morph weight vector. The Morph node "weights" + * the corresponding GeometryArray by the amount specified. + * The weights apply a morph weight vector component that creates + * the desired morphing effect. + * The length + * of the <code>weights</code> parameter must be equal to the length + * of the array with which this Morph node was created, otherwise + * an IllegalArgumentException is thown. + * @param weights the morph weight vector that the morph node will + * use in combining the node's geometryArrays. The morph node will "weight" + * the corresponding GeometryArray by the amount specified. + * N.B.: the sum of the weights should equal 1.0 + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if sum of all 'weights' is + * NOT 1.0 or number of weights is NOT exqual to number of GeometryArrays. + */ + public void setWeights(double weights[]) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_WEIGHTS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph8")); + + ((MorphRetained)this.retained).setWeights(weights); + } + + /** + * Retrieves the Morph node's morph weight vector. + * @return the morph weight vector component of this morph node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public double[] getWeights() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_WEIGHTS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph9")); + + return ((MorphRetained)this.retained).getWeights(); + } + + /** + * Sets a flag that indicates whether this node's appearance can + * be overridden. If the flag is true, this node's + * appearance may be overridden by an AlternateAppearance leaf + * node, regardless of the value of the ALLOW_APPEARANCE_WRITE + * capability bit. + * The default value is false. + * + * @param flag the apperance override enable flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see AlternateAppearance + * + * @since Java 3D 1.2 + */ + public void setAppearanceOverrideEnable(boolean flag) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_APPEARANCE_OVERRIDE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph11")); + + ((MorphRetained)this.retained).setAppearanceOverrideEnable(flag); + } + + /** + * Retrieves the appearanceOverrideEnable flag for this node. + * @return true if the appearance can be overridden; false + * otherwise. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public boolean getAppearanceOverrideEnable() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_APPEARANCE_OVERRIDE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Morph12")); + + return ((MorphRetained)this.retained).getAppearanceOverrideEnable(); + } + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Morph m = new Morph(); + m.duplicateNode(this, forceDuplicate); + return m; + } + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @exception ClassCastException if originalNode is not an instance of + * <code>Morph</code> + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + checkDuplicateNode(originalNode, forceDuplicate); + } + + /** + * Copies all Morph information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + MorphRetained attr = (MorphRetained) originalNode.retained; + MorphRetained rt = (MorphRetained) retained; + + Hashtable hashtable = originalNode.nodeHashtable; + + double weights[] = attr.getWeights(); + + rt.setCollisionBounds(attr.getCollisionBounds()); + rt.setAppearance((Appearance) getNodeComponent( + attr.getAppearance(), + forceDuplicate, + hashtable)); + + GeometryArray ga[] = new GeometryArray[weights.length]; + + for (int i=weights.length-1; i>=0; i--) { + ga[i] = (GeometryArray) getNodeComponent( + attr.getGeometryArray(i), + forceDuplicate, + hashtable); + } + rt.setGeometryArrays(ga); + rt.setWeights(weights); + } + + // Method to check whether all geometries have allow intersect + // capability bit set; it will throw an exception if any don't + // have the bit set. + private void checkForAllowIntersect() { + MorphRetained morphR = ((MorphRetained)this.retained); + for (int i = 0; i < morphR.numGeometryArrays; i++) { + if (!morphR.geometryArrays[i].source. + getCapability(Geometry.ALLOW_INTERSECT)) { + + throw new CapabilityNotSetException(J3dI18N.getString("Morph6")); + } + } + } + +} diff --git a/src/classes/share/javax/media/j3d/MorphRetained.java b/src/classes/share/javax/media/j3d/MorphRetained.java new file mode 100644 index 0000000..ac3c0df --- /dev/null +++ b/src/classes/share/javax/media/j3d/MorphRetained.java @@ -0,0 +1,1894 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; +import java.util.Vector; + +/** + * A morph leaf node consisting of geometery and appearance properties. + */ + +class MorphRetained extends LeafRetained implements GeometryUpdater { + + // These bits should match the Shape3D bits ...(Since the mirrors for + // both are Shape3DRetained + static final int GEOMETRY_CHANGED = 0x00001; + static final int APPEARANCE_CHANGED = 0x00002; + static final int COLLISION_CHANGED = 0x00004; + static final int BOUNDS_CHANGED = 0x00008; + static final int APPEARANCEOVERRIDE_CHANGED = 0x00010; + static final int UPDATE_MORPH = 0x00020; + + private static final double TOLERANCE = 1.0e-4; + + + /** + * The mirror Shape3DRetained nodes for this object. There is one + * mirror for each instance of this Shape3D node. If it is not in + * a SharedGroup, only index 0 is valid. + */ + ArrayList mirrorShape3D = new ArrayList(); + + + // Target threads to be notified when morph changes + final static int targetThreads = (J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_GEOMETRY); + + /** + * The appearance component of the morph node. + */ + AppearanceRetained appearance = null; + + /** + * The Geosets associated with the morph node. + */ + GeometryArrayRetained geometryArrays[]; + + int numGeometryArrays = 0; + + /** + * The weight vector the morph node. + */ + double weights[]; + + /** + * Reference to the BranchGroup path of this mirror shape + * This is used for picking only. + */ + BranchGroupRetained branchGroupPath[]; + + + // cache value for picking in mirror shape. + // True if all the node of the path from this to root are all pickable + boolean isPickable = true; + + + // cache value for collidable in mirror shape. + // True if all the node of the path from this to root are all collidable + boolean isCollidable = true; + + + // closest switch parent + SwitchRetained closestSwitchParent = null; + + // the child index from the closest switch parent + int closestSwitchIndex = -1; + + // Is this Morph visible ? The default is true. + boolean visible = true; + + // geometry Bounds in local coordinate + Bounds bounds = null; + + // geometry Bounds in virtual world coordinate + BoundingBox vwcBounds = new BoundingBox(); + + // collision Bound in local coordinate + Bounds collisionBound = null; + + // collision Bounds in virtual world coordinate + Bounds collisionVwcBound = null; + + + GeometryArray morphedGeometryArray = null; + + // Morph data + float[] Mcoord = null; + float[] Mcolor = null; + float[] Mnormal = null; + // First dimension is the coordSet, second dimenension is the vertex index + // each vertex has 2 or 3floats + float[][]MtexCoord = null; + + // Whether the normal appearance is overrided by the alternate app + boolean appearanceOverrideEnable = false; + + int changedFrequent = 0; + + + MorphRetained() { + this.nodeType = NodeRetained.MORPH; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + /** + * Sets the collision bounds of a node. + * @param bounds the bounding object for the node + */ + void setCollisionBounds(Bounds bounds) { + if (bounds != null) { + collisionBound = (Bounds)bounds.clone(); + } else { + collisionBound = null; + } + if (source.isLive()) { + // Notify Geometry Structure to set mirror shape collision + // bound and check for collision + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.COLLISION_BOUND_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM; + message.universe = universe; + message.args[1] = collisionBound; + VirtualUniverse.mc.processMessage(message); + } + } + + /** + * Sets the geometric bounds of a node. + * @param bounds the bounding object for the node + */ + void setBounds(Bounds bounds) { + super.setBounds(bounds); + if (source.isLive() && !boundsAutoCompute) { + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.REGION_BOUND_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + targetThreads; + message.universe = universe; + message.args[0] = Shape3DRetained.getGeomAtomsArray(mirrorShape3D); + message.args[1] = localBounds; + VirtualUniverse.mc.processMessage(message); + } + } + + /** + * Gets the collision bounds of a node. + * @return the node's bounding object + */ + Bounds getCollisionBounds() { + return (collisionBound == null? null : (Bounds)collisionBound.clone()); + } + + /** + * Sets the geometryArrays component of the Morph node. + * @param geometryArrays the new vector of geometryArrays for the morph node + */ + void setGeometryArrays(GeometryArray geometryArrays[]) { + int i; + + if ((geometryArrays == null || geometryArrays.length == 0) && numGeometryArrays == 0) + return; + + GeometryArrayRetained geo, prevGeo; + + if (numGeometryArrays != 0 && (geometryArrays == null || numGeometryArrays != geometryArrays.length)) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained0")); + + + for (i=1;i < geometryArrays.length;i++) { + if (geometryArrays[i] == null || geometryArrays[i-1] == null) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + geo = (GeometryArrayRetained)geometryArrays[i].retained; + prevGeo = (GeometryArrayRetained)geometryArrays[i-1].retained; + if (prevGeo == null || geo == null) { + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + + } + doErrorCheck(prevGeo, geo); + } + // Check if the first one is in Immediate context + if (geometryArrays[0] != null) { + geo = (GeometryArrayRetained)geometryArrays[0].retained; + } + + if (numGeometryArrays == 0) { + this.geometryArrays = new GeometryArrayRetained[geometryArrays.length]; + numGeometryArrays = geometryArrays.length; + } + + for (i=0;i < numGeometryArrays;i++) { + geo = (GeometryArrayRetained)geometryArrays[i].retained; + if (((Morph)this.source).isLive()) { + if (this.geometryArrays[i] != null) { + this.geometryArrays[i].clearLive(refCount); + this.geometryArrays[i].removeMorphUser(this); + } + if (geo != null) { + geo.setLive(inBackgroundGroup, refCount); + geo.addMorphUser(this); + } + } + + this.geometryArrays[i] = geo; + } + if (this.geometryArrays[0] == null) + return; + + + if (weights == null) { + weights = new double[numGeometryArrays]; + weights[0] = 1.0; + int vFormat = this.geometryArrays[0].vertexFormat; + // default is zero when new array + //for (i=1; i < numGeometryArrays;i++) weights[i] = 0.0; + + int texCoordSetCount = this.geometryArrays[0].getTexCoordSetCount(); + if (this.geometryArrays[0] instanceof IndexedGeometryArrayRetained) { + Mcoord = new float[this.geometryArrays[0].getNumCoordCount()* 3]; + + if ((vFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) + Mcolor = new float[this.geometryArrays[0].getNumColorCount()* 3]; + else if ((vFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) + Mcolor = new float[this.geometryArrays[0].getNumColorCount()* 4]; + + MtexCoord = new float[texCoordSetCount][]; + if ((vFormat & GeometryArray.NORMALS) != 0) + Mnormal = new float[this.geometryArrays[0].getNumNormalCount() *3]; + for (int k = 0; k < texCoordSetCount; k++) { + if ((vFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) + MtexCoord[k] = new float[this.geometryArrays[0].getNumTexCoordCount(k) * 2]; + else if (((vFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0)) + MtexCoord[k] = new float[this.geometryArrays[0].getNumTexCoordCount(k) * 3]; + else if (((vFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0)) + MtexCoord[k] = new float[this.geometryArrays[0].getNumTexCoordCount(k) * 4]; + } + } + else { + Mcoord = new float[this.geometryArrays[0].validVertexCount* 3]; + + if ((vFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) { + Mcolor = new float[this.geometryArrays[0].validVertexCount* 3]; + } else if ((vFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) { + Mcolor = new float[this.geometryArrays[0].validVertexCount* 4]; + } + MtexCoord = new float[texCoordSetCount][]; + if ((vFormat & GeometryArray.NORMALS) != 0) { + Mnormal = new float[this.geometryArrays[0].validVertexCount *3]; + } + for (int k = 0; k < texCoordSetCount; k++) { + if ((vFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) + MtexCoord[k] = new float[this.geometryArrays[0].validVertexCount * 2]; + else if (((vFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0)) + MtexCoord[k] = new float[this.geometryArrays[0].validVertexCount * 3]; + else if (((vFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0)) + MtexCoord[k] = new float[this.geometryArrays[0].validVertexCount * 4]; + } + } + } + + // create a new morphedGeometryArray + initMorphedGeometry(); + + if (source.isLive()) { + + Shape3DRetained shape = (Shape3DRetained)mirrorShape3D.get(0); + + shape.setMorphGeometry(morphedGeometryArray, mirrorShape3D); + + J3dMessage mChangeMessage = null; + mChangeMessage = VirtualUniverse.mc.getMessage(); + mChangeMessage.type = J3dMessage.MORPH_CHANGED; + mChangeMessage.threads = (J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_TRANSFORM); + // If its a indexed geometry array, unindexify in renderBin + if (this.geometryArrays[0] instanceof IndexedGeometryArrayRetained) + mChangeMessage.threads |= J3dThread.UPDATE_RENDERING_ATTRIBUTES; + mChangeMessage.args[0] = this; + mChangeMessage.args[1]= new Integer(GEOMETRY_CHANGED); + // a shadow copy of this ArrayList instance. (The elements themselves are not copied.) + mChangeMessage.args[3] = Shape3DRetained.getGeomAtomsArray(mirrorShape3D); + mChangeMessage.universe = universe; + VirtualUniverse.mc.processMessage(mChangeMessage); + + if (boundsAutoCompute) { + GeometryArrayRetained mga = (GeometryArrayRetained)morphedGeometryArray.retained; + // Compute the bounds once + mga.incrComputeGeoBounds();// This compute the bbox if dirty + mga.decrComputeGeoBounds(); + } + } + + + + } + + /** + * Retrieves the geometryArrays component of this Morph node. + * @param index the index of GeometryArray to be returned + * @return the geometryArray component of this morph node + */ + GeometryArray getGeometryArray(int index) { + return (GeometryArray)this.geometryArrays[index].source; + } + + /** + * Sets the appearance component of this Morph node. + * @param appearance the new apearance component for this morph node + */ + void setAppearance(Appearance newAppearance) { + boolean visibleIsDirty = false; + + if (((Morph)this.source).isLive()) { + + if (appearance != null) { + this.appearance.clearLive(refCount); + for (int i=mirrorShape3D.size()-1; i>=0; i--) { + this.appearance.removeAMirrorUser( + (Shape3DRetained)mirrorShape3D.get(i)); + } + } + + if (newAppearance != null) { + ((AppearanceRetained)newAppearance.retained).setLive(inBackgroundGroup, refCount); + appearance = ((AppearanceRetained)newAppearance.retained); + int size= mirrorShape3D.size(); + for (int i=0; i<size; i++) { + appearance.addAMirrorUser((Shape3DRetained)mirrorShape3D.get(i)); + } + if((appearance.renderingAttributes != null) && + (visible != appearance.renderingAttributes.visible)) { + visible = appearance.renderingAttributes.visible; + visibleIsDirty = true; + } + } + else { + if(visible == false) { + visible = true; + visibleIsDirty = true; + } + } + + // Send a message + int size = 0; + + if (visibleIsDirty) + size = 2; + else + size = 1; + J3dMessage[] createMessage = new J3dMessage[size]; + createMessage[0] = VirtualUniverse.mc.getMessage(); + createMessage[0].threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + createMessage[0].type = J3dMessage.MORPH_CHANGED; + createMessage[0].universe = universe; + createMessage[0].args[0] = this; + createMessage[0].args[1]= new Integer(APPEARANCE_CHANGED); + Shape3DRetained[] s3dArr = new Shape3DRetained[mirrorShape3D.size()]; + mirrorShape3D.toArray(s3dArr); + createMessage[0].args[2] = s3dArr; + Object[] obj = new Object[2]; + if (newAppearance == null) { + obj[0] = null; + } + else { + obj[0] = appearance.mirror; + } + obj[1] = new Integer(changedFrequent); + createMessage[0].args[3] = obj; + createMessage[0].args[4] = Shape3DRetained.getGeomAtomsArray(mirrorShape3D); + if(visibleIsDirty) { + createMessage[1] = VirtualUniverse.mc.getMessage(); + createMessage[1].threads = J3dThread.UPDATE_GEOMETRY; + createMessage[1].type = J3dMessage.SHAPE3D_CHANGED; + createMessage[1].universe = universe; + createMessage[1].args[0] = this; + createMessage[1].args[1]= new Integer(APPEARANCE_CHANGED); + createMessage[1].args[2]= visible?Boolean.TRUE:Boolean.FALSE; + createMessage[1].args[3]= createMessage[0].args[4]; + } + VirtualUniverse.mc.processMessage(createMessage); + } + else { + if (newAppearance == null) { + appearance = null; + } else { + appearance = (AppearanceRetained) newAppearance.retained; + } + } + } + + /** + * Retrieves the morph node's appearance component. + * @return the morph node's appearance + */ + Appearance getAppearance() { + return (appearance == null ? null : + (Appearance) this.appearance.source); + } + + void setAppearanceOverrideEnable(boolean flag) { + if (((Morph)this.source).isLive()) { + + // Send a message + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER;; + createMessage.type = J3dMessage.MORPH_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(APPEARANCEOVERRIDE_CHANGED); + Shape3DRetained[] s3dArr = new Shape3DRetained[mirrorShape3D.size()]; + mirrorShape3D.toArray(s3dArr); + createMessage.args[2] = s3dArr; + Object[] obj = new Object[2]; + if (flag) { + obj[0] = Boolean.TRUE; + } + else { + obj[0] = Boolean.FALSE; + } + obj[1] = new Integer(changedFrequent); + createMessage.args[3] = obj; + createMessage.args[4] = Shape3DRetained.getGeomAtomsArray(mirrorShape3D); + VirtualUniverse.mc.processMessage(createMessage); + } + appearanceOverrideEnable = flag; + } + + boolean getAppearanceOverrideEnable() { + return appearanceOverrideEnable; + } + + + /** + * Check if the geometry component of this shape node under path + * intersects with the pickShape. + * @return true if intersected else false. If return is true, dist + * contains the closest distance of intersection if it is not + * equal to null. + */ + boolean intersect(SceneGraphPath path, PickShape pickShape, + double[] dist) { + + // This method will not do bound intersect check, as it assume caller + // has already done that. ( For performance and code simplification + // reasons. ) + + Transform3D localToVworld = path.getTransform(); + + if (localToVworld == null) { + throw new RuntimeException(J3dI18N.getString("MorphRetained5")); + } + + Transform3D vworldToLocal = VirtualUniverse.mc.getTransform3D(null); + vworldToLocal.invert(localToVworld); + PickShape newPS = pickShape.transform(vworldToLocal); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, vworldToLocal); + + Point3d iPnt = Shape3DRetained.getPoint3d(); + + GeometryRetained geo = (GeometryRetained) (morphedGeometryArray.retained); + + if (geo.mirrorGeometry != null) { + geo = geo.mirrorGeometry; + } + + boolean isIntersect; + if (dist != null) { + isIntersect = geo.intersect(newPS, dist, iPnt); + if (isIntersect) { + // compute the real distance since the dist return + // above distance may scaled (non-uniform) by transform3d + localToVworld.transform(iPnt); + dist[0] = pickShape.distance(iPnt); + } + } else { + isIntersect = geo.intersect(newPS, null, iPnt); + } + Shape3DRetained.freePoint3d(iPnt); + return isIntersect; + } + + /** + * Sets the Morph node's weight vector + * @param wieghts the new vector of weights for the morph node + */ + void setWeights(double weights[]) { + int i; + double sum= 0.0; + + if (weights.length != numGeometryArrays) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained7")); + + for (i=weights.length-1; i>=0; i--) { + sum += weights[i]; + } + + if (Math.abs(sum - 1.0) > TOLERANCE) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained8")); + + // Weights array is ALWAYS malloced in setGeometryArrays method + for (i=numGeometryArrays-1; i>=0; i--) + this.weights[i] = weights[i]; + + + if (source.isLive()) { + ((GeometryArrayRetained)morphedGeometryArray.retained).updateData(this); + J3dMessage mChangeMessage = null; + mChangeMessage = VirtualUniverse.mc.getMessage(); + mChangeMessage.type = J3dMessage.MORPH_CHANGED; + mChangeMessage.threads = (J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_TRANSFORM); + // If its a indexed geometry array, unindexify in renderBin + if (this.geometryArrays[0] instanceof IndexedGeometryArrayRetained) + mChangeMessage.threads |= J3dThread.UPDATE_RENDERING_ATTRIBUTES; + mChangeMessage.args[0] = this; + mChangeMessage.args[1]= new Integer(GEOMETRY_CHANGED); + mChangeMessage.args[3] = Shape3DRetained.getGeomAtomsArray(mirrorShape3D); + mChangeMessage.universe = universe; + VirtualUniverse.mc.processMessage(mChangeMessage); + } + + } + + /** + * Retrieves the Morph node's weight vector + * @return the morph node's weight vector. + */ + double[] getWeights() { + return (double[]) weights.clone(); + } + + /** + * Gets the bounding object of a node. + * @return the node's bounding object + */ + Bounds getBounds() { + if(boundsAutoCompute) { + GeometryArrayRetained mga = + (GeometryArrayRetained)morphedGeometryArray.retained; + if (mga != null) { + synchronized(mga.geoBounds) { + return (Bounds) mga.geoBounds.clone(); + } + } else { + return null; + } + } else { + return super.getBounds(); + } + } + + Bounds getEffectiveBounds() { + if(boundsAutoCompute) { + return getBounds(); + } + else { + return super.getEffectiveBounds(); + } + } + + /** + * ONLY needed for SHAPE, MORPH, and LINK node type. + * Compute the combine bounds of bounds and its localBounds. + */ + void computeCombineBounds(Bounds bounds) { + + if(boundsAutoCompute) { + GeometryArrayRetained mga = + (GeometryArrayRetained)morphedGeometryArray.retained; + if (mga != null) { + synchronized(mga.geoBounds) { + bounds.combine((Bounds) mga.geoBounds); + } + } + } else { + // Should this be lock too ? ( MT safe ? ) + synchronized(localBounds) { + bounds.combine((Bounds) localBounds); + } + } + } + + // If the geometry of a morph changes, make sure that the + // validVertexCount has not changed + void updateMorphedGeometryArray(GeometryArrayRetained geo, boolean coordinatesChanged) { + if (numGeometryArrays > 0) { + // check if not the first geo, then compare with the first geometry + if (geometryArrays[0] != geo) { + doErrorCheck(geo, geometryArrays[0]); + } + else { + // if first geo, compare with the second geo + if (numGeometryArrays > 1) { + doErrorCheck(geo, geometryArrays[1]); + } + + } + } + + + ((GeometryArrayRetained)morphedGeometryArray.retained).updateData(this); + // Compute the bounds once + if (boundsAutoCompute && coordinatesChanged) { + GeometryArrayRetained mga = (GeometryArrayRetained)morphedGeometryArray.retained; + mga.incrComputeGeoBounds(); // This compute the bbox if dirty + mga.decrComputeGeoBounds(); + } + } + + /** + * Update GeometryArray computed by morphing input GeometryArrays + * with weights + */ + public void updateData(Geometry mga) { + + int i,j,k, vFormat, geoType, stripVCount[]; + int iCount = 0; + int numStrips = 0; + int texCoordSetCount = 0; + float coord[] = new float[3], color[] = new float[4], + normal[] = new float[3], texCoord[] = new float[3]; + + vFormat = geometryArrays[0].vertexFormat; + geoType = ((GeometryArrayRetained)geometryArrays[0]).geoType; + texCoordSetCount = geometryArrays[0].getTexCoordSetCount(); + + + + int vc = 0, nc = 0, cc = 0, n = 0; + int count = 0; + if (geometryArrays[0] instanceof IndexedGeometryArrayRetained){ + count = geometryArrays[0].getNumCoordCount(); + } else { + count = geometryArrays[0].validVertexCount; + } + + for (i=0; i < count; i++) { + Mcoord[vc++] = Mcoord[vc++] = Mcoord[vc++] = 0.0f; + } + + + if ((vFormat & GeometryArray.COLOR) != 0) { + if (geometryArrays[0] instanceof IndexedGeometryArrayRetained){ + count = geometryArrays[0].getNumColorCount(); + } else { + count = geometryArrays[0].validVertexCount; + } + for (i=0; i < count; i++) { + if ((vFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_3) + Mcolor[cc++] = Mcolor[cc++] = Mcolor[cc++] = 0.0f; + + else if ((vFormat & GeometryArray.COLOR_4) == GeometryArray.COLOR_4) + Mcolor[cc++] = Mcolor[cc++] = Mcolor[cc++] = Mcolor[cc++] = 0.0f; + } + } + + + if ((vFormat & GeometryArray.NORMALS) != 0) { + if (geometryArrays[0] instanceof IndexedGeometryArrayRetained){ + count = geometryArrays[0].getNumNormalCount(); + } else { + count = geometryArrays[0].validVertexCount; + } + for (i=0; i < count; i++) { + Mnormal[nc++] = Mnormal[nc++] = Mnormal[nc++] = 0.0f; + } + } + + if ((vFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (k = 0; k < texCoordSetCount; k++) { + if (geometryArrays[0] instanceof IndexedGeometryArrayRetained){ + count = geometryArrays[0].getNumTexCoordCount(k); + } else { + count = geometryArrays[0].validVertexCount; + } + int tcount = 0; + for (i=0; i < count; i++) { + MtexCoord[k][tcount++] = MtexCoord[k][tcount++] = 0.0f; + if ((vFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + MtexCoord[k][tcount++] = 0.0f; + } else if ((vFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + MtexCoord[k][tcount++] = 0.0f; + MtexCoord[k][tcount++] = 0.0f; + } + } + } + } + // If by copy, then ... + if ((vFormat & GeometryArray.BY_REFERENCE) == 0) { + count = 0; + for (j=0;j < numGeometryArrays;j++) { + double w = weights[j]; + if (w != 0) { + vc = 0; nc = 0; cc = 0; + int initialVertex = 0; + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + initialVertex = 0; + count = geometryArrays[j].getNumCoordCount(); + } + else { + initialVertex = geometryArrays[j].getInitialVertexIndex(); + count = geometryArrays[j].validVertexCount; + } + int endVertex = initialVertex + count; + for (i=initialVertex; i< endVertex; i++) { + geometryArrays[j].getCoordinate(i, coord); + Mcoord[vc++] += coord[0]*w; + Mcoord[vc++] += coord[1]*w; + Mcoord[vc++] += coord[2]*w; + } + + if ((vFormat & GeometryArray.COLOR) != 0) { + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + count = geometryArrays[j].getNumColorCount(); + } + endVertex = initialVertex + count; + for (i=initialVertex; i< endVertex; i++) { + geometryArrays[j].getColor(i, color); + Mcolor[cc++] += color[0]*w; + Mcolor[cc++] += color[1]*w; + Mcolor[cc++] += color[2]*w; + if ((vFormat & GeometryArray.WITH_ALPHA) != 0) + Mcolor[cc++] += color[3]*w; + } + } + if ((vFormat & GeometryArray.NORMALS) != 0) { + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + count = geometryArrays[j].getNumNormalCount(); + } + endVertex = initialVertex + count; + for (i=initialVertex; i< endVertex; i++) { + geometryArrays[j].getNormal(i, normal); + Mnormal[nc++] += normal[0]*w; + Mnormal[nc++] += normal[1]*w; + Mnormal[nc++] += normal[2]*w; + } + } + + if ((vFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (k = 0; k < texCoordSetCount; k++) { + int tcount = 0; + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + count = geometryArrays[j].getNumTexCoordCount(i); + } + endVertex = initialVertex + count; + for (i=initialVertex; i< endVertex; i++) { + geometryArrays[j].getTextureCoordinate(k, i, texCoord); + MtexCoord[k][tcount++] += texCoord[0]*w; + MtexCoord[k][tcount++] += texCoord[1]*w; + if ((vFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + MtexCoord[k][tcount++] += texCoord[2]*w; + } else if ((vFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + MtexCoord[k][tcount++] += texCoord[2]*w; + MtexCoord[k][tcount++] += texCoord[3]*w; + } + } + } + } + } + } + } + else { + int vIndex, tIndex, cIndex, nIndex, tstride = 0, cstride = 0; + if ((vFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + if ((vFormat & GeometryArray.TEXTURE_COORDINATE_2) != 0) { + tstride = 2; + } else if ((vFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + tstride = 3; + } else { + tstride = 4; + } + } + + if ((vFormat & GeometryArray.COLOR) != 0) { + cstride = 3; + if ((vFormat & GeometryArray.WITH_ALPHA) != 0) + cstride = 4; + } + + if ((vFormat & GeometryArray.INTERLEAVED) != 0) { + float[] vdata; + int stride; + + stride = geometryArrays[0].stride(); + int coffset = geometryArrays[0].colorOffset(); + int noffset = geometryArrays[0].normalOffset(); + int voffset = geometryArrays[0].coordinateOffset(); + int offset = 0; + + int initialVertex = 0; + for (j=0;j < numGeometryArrays;j++) { + double w = weights[j]; + if (w != 0) { + vc = 0; nc = 0; cc = 0; n = 0; + vdata = geometryArrays[j].getInterleavedVertices(); + if ((vFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (k = 0; k < texCoordSetCount; k++) { + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + tIndex = 0; + count = geometryArrays[j].getNumCoordCount(); + } + else { + tIndex = geometryArrays[j].getInitialVertexIndex(); + count = geometryArrays[j].validVertexCount; + } + offset = (tIndex * stride)+k*tstride; + int tcount = 0; + for (i = 0; i < count; i++, offset += stride) { + MtexCoord[k][tcount++] += vdata[offset] * w; + MtexCoord[k][tcount++] += vdata[offset+1] * w; + if ((vFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) { + MtexCoord[k][tcount++] += vdata[offset+2]*w; + } else if ((vFormat & GeometryArray.TEXTURE_COORDINATE_4) != 0) { + MtexCoord[k][tcount++] += vdata[offset+2]*w; + MtexCoord[k][tcount++] += vdata[offset+3]*w; + } + } + } + + } + if ((vFormat & GeometryArray.COLOR) != 0) { + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + cIndex = 0; + count = geometryArrays[j].getNumCoordCount(); + } + else { + cIndex = geometryArrays[j].getInitialVertexIndex(); + count = geometryArrays[j].validVertexCount; + } + offset = (cIndex * stride)+coffset; + for (i = 0; i < count; i++, offset += stride) { + Mcolor[cc++] += vdata[offset]*w; + Mcolor[cc++] += vdata[offset+1]*w; + Mcolor[cc++] += vdata[offset+2]*w; + if ((vFormat & GeometryArray.WITH_ALPHA)!= 0) + Mcolor[cc++] += vdata[offset+3]*w; + + } + } + + if ((vFormat & GeometryArray.NORMALS) != 0) { + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + nIndex = 0; + count = geometryArrays[j].getNumCoordCount(); + } + else { + nIndex = geometryArrays[j].getInitialVertexIndex(); + count = geometryArrays[j].validVertexCount; + } + offset = (nIndex * stride)+noffset; + for (i = 0; i < count; i++, offset += stride) { + Mnormal[nc++] += vdata[offset]*w; + Mnormal[nc++] += vdata[offset+1]*w; + Mnormal[nc++] += vdata[offset+2]*w; + } + } + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + vIndex = 0; + count = geometryArrays[j].getNumCoordCount(); + } + else { + vIndex = geometryArrays[j].getInitialVertexIndex(); + count = geometryArrays[j].validVertexCount; + } + offset = (vIndex * stride)+voffset; + for (i = 0; i < count; i++, offset += stride) { + Mcoord[vc++] += vdata[offset]*w; + Mcoord[vc++] += vdata[offset+1]*w; + Mcoord[vc++] += vdata[offset+2]*w; + + } + } + } + } + else { + float byteToFloatScale = 1.0f/255.0f; + for (j=0;j < numGeometryArrays;j++) { + double w = weights[j]; + if (w != 0) { + if ((vFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + switch ((geometryArrays[j].vertexType & GeometryArrayRetained.TEXCOORD_DEFINED)) { + case GeometryArrayRetained.TF: + for (k = 0; k < texCoordSetCount; k++) { + float[] tf = geometryArrays[j].getTexCoordRefFloat(k); + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + tIndex = 0; + count = geometryArrays[j].getNumTexCoordCount(k); + } + else { + tIndex = geometryArrays[j].getInitialTexCoordIndex(k); + count = geometryArrays[j].validVertexCount; + } + tIndex *= tstride; + int tcount = 0; + for (i=0; i< count; i++) { + MtexCoord[k][tcount++] += tf[tIndex++]*w; + MtexCoord[k][tcount++] += tf[tIndex++]*w; + if ((vFormat & GeometryArray.TEXTURE_COORDINATE_3) != 0) + MtexCoord[k][tcount++] += tf[tIndex++]*w; + } + } + break; + case GeometryArrayRetained.T2F: + for (k = 0; k < texCoordSetCount; k++) { + int tcount = 0; + float[] tf = geometryArrays[j].getTexCoordRefFloat(k); + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + tIndex = 0; + count = geometryArrays[j].getNumTexCoordCount(k); + } + else { + tIndex = geometryArrays[j].getInitialTexCoordIndex(k); + count = geometryArrays[j].validVertexCount; + } + TexCoord2f[] t2f = geometryArrays[j].getTexCoordRef2f(k); + for (i=0; i< count; i++, tIndex++) { + MtexCoord[k][tcount++] += t2f[tIndex].x*w; + MtexCoord[k][tcount++] += t2f[tIndex].y*w; + } + } + break; + case GeometryArrayRetained.T3F: + for (k = 0; k < texCoordSetCount; k++) { + int tcount = 0; + TexCoord3f[] t3f = geometryArrays[j].getTexCoordRef3f(k); + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + tIndex = 0; + count = geometryArrays[j].getNumTexCoordCount(k); + } + else { + tIndex = geometryArrays[j].getInitialTexCoordIndex(k); + count = geometryArrays[j].validVertexCount; + } + for (i=0; i< count; i++, tIndex++) { + MtexCoord[k][tcount++] += t3f[tIndex].x*w; + MtexCoord[k][tcount++] += t3f[tIndex].y*w; + MtexCoord[k][tcount++] += t3f[tIndex].z*w; + } + } + break; + + } + } + if ((vFormat & GeometryArray.COLOR) != 0) { + double val = byteToFloatScale * w; + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + cIndex = 0; + count = geometryArrays[j].getNumColorCount(); + } + else { + cIndex = geometryArrays[j].getInitialColorIndex(); + count = geometryArrays[j].validVertexCount; + } + + switch ((geometryArrays[j].vertexType & GeometryArrayRetained.COLOR_DEFINED)) { + case GeometryArrayRetained.CF: + float[] cf = geometryArrays[j].getColorRefFloat(); + cc = 0; + cIndex *= cstride; + for (i=0; i< count; i++) { + Mcolor[cc++] += cf[cIndex++]*w; + Mcolor[cc++] += cf[cIndex++]*w; + Mcolor[cc++] += cf[cIndex++]*w; + if ((vFormat & GeometryArray.WITH_ALPHA)!= 0) + Mcolor[cc++] += cf[cIndex++]*w; + } + break; + case GeometryArrayRetained.CUB: + byte[] cub = geometryArrays[j].getColorRefByte(); + cc = 0; + cIndex *= cstride; + for (i=0; i< count; i++) { + Mcolor[cc++] += (cub[cIndex++] & 0xff) * val; + Mcolor[cc++] += (cub[cIndex++] & 0xff) *val; + Mcolor[cc++] += (cub[cIndex++] & 0xff) *val; + if ((vFormat & GeometryArray.WITH_ALPHA)!= 0) + Mcolor[cc++] += (cub[cIndex++] & 0xff) *val; + } + + break; + case GeometryArrayRetained.C3F: + Color3f[] c3f = geometryArrays[j].getColorRef3f(); + cc = 0; + for (i=0; i< count; i++, cIndex++) { + Mcolor[cc++] += c3f[cIndex].x * w; + Mcolor[cc++] += c3f[cIndex].y * w; + Mcolor[cc++] += c3f[cIndex].z * w; + } + break; + case GeometryArrayRetained.C4F: + Color4f[] c4f = geometryArrays[j].getColorRef4f(); + cc = 0; + for (i=0; i< count; i++, cIndex++) { + Mcolor[cc++] += c4f[cIndex].x * w; + Mcolor[cc++] += c4f[cIndex].y * w; + Mcolor[cc++] += c4f[cIndex].z * w; + Mcolor[cc++] += c4f[cIndex].w * w; + } + break; + case GeometryArrayRetained.C3UB: + Color3b[] c3b = geometryArrays[j].getColorRef3b(); + cc = 0; + for (i=0; i< count; i++, cIndex++) { + Mcolor[cc++] += (c3b[cIndex].x & 0xff)* val; + Mcolor[cc++] += (c3b[cIndex].y & 0xff) * val; + Mcolor[cc++] += (c3b[cIndex].z & 0xff) * val; + } + break; + case GeometryArrayRetained.C4UB: + Color4b[] c4b = geometryArrays[j].getColorRef4b(); + cc = 0; + for (i=0; i< count; i++, cIndex++) { + Mcolor[cc++] += (c4b[cIndex].x & 0xff)* val; + Mcolor[cc++] += (c4b[cIndex].y & 0xff) * val; + Mcolor[cc++] += (c4b[cIndex].z & 0xff) * val; + Mcolor[cc++] += (c4b[cIndex].w & 0xff) * val; + } + break; + + } + } + if ((vFormat & GeometryArray.NORMALS) != 0) { + nc = 0; + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + nIndex = 0; + count = geometryArrays[j].getNumNormalCount(); + } + else { + nIndex = geometryArrays[j].getInitialNormalIndex(); + count = geometryArrays[j].validVertexCount; + } + switch ((geometryArrays[j].vertexType & GeometryArrayRetained.NORMAL_DEFINED)) { + case GeometryArrayRetained.NF: + float[] nf = geometryArrays[j].getNormalRefFloat(); + nIndex *= 3; + for (i=0; i< count; i++) { + Mnormal[nc++] += nf[nIndex++]*w; + Mnormal[nc++] += nf[nIndex++]*w; + Mnormal[nc++] += nf[nIndex++]*w; + } + break; + case GeometryArrayRetained.N3F: + Vector3f[] n3f = geometryArrays[j].getNormalRef3f(); + for (i=0; i< count; i++, nIndex++) { + Mnormal[nc++] += n3f[nIndex].x*w; + Mnormal[nc++] += n3f[nIndex].y*w; + Mnormal[nc++] += n3f[nIndex].z*w; + } + break; + } + } + // Handle vertices .. + vc = 0; + if (geometryArrays[j] instanceof IndexedGeometryArrayRetained) { + vIndex = 0; + count = geometryArrays[j].getNumCoordCount(); + } + else { + vIndex = geometryArrays[j].getInitialCoordIndex(); + count = geometryArrays[j].validVertexCount; + } + switch ((geometryArrays[j].vertexType & GeometryArrayRetained.VERTEX_DEFINED)) { + case GeometryArrayRetained.PF: + float[] pf = geometryArrays[j].getCoordRefFloat(); + vIndex *= 3; + for (i=0; i< count; i++) { + Mcoord[vc++] += pf[vIndex++]*w; + Mcoord[vc++] += pf[vIndex++]*w; + Mcoord[vc++] += pf[vIndex++]*w; + } + break; + case GeometryArrayRetained.PD: + double[] pd = geometryArrays[j].getCoordRefDouble(); + vIndex *= 3; + for (i=0; i< count; i++) { + Mcoord[vc++] += (float)pd[vIndex++]*w; + Mcoord[vc++] += (float)pd[vIndex++]*w; + Mcoord[vc++] += (float)pd[vIndex++]*w; + } + break; + case GeometryArrayRetained.P3F: + Point3f[] p3f = geometryArrays[j].getCoordRef3f(); + for (i=0; i< count; i++, vIndex++) { + Mcoord[vc++] += p3f[vIndex].x*w; + Mcoord[vc++] += p3f[vIndex].y*w; + Mcoord[vc++] += p3f[vIndex].z*w; + } + break; + case GeometryArrayRetained.P3D: + Point3d[] p3d = geometryArrays[j].getCoordRef3d(); + for (i=0; i< count; i++, vIndex++) { + Mcoord[vc++] += (float)p3d[vIndex].x*w; + Mcoord[vc++] += (float)p3d[vIndex].y*w; + Mcoord[vc++] += (float)p3d[vIndex].z*w; + } + break; + + } + + } + } + } + } + + GeometryArrayRetained mgaR = + (GeometryArrayRetained)mga.retained; + + mgaR.setCoordRefFloat(Mcoord); + + if ((vFormat & GeometryArray.COLOR) != 0) + mgaR.setColorRefFloat(Mcolor); + + // *******Need to normalize normals + if ((vFormat & GeometryArray.NORMALS) != 0) + mgaR.setNormalRefFloat(Mnormal); + + if ((vFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (k = 0; k < texCoordSetCount; k++) { + mgaR.setTexCoordRefFloat(k, MtexCoord[k]); + } + } + } + + void updateImmediateMirrorObject(Object[] objs) { + int i; + + int component = ((Integer)objs[1]).intValue(); + Shape3DRetained[] msArr = (Shape3DRetained[]) objs[2]; + if ((component & APPEARANCE_CHANGED) != 0) { + Object[] arg = (Object[])objs[3]; + int val = ((Integer)arg[1]).intValue(); + for ( i = msArr.length-1; i >=0; i--) { + msArr[i].appearance = (AppearanceRetained)arg[0]; + msArr[i].changedFrequent = val; + } + } + if ((component & APPEARANCEOVERRIDE_CHANGED) != 0) { + Object[] arg = (Object[])objs[3]; + int val = ((Integer)arg[1]).intValue(); + System.out.println("ChangedFrequent = "+changedFrequent); + for ( i = msArr.length-1; i >=0; i--) { + msArr[i].appearanceOverrideEnable = ((Boolean)arg[0]).booleanValue(); + msArr[i].changedFrequent = val; + } + } + } + + /** + * assign a name to this node when it is made live. + */ + void setLive(SetLiveState s) { + int i, j; + Shape3DRetained shape; + ArrayList msList = new ArrayList(); + GeometryAtom ga; + int oldrefCount = refCount; + + super.doSetLive(s); + nodeId = universe.getNodeId(); + + + for (i = 0; i < numGeometryArrays; i++) { + synchronized(geometryArrays[i].liveStateLock) { + geometryArrays[i].setLive(inBackgroundGroup, s.refCount); + // Add this morph object as user the first time + if (oldrefCount <= 0) { + geometryArrays[i].addMorphUser(this); + } + } + } + + if (this.morphedGeometryArray == null){ + initMorphedGeometry(); + + } + ((GeometryArrayRetained)(morphedGeometryArray.retained)).setLive(inBackgroundGroup, s.refCount); + + if (boundsAutoCompute) { + GeometryArrayRetained mga = (GeometryArrayRetained)morphedGeometryArray.retained; + // Compute the bounds once + mga.incrComputeGeoBounds(); // This compute the bbox if dirty + mga.decrComputeGeoBounds(); + localBounds.setWithLock(mga.geoBounds); + } + + + if (inSharedGroup) { + for (i=0; i<s.keys.length; i++) { + shape = new Shape3DRetained(); + shape.key = s.keys[i]; + shape.localToVworld = new Transform3D[1][]; + shape.localToVworldIndex = new int[1][]; + + + j = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + if(j < 0) { + System.out.println("MorphRetained : Can't find hashKey"); + } + + shape.localToVworld[0] = localToVworld[j]; + shape.localToVworldIndex[0] = localToVworldIndex[j]; + shape.branchGroupPath = (BranchGroupRetained []) branchGroupPaths.get(j); + shape.isPickable = s.pickable[i]; + shape.isCollidable = s.collidable[i]; + + shape.initMirrorShape3D(s, this, j); + mirrorShape3D.add(j, shape); + + msList.add(shape); + // Add any scoped lights to the mirror shape + if (s.lights != null) { + ArrayList l = (ArrayList)s.lights.get(j); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addLight((LightRetained)l.get(m)); + } + } + } + + // Add any scoped fog + if (s.fogs != null) { + ArrayList l = (ArrayList)s.fogs.get(j); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addFog((FogRetained)l.get(m)); + } + } + } + + // Add any scoped modelClip + if (s.modelClips != null) { + ArrayList l = (ArrayList)s.modelClips.get(j); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addModelClip((ModelClipRetained)l.get(m)); + } + } + } + + // Add any scoped alt app + if (s.altAppearances != null) { + ArrayList l = (ArrayList)s.altAppearances.get(j); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addAltApp((AlternateAppearanceRetained)l.get(m)); + } + } + } + + if (s.viewLists != null) + shape.viewList = (ArrayList)s.viewLists.get(i); + else + shape.viewList = null; + + // ((GeometryArrayRetained)(morphedGeometryArray.retained)).addUser(shape); + + + ga = Shape3DRetained.getGeomAtom(shape); + + // Add the geometry atom for this shape to the Targets + s.nodeList.add(ga); + + if (s.transformTargets != null && + s.transformTargets[i] != null) { + s.transformTargets[i].addNode(ga, Targets.GEO_TARGETS); + } + if (s.switchTargets != null && + s.switchTargets[i] != null) { + s.switchTargets[i].addNode(shape, Targets.GEO_TARGETS); + shape.closestSwitchParent = s.closestSwitchParents[i]; + shape.closestSwitchIndex = s.closestSwitchIndices[i]; + } + shape.switchState = (SwitchState)s.switchStates.get(j); + + } + } else { + shape = new Shape3DRetained(); + shape.localToVworld = new Transform3D[1][]; + shape.localToVworldIndex = new int[1][]; + shape.localToVworld[0] = this.localToVworld[0]; + shape.localToVworldIndex[0] = this.localToVworldIndex[0]; + shape.branchGroupPath = (BranchGroupRetained []) branchGroupPaths.get(0); + shape.isPickable = s.pickable[0]; + shape.isCollidable = s.collidable[0]; + shape.initMirrorShape3D(s, this, 0); + mirrorShape3D.add(shape); + + msList.add(shape); + // Add any scoped lights to the mirror shape + if (s.lights != null) { + ArrayList l = (ArrayList)s.lights.get(0); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addLight((LightRetained)l.get(m)); + } + } + } + + // Add any scoped fog + if (s.fogs != null) { + ArrayList l = (ArrayList)s.fogs.get(0); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addFog((FogRetained)l.get(m)); + } + } + } + + // Add any scoped modelClip + if (s.modelClips != null) { + ArrayList l = (ArrayList)s.modelClips.get(0); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addModelClip((ModelClipRetained)l.get(m)); + } + } + } + + // Add any scoped alt app + if (s.altAppearances != null) { + ArrayList l = (ArrayList)s.altAppearances.get(0); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addAltApp((AlternateAppearanceRetained)l.get(m)); + } + } + } + + if (s.viewLists != null) + shape.viewList = (ArrayList)s.viewLists.get(0); + else + shape.viewList = null; + + // ((GeometryArrayRetained)(morphedGeometryArray.retained)).addUser(shape); + + ga = Shape3DRetained.getGeomAtom(shape); + + // Add the geometry atom for this shape to the Targets + s.nodeList.add(ga); + + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(ga, Targets.GEO_TARGETS); + } + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(shape, Targets.GEO_TARGETS); + shape.closestSwitchParent = s.closestSwitchParents[0]; + shape.closestSwitchIndex = s.closestSwitchIndices[0]; + } + shape.switchState = (SwitchState)s.switchStates.get(0); + } + if (appearance != null) { + synchronized(appearance.liveStateLock) { + appearance.setLive(inBackgroundGroup, s.refCount); + appearance.initMirrorObject(); + if (appearance.renderingAttributes != null) + visible = appearance.renderingAttributes.visible; + for (int k = 0; k < msList.size(); k++) { + Shape3DRetained sh = (Shape3DRetained)msList.get(k); + sh.appearance = (AppearanceRetained)appearance.mirror; + appearance.addAMirrorUser(sh); + } + } + + } + else { + for (int k = 0; k < msList.size(); k++) { + Shape3DRetained sh = (Shape3DRetained)msList.get(k); + sh.appearance = null; + } + } + + s.notifyThreads |= (J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_RENDERING_ATTRIBUTES); + + // Need to clone the geometry , if its indexed ... + if (refCount == 1 && this.geometryArrays[0] instanceof IndexedGeometryArrayRetained) { + J3dMessage mChangeMessage = VirtualUniverse.mc.getMessage(); + mChangeMessage.type = J3dMessage.MORPH_CHANGED; + mChangeMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + mChangeMessage.args[0] = this; + mChangeMessage.args[1]= new Integer(GEOMETRY_CHANGED); + mChangeMessage.universe = universe; + VirtualUniverse.mc.processMessage(mChangeMessage); + } + super.markAsLive(); + + } + + + /** + * assign a name to this node when it is made live. + */ + void clearLive(SetLiveState s) { + int i, j; + Shape3DRetained shape; + Object[] shapes; + ArrayList appList = new ArrayList(); + GeometryAtom ga; + + super.clearLive(s); + + for (i = 0; i < numGeometryArrays; i++) { + synchronized(geometryArrays[i].liveStateLock) { + geometryArrays[i].clearLive(s.refCount); + // Remove this morph object as user, when the last branch + // is clearlived + if (refCount <= 0) { + geometryArrays[i].removeMorphUser(this); + } + } + } + GeometryArrayRetained mga = (GeometryArrayRetained)morphedGeometryArray.retained; + + mga.clearLive( s.refCount); + + if (inSharedGroup) { + shapes = mirrorShape3D.toArray(); + for (i=0; i<s.keys.length; i++) { + for (j=0; j<shapes.length; j++) { + shape = (Shape3DRetained)shapes[j]; + if (shape.key.equals(s.keys[i])) { + // clearMirrorShape(shape); + mirrorShape3D.remove(j); + if (s.switchTargets != null && + s.switchTargets[i] != null) { + s.switchTargets[i].addNode(shape, + Targets.GEO_TARGETS); + } + if (appearance != null) + appList.add(shape); + + // ((GeometryArrayRetained)(morphedGeometryArray.retained)).removeUser(shape); + ga = Shape3DRetained.getGeomAtom(shape); + + // Add the geometry atoms for this shape to the Targets + s.nodeList.add(ga); + if (s.transformTargets != null && + s.transformTargets[i] != null) { + s.transformTargets[i].addNode(ga, + Targets.GEO_TARGETS); + } + } + } + } + } else { + // Only entry 0 is valid + shape = (Shape3DRetained)mirrorShape3D.get(0); + // clearMirrorShape(shape); + mirrorShape3D.remove(0); + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(shape, Targets.GEO_TARGETS); + } + if (appearance != null) + appList.add(shape); + + // ((GeometryArrayRetained)(morphedGeometryArray.retained)).removeUser(shape); + ga = Shape3DRetained.getGeomAtom(shape); + + // Add the geometry atom for this shape to the Targets + s.nodeList.add(ga); + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(ga, Targets.GEO_TARGETS); + } + } + if (appearance != null) { + synchronized(appearance.liveStateLock) { + appearance.clearLive(s.refCount); + for (int k = 0; k < appList.size(); k++) { + appearance.removeAMirrorUser((Shape3DRetained)appList.get(k)); + } + } + } + + s.notifyThreads |= (J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_TRANSFORM | + // This is used to clear the scope info + // of all the mirror shapes + J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER); + + } + + + void updatePickable(HashKey keys[], boolean pick[]) { + super.updatePickable(keys, pick); + + Shape3DRetained shape; + + if (!inSharedGroup) { + shape = (Shape3DRetained) mirrorShape3D.get(0); + shape.isPickable = pick[0]; + } else { + int size = mirrorShape3D.size(); + for (int j=0; j< keys.length; j++) { + for (int i=0; i < size; i++) { + shape = (Shape3DRetained) mirrorShape3D.get(i); + if (keys[j].equals(shape.key)) { + shape.isPickable = pick[j]; + break; + } + + } + } + } + } + + + void updateCollidable(HashKey keys[], boolean collide[]) { + super.updateCollidable(keys, collide); + Shape3DRetained shape; + + if (!inSharedGroup) { + shape = (Shape3DRetained) mirrorShape3D.get(0); + shape.isCollidable = collide[0]; + } else { + int size = mirrorShape3D.size(); + for (int j=0; j< keys.length; j++) { + for (int i=0; i < size; i++) { + shape = (Shape3DRetained) mirrorShape3D.get(i); + if (keys[j].equals(shape.key)) { + shape.isCollidable = collide[j]; + break; + } + + } + } + } + } + + Shape3DRetained getMirrorShape(SceneGraphPath path) { + if (!inSharedGroup) { + return (Shape3DRetained) mirrorShape3D.get(0); + } + HashKey key = new HashKey(""); + path.getHashKey(key); + return getMirrorShape(key); + } + + Shape3DRetained getMirrorShape(HashKey key) { + int i = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + if (i>=0) { + return (Shape3DRetained) mirrorShape3D.get(i); + } + + // Not possible + throw new RuntimeException("Shape3DRetained: MirrorShape Not found!"); + } + + void getMirrorObjects(ArrayList leafList, HashKey key) { + Shape3DRetained ms; + if (inSharedGroup) { + ms = getMirrorShape(key); + } + else { + ms = (Shape3DRetained)mirrorShape3D.get(0); + } + GeometryAtom ga = Shape3DRetained.getGeomAtom(ms); + leafList.add(ga); + + } + + void setBoundsAutoCompute(boolean autoCompute) { + if (autoCompute != boundsAutoCompute) { + if (autoCompute) { + // localBounds may not have been set to bbox + localBounds = new BoundingBox(); + if (source.isLive() && morphedGeometryArray != null) { + GeometryArrayRetained mga = (GeometryArrayRetained)morphedGeometryArray.retained; + mga.incrComputeGeoBounds(); // This compute the bbox if dirty + mga.decrComputeGeoBounds(); + } + } + + + localBounds = getBounds(); + super.setBoundsAutoCompute(autoCompute); + if (source.isLive()) { + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.BOUNDS_AUTO_COMPUTE_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_RENDER; + message.universe = universe; + message.args[0] = Shape3DRetained.getGeomAtomsArray(mirrorShape3D); + message.args[1] = localBounds; + VirtualUniverse.mc.processMessage(message); + } + } + } + + void updateBounds() { + localBounds = getEffectiveBounds(); + if (source.isLive()) { + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.BOUNDS_AUTO_COMPUTE_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_RENDER; + message.universe = universe; + message.args[0] = Shape3DRetained.getGeomAtomsArray(mirrorShape3D); + message.args[1] = localBounds; + VirtualUniverse.mc.processMessage(message); + } + } + + /** + * Initialization of morphed geometry + */ + void initMorphedGeometry() { + int vFormat, geoType, stripVCount[]; + int iCount = 0; + int numStrips = 0; + int texCoordSetCount = 0; + int texCoordSetMapLen = 0; + int [] texCoordSetMap = null; + int k; + GeometryArrayRetained geo = geometryArrays[0]; + vFormat = ((geo.getVertexFormat() | (GeometryArray.BY_REFERENCE)) & ~(GeometryArray.INTERLEAVED)) ; + texCoordSetCount = geo.getTexCoordSetCount(); + texCoordSetMapLen = geo.getTexCoordSetMapLength(); + if (texCoordSetMapLen > 0) { + texCoordSetMap = new int[texCoordSetMapLen]; + geo.getTexCoordSetMap(texCoordSetMap); + } + geoType = geo.geoType; + + switch (geoType){ + case GeometryRetained.GEO_TYPE_QUAD_SET: + this.morphedGeometryArray = + new QuadArray(geometryArrays[0].validVertexCount, vFormat, texCoordSetCount, + texCoordSetMap); + break; + case GeometryRetained.GEO_TYPE_TRI_SET: + this.morphedGeometryArray = + new TriangleArray(geometryArrays[0].validVertexCount, vFormat, texCoordSetCount, + texCoordSetMap); + break; + case GeometryRetained.GEO_TYPE_POINT_SET: + this.morphedGeometryArray = + new PointArray(geometryArrays[0].validVertexCount, vFormat, texCoordSetCount, + texCoordSetMap); + break; + case GeometryRetained.GEO_TYPE_LINE_SET: + this.morphedGeometryArray = + new LineArray(geometryArrays[0].validVertexCount, vFormat, texCoordSetCount, + texCoordSetMap); + break; + case GeometryRetained.GEO_TYPE_TRI_STRIP_SET: + numStrips = ((TriangleStripArrayRetained)geo).getNumStrips(); + stripVCount = new int[numStrips]; + ((TriangleStripArrayRetained)geo).getStripVertexCounts(stripVCount); + this.morphedGeometryArray = + new TriangleStripArray(geometryArrays[0].validVertexCount, vFormat, texCoordSetCount, + texCoordSetMap, stripVCount); + break; + case GeometryRetained.GEO_TYPE_TRI_FAN_SET: + numStrips = ((TriangleFanArrayRetained)geo).getNumStrips(); + stripVCount = new int[numStrips]; + ((TriangleFanArrayRetained)geo).getStripVertexCounts(stripVCount); + this.morphedGeometryArray = + new TriangleFanArray(geometryArrays[0].validVertexCount, vFormat, texCoordSetCount, + texCoordSetMap, stripVCount); + break; + case GeometryRetained.GEO_TYPE_LINE_STRIP_SET: + numStrips = ((LineStripArrayRetained)geo).getNumStrips(); + stripVCount = new int[numStrips]; + ((LineStripArrayRetained)geo).getStripVertexCounts(stripVCount); + this.morphedGeometryArray = + new LineStripArray(geometryArrays[0].validVertexCount, vFormat, texCoordSetCount, + texCoordSetMap, stripVCount); + break; + + case GeometryRetained.GEO_TYPE_INDEXED_QUAD_SET: + iCount = ((IndexedGeometryArrayRetained)geo).getIndexCount(); + this.morphedGeometryArray = + new IndexedQuadArray(geometryArrays[0].getNumCoordCount(), vFormat, texCoordSetCount, + texCoordSetMap, iCount); + break; + case GeometryRetained.GEO_TYPE_INDEXED_TRI_SET: + iCount = ((IndexedGeometryArrayRetained)geo).getIndexCount(); + this.morphedGeometryArray = + new IndexedTriangleArray(geometryArrays[0].getNumCoordCount(), vFormat, texCoordSetCount, + texCoordSetMap, iCount); + break; + case GeometryRetained.GEO_TYPE_INDEXED_POINT_SET: + iCount = ((IndexedGeometryArrayRetained)geo).getIndexCount(); + this.morphedGeometryArray = + new IndexedPointArray(geometryArrays[0].getNumCoordCount(), vFormat, texCoordSetCount, + texCoordSetMap, iCount); + break; + case GeometryRetained.GEO_TYPE_INDEXED_LINE_SET: + iCount = ((IndexedGeometryArrayRetained)geo).getIndexCount(); + this.morphedGeometryArray = + new IndexedLineArray(geometryArrays[0].getNumCoordCount(), vFormat, texCoordSetCount, + texCoordSetMap, iCount); + break; + case GeometryRetained.GEO_TYPE_INDEXED_TRI_STRIP_SET: + iCount = ((IndexedGeometryArrayRetained)geo).getIndexCount(); + numStrips = ((IndexedTriangleStripArrayRetained)geo).getNumStrips(); + stripVCount = new int[numStrips]; + ((IndexedTriangleStripArrayRetained)geo).getStripIndexCounts(stripVCount); + this.morphedGeometryArray = + new IndexedTriangleStripArray(geometryArrays[0].getNumCoordCount(), vFormat, texCoordSetCount, + texCoordSetMap, iCount, stripVCount);break; + case GeometryRetained.GEO_TYPE_INDEXED_TRI_FAN_SET: + iCount = ((IndexedGeometryArrayRetained)geo).getIndexCount(); + numStrips = ((IndexedTriangleFanArrayRetained)geo).getNumStrips(); + stripVCount = new int[numStrips]; + ((IndexedTriangleFanArrayRetained)geo).getStripIndexCounts(stripVCount); + this.morphedGeometryArray = + new IndexedTriangleFanArray(geometryArrays[0].getNumCoordCount(), vFormat, texCoordSetCount, + texCoordSetMap, iCount, stripVCount);break; + case GeometryRetained.GEO_TYPE_INDEXED_LINE_STRIP_SET: + iCount = ((IndexedGeometryArrayRetained)geo).getIndexCount(); + numStrips = ((IndexedLineStripArrayRetained)geo).getNumStrips(); + stripVCount = new int[numStrips]; + ((IndexedLineStripArrayRetained)geo).getStripIndexCounts(stripVCount); + this.morphedGeometryArray = + new IndexedLineStripArray(geometryArrays[0].getNumCoordCount(), vFormat, texCoordSetCount, + texCoordSetMap, iCount, stripVCount);break; + } + if (geometryArrays[0] instanceof IndexedGeometryArrayRetained) { + IndexedGeometryArrayRetained igeo = (IndexedGeometryArrayRetained) + geometryArrays[0]; + IndexedGeometryArray morphedGeo = (IndexedGeometryArray) + morphedGeometryArray; + if ((vFormat & GeometryArray.COORDINATES) != 0) { + morphedGeo.setCoordinateIndices(0, igeo.indexCoord); + } + if ((vFormat & GeometryArray.NORMALS) != 0) { + morphedGeo.setNormalIndices(0, igeo.indexNormal); + } + if ((vFormat & GeometryArray.COLOR) != 0) { + morphedGeo.setColorIndices(0, igeo.indexColor); + } + if ((vFormat & GeometryArray.TEXTURE_COORDINATE) != 0) { + for (k = 0; k < texCoordSetCount; k++) { + morphedGeo.setTextureCoordinateIndices(k, 0, + (int[]) igeo.indexTexCoord[k]); + } + } + } + this.morphedGeometryArray.setCapability(GeometryArray.ALLOW_REF_DATA_WRITE); + + GeometryArrayRetained mga = (GeometryArrayRetained)morphedGeometryArray.retained; + mga.updateData(this); + + + } + + void getMirrorShape3D(ArrayList list, HashKey k) { + Shape3DRetained ms; + if (inSharedGroup) { + ms = getMirrorShape(k); + } + else { + ms = (Shape3DRetained)mirrorShape3D.get(0); + } + list.add(ms); + + } + + void compile(CompileState compState) { + + super.compile(compState); + + // TODO: for now keep the static transform in the parent tg + compState.keepTG = true; + + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numMorphs++; + } + } + + void doErrorCheck(GeometryArrayRetained prevGeo, GeometryArrayRetained geo) { + + // If indexed Geometry array check the entire list instead of just the vcount + if ((prevGeo.vertexFormat != geo.vertexFormat) || + (prevGeo.validVertexCount != geo.validVertexCount) || + (prevGeo.geoType != geo.geoType) || + (prevGeo.texCoordSetCount != geo.texCoordSetCount)) { + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + } + if (geo.getTexCoordSetMapLength() != prevGeo.getTexCoordSetMapLength()){ + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + } + int texCoordSetMapLen = geo.getTexCoordSetMapLength(); + int[] prevSvc= prevGeo.texCoordSetMap; + int[] svc= geo.texCoordSetMap; + for (int k = 0; k < texCoordSetMapLen; k++) { + if (prevSvc[k] != svc[k]) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + } + + if (geo instanceof GeometryStripArrayRetained) { + prevSvc= ((GeometryStripArrayRetained)prevGeo).stripVertexCounts; + svc= ((GeometryStripArrayRetained)geo).stripVertexCounts; + if (prevSvc.length != svc.length) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + for (int k = 0; k < prevSvc.length; k++) { + if (prevSvc[k] != svc[k]) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + } + } else if (geo instanceof IndexedGeometryArrayRetained) { + if (((IndexedGeometryArrayRetained)prevGeo).validIndexCount != ((IndexedGeometryArrayRetained)geo).validIndexCount) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + + // If by reference, then all array lengths should be same + if (geo.getNumCoordCount() != prevGeo.getNumCoordCount() || + geo.getNumColorCount() != prevGeo.getNumColorCount() || + geo.getNumNormalCount() != prevGeo.getNumNormalCount()) { + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + } + int texCoordSetCount = geo.getTexCoordSetCount(); + for (int k = 0; k < texCoordSetCount; k++) { + if (geo.getNumTexCoordCount(k) != prevGeo.getNumTexCoordCount(k)) { + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + } + } + + if (geo instanceof IndexedGeometryStripArrayRetained) { + prevSvc= ((IndexedGeometryStripArrayRetained)prevGeo).stripIndexCounts; + svc= ((IndexedGeometryStripArrayRetained)geo).stripIndexCounts; + if (prevSvc.length != svc.length) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + for (int k = 0; k < prevSvc.length; k++) { + if (prevSvc[k] != svc[k]) + throw new IllegalArgumentException(J3dI18N.getString("MorphRetained1")); + } + } + } + } + + void handleFrequencyChange(int bit) { + int mask = 0; + if (bit == Morph.ALLOW_GEOMETRY_ARRAY_WRITE) { + mask = GEOMETRY_CHANGED; + } + else if (bit == Morph.ALLOW_APPEARANCE_WRITE) { + mask = APPEARANCE_CHANGED; + } + else if (bit == Morph.ALLOW_APPEARANCE_OVERRIDE_WRITE) { + mask = APPEARANCEOVERRIDE_CHANGED; + } + if (mask != 0) { + if (source.getCapabilityIsFrequent(bit)) + changedFrequent |= mask; + else if (!source.isLive()) { + changedFrequent &= ~mask; + } + } + } + + void searchGeometryAtoms(UnorderList list) { + list.add(Shape3DRetained.getGeomAtom( + (Shape3DRetained) mirrorShape3D.get(0))); + } +} + + + + + diff --git a/src/classes/share/javax/media/j3d/MultipleParentException.java b/src/classes/share/javax/media/j3d/MultipleParentException.java new file mode 100644 index 0000000..23092d8 --- /dev/null +++ b/src/classes/share/javax/media/j3d/MultipleParentException.java @@ -0,0 +1,37 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates + * an attempt to add a node that is already a child of one + * group node, into another group node. + */ +public class MultipleParentException extends IllegalSharingException { + +/** + * Create the exception object with default values. + */ + public MultipleParentException(){ + } + +/** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public MultipleParentException(String str){ + + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/NativeAPIInfo.java b/src/classes/share/javax/media/j3d/NativeAPIInfo.java new file mode 100644 index 0000000..d1bf0ca --- /dev/null +++ b/src/classes/share/javax/media/j3d/NativeAPIInfo.java @@ -0,0 +1,32 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * Portions of this code were derived from work done by the Blackdown + * group (www.blackdown.org), who did the initial Linux implementation + * of the Java 3D API. + */ + +package javax.media.j3d; + +class NativeAPIInfo { + + /** + * Returns the rendering API being used. + * @return the rendering API, one of: + * <code>MasterControl.RENDER_OPENGL_LINUX</code>, + * <code>MasterControl.RENDER_OPENGL_SOLARIS</code>, + * <code>MasterControl.RENDER_OPENGL_WIN32</code>, + * or <code>MasterControl.RENDER_DIRECT3D</code> + */ + native int getRenderingAPI(); +} diff --git a/src/classes/share/javax/media/j3d/NnuId.java b/src/classes/share/javax/media/j3d/NnuId.java new file mode 100644 index 0000000..beb1f26 --- /dev/null +++ b/src/classes/share/javax/media/j3d/NnuId.java @@ -0,0 +1,25 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Defines a "not necessarily unique ID" + */ + +interface NnuId { + + abstract int equal(NnuId obj); + + abstract int getId(); + +} diff --git a/src/classes/share/javax/media/j3d/NnuIdManager.java b/src/classes/share/javax/media/j3d/NnuIdManager.java new file mode 100644 index 0000000..6cf3696 --- /dev/null +++ b/src/classes/share/javax/media/j3d/NnuIdManager.java @@ -0,0 +1,335 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class NnuIdManager { + static int nnuId = 0; + + final static int getId() { + if(nnuId == Integer.MAX_VALUE) { + nnuId = 0; + } + + return nnuId++; + } + + final static int equals(NnuId nnuIdArr[], NnuId key, int start, int end) { + int mid; + + mid = start +((end - start)/ 2); + if(nnuIdArr[mid] != null) { + int test = key.equal(nnuIdArr[mid]); + + if((test < 0) && (start != mid)) + return equals(nnuIdArr, key, start, mid); + else if((test > 0) && (start != mid)) + return equals(nnuIdArr, key, mid, end); + else if(test == 0) { + // Since id is not necessary unique, we've to do + // some extra check. + + // check for equal reference. + if(key == nnuIdArr[mid]) { + return mid; + } + + int temp = mid - 1; + // Look to the left. + while((temp >= start) && (key.equal(nnuIdArr[temp]) == 0)) { + if(key == nnuIdArr[temp]) { + return temp; + } + temp--; + } + + // Look to the right. + temp = mid + 1; + while((temp < end) && (key.equal(nnuIdArr[temp]) == 0)) { + if(key == nnuIdArr[temp]) { + return temp; + } + temp++; + } + + // Fail equal reference check. + return -1; + } + else + return -1; + } + // A null NnuId object encountered. + return -2; + } + + final static boolean equals(NnuId nnuIdArr[], NnuId key, int[] index, + int start, int end) { + + int mid; + + mid = start +((end - start)/ 2); + + if(nnuIdArr[mid] != null) { + int test = key.equal(nnuIdArr[mid]); + + if(start != mid) { + if(test < 0) { + return equals(nnuIdArr, key, index, start, mid); + } + else if(test > 0) { + return equals(nnuIdArr, key, index, mid, end); + } + } + else { // (start == mid) + if(test < 0) { + index[0] = mid; + return false; + } + else if(test > 0) { + index[0] = mid+1; + return false; + } + } + + // (test == 0) + // Since id is not necessary unique, we've to do + // some extra check. + + // check for equal reference. + if(key == nnuIdArr[mid]) { + index[0] = mid; + return true; + } + + int temp = mid - 1; + // Look to the left. + while((temp >= start) && (key.equal(nnuIdArr[temp]) == 0)) { + if(key == nnuIdArr[temp]) { + index[0] = temp; + return true; + } + temp--; + } + + // Look to the right. + temp = mid + 1; + while((temp < end) && (key.equal(nnuIdArr[temp]) == 0)) { + if(key == nnuIdArr[temp]) { + index[0] = temp; + return true; + } + temp++; + } + + // Fail equal reference check. + index[0] = temp; + return false; + + } + // A null entry encountered. + // But we still want to return the index where we encounter it. + index[0] = mid; + return false; + } + + final static void sort(NnuId nnuIdArr[]) { + if (nnuIdArr.length < 20) { + insertSort(nnuIdArr); + } else { + quicksort(nnuIdArr, 0, nnuIdArr.length-1); + } + } + + // Insertion sort on smaller array + final static void insertSort(NnuId nnuIdArr[]) { + + + for (int i=0; i<nnuIdArr.length; i++) { + for (int j=i; j>0 && + (nnuIdArr[j-1].getId() > nnuIdArr[j].getId()); j--) { + NnuId temp = nnuIdArr[j]; + nnuIdArr[j] = nnuIdArr[j-1]; + nnuIdArr[j-1] = temp; + } + } + } + + final static void quicksort( NnuId nnuIdArr[], int l, int r ) { + int i = l; + int j = r; + int k = nnuIdArr[(l+r) / 2].getId(); + + do { + while (nnuIdArr[i].getId() < k) i++; + while (k < nnuIdArr[j].getId()) j--; + if (i<=j) { + NnuId tmp = nnuIdArr[i]; + nnuIdArr[i] = nnuIdArr[j]; + nnuIdArr[j] = tmp; + + i++; + j--; + } + } while (i<=j); + + if (l<j) quicksort(nnuIdArr, l,j); + if (l<r) quicksort(nnuIdArr, i,r); + } + + + + // This method assumes that nnuIdArr0 and nnuIdArr1 are sorted. + final static NnuId[] delete( NnuId nnuIdArr0[], NnuId nnuIdArr1[] ) { + + int i, index, len; + int curStart =0, newStart =0; + boolean found = false; + + int size = nnuIdArr0.length - nnuIdArr1.length; + + if(size > 0) { + NnuId newNnuIdArr[] = new NnuId[size]; + + for(i=0; i<nnuIdArr1.length; i++) { + index = equals(nnuIdArr0, nnuIdArr1[i], 0, nnuIdArr0.length); + + if(index >= 0) { + found = true; + if(index == curStart) { + curStart++; + } + else { + len = index - curStart; + System.arraycopy(nnuIdArr0, curStart, + newNnuIdArr, newStart, len); + + curStart = index+1; + newStart = newStart + len; + } + } + else { + found = false; + System.out.println("Can't Find matching nnuId."); + System.out.println("We're in TROUBLE!!!"); + } + } + + if((found == true) && (curStart < nnuIdArr0.length)) { + len = nnuIdArr0.length - curStart; + System.arraycopy(nnuIdArr0, curStart, newNnuIdArr, newStart, len); + } + + return newNnuIdArr; + } + else if( size == 0) { + // Remove all. + } + else { + // We are in trouble !!! + } + + return null; + + } + + + // This method assumes that nnuIdArr0 and nnuIdArr1 are sorted. + final static NnuId[] merge( NnuId nnuIdArr0[], NnuId nnuIdArr1[] ) { + + int index[] = new int[1]; + int indexPlus1, blkSize, i, j; + + int size = nnuIdArr0.length + nnuIdArr1.length; + + NnuId newNnuIdArr[] = new NnuId[size]; + + // Copy the nnuIdArr0 data into the newly created newNnuIdArr. + System.arraycopy(nnuIdArr0, 0, newNnuIdArr, 0, nnuIdArr0.length); + + for(i=nnuIdArr0.length, j=0; i<size; i++, j++) { + // True or false, it doesn't matter. + equals((NnuId[])newNnuIdArr, nnuIdArr1[j], index, 0, i); + + if(index[0] == i) { // Append to last. + newNnuIdArr[i] = nnuIdArr1[j]; + } + else { // Insert in between array elements. + indexPlus1 = index[0] + 1; + blkSize = i - index[0]; + + // Shift the later portion of array elements by one position. + // This is the make room for the new data entry. + System.arraycopy(newNnuIdArr, index[0], newNnuIdArr, + indexPlus1, blkSize); + + newNnuIdArr[index[0]] = nnuIdArr1[j]; + } + + } + + return newNnuIdArr; + + } + + + final static void replace(NnuId oldObj, NnuId newObj, NnuId nnuIdArr[]) { + + + int[] index = new int[1]; + int lenLess1 = nnuIdArr.length - 1; + int blkSize; + + // delete old from nnuIdArr. + index[0] = equals(nnuIdArr, oldObj, 0, nnuIdArr.length); + if(index[0] == lenLess1) { + nnuIdArr[index[0]] = null; + } + else if(index[0] >= 0) { + blkSize = lenLess1 - index[0]; + System.arraycopy(nnuIdArr, index[0]+1, + nnuIdArr, index[0], blkSize); + nnuIdArr[lenLess1] = null; + } + else { + System.out.println("Can't Find matching nnuId."); + System.out.println("We're in TROUBLE!!!"); + } + + // insert new to nnuIdArr. + equals(nnuIdArr, newObj, index, 0, lenLess1); + + if(index[0] == lenLess1) { // Append to last. + nnuIdArr[index[0]] = newObj; + } + else { // Insert in between array elements. + blkSize = lenLess1 - index[0]; + + // Shift the later portion of array elements by one position. + // This is the make room for the new data entry. + System.arraycopy(nnuIdArr, index[0], nnuIdArr, + index[0]+1, blkSize); + + nnuIdArr[index[0]] = newObj; + } + + + } + + + final static void printIds(NnuId nnuIdArr[]) { + for(int i=0; i<nnuIdArr.length; i++) { + System.out.println("[" + i +"] is " + nnuIdArr[i].getId()); + } + + } + +} diff --git a/src/classes/share/javax/media/j3d/Node.java b/src/classes/share/javax/media/j3d/Node.java new file mode 100644 index 0000000..6488a15 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Node.java @@ -0,0 +1,733 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Hashtable; +import java.util.Enumeration; +import java.lang.reflect.Constructor; + +/** + * The Node class provides an abstract class for all Group and Leaf Nodes. + * It provides a common framework for constructing a Java 3D scene graph, + * specifically bounding volumes. + * <p> + * NOTE: Applications should <i>not</i> extend this class directly. + */ +public abstract class Node extends SceneGraphObject { + + /** + * Specifies that this Node will be reported in the pick + * SceneGraphPath if a pick occurs. This capability is only + * specifiable for Group nodes; it is ignored for leaf nodes. + * The default for Group nodes is false. All interior nodes not + * needed for uniqueness in a SceneGraphPath that don't have + * ENABLE_PICK_REPORTING set to true will not be reported in the + * SceneGraphPath. + * @see SceneGraphPath + */ + public static final int + ENABLE_PICK_REPORTING = CapabilityBits.NODE_ENABLE_PICK_REPORTING; + + /** + * Specifies that this Node will be reported in the collision + * SceneGraphPath if a collision occurs. This capability is only + * specifiable for Group nodes; it is ignored for leaf nodes. + * The default for Group nodes is false. All interior nodes not + * needed for uniqueness in a SceneGraphPath that don't have + * ENABLE_COLLISION_REPORTING set to true will not be reported + * in the SceneGraphPath. + * @see SceneGraphPath + */ + public static final int + ENABLE_COLLISION_REPORTING = CapabilityBits.NODE_ENABLE_COLLISION_REPORTING; + + /** + * Specifies that this Node allows read access to its bounds + * information. + */ + public static final int + ALLOW_BOUNDS_READ = CapabilityBits.NODE_ALLOW_BOUNDS_READ; + + /** + * Specifies that this Node allows write access to its bounds + * information. + */ + public static final int + ALLOW_BOUNDS_WRITE = CapabilityBits.NODE_ALLOW_BOUNDS_WRITE; + + /** + * Specifies that this Node allows reading its pickability state. + */ + public static final int + ALLOW_PICKABLE_READ = CapabilityBits.NODE_ALLOW_PICKABLE_READ; + + /** + * Specifies that this Node allows write access its pickability state. + */ + public static final int + ALLOW_PICKABLE_WRITE = CapabilityBits.NODE_ALLOW_PICKABLE_WRITE; + + /** + * Specifies that this Node allows reading its collidability state. + */ + public static final int + ALLOW_COLLIDABLE_READ = CapabilityBits.NODE_ALLOW_COLLIDABLE_READ; + + /** + * Specifies that this Node allows write access its collidability state. + */ + public static final int + ALLOW_COLLIDABLE_WRITE = CapabilityBits.NODE_ALLOW_COLLIDABLE_WRITE; + + /** + * Specifies that this Node allows read access to its bounds + * auto compute information. + */ + public static final int + ALLOW_AUTO_COMPUTE_BOUNDS_READ = CapabilityBits.NODE_ALLOW_AUTO_COMPUTE_BOUNDS_READ; + + /** + * Specifies that this Node allows write access to its bounds + * auto compute information. + */ + public static final int + ALLOW_AUTO_COMPUTE_BOUNDS_WRITE = CapabilityBits.NODE_ALLOW_AUTO_COMPUTE_BOUNDS_WRITE; + + /** + * Specifies that this Node allows read access to its local + * coordinates to virtual world (Vworld) coordinates transform. + */ + public static final int + ALLOW_LOCAL_TO_VWORLD_READ = CapabilityBits.NODE_ALLOW_LOCAL_TO_VWORLD_READ; + + // for checking for cycles + private boolean visited = false; + + + /** + * Constructs a Node object with default parameters. The default + * values are as follows: + * <ul> + * pickable : true<br> + * collidable : true<br> + * bounds auto compute : true<br> + * bounds : N/A (automatically computed)<br> + * </ul> + */ + public Node() { + } + + /** + * Retrieves the parent of this Node. This method is only valid + * during the construction of the scene graph. + * @return the parent of this node, or null if this node has no parent + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + */ + public Node getParent() { + if (isLiveOrCompiled()) + throw new RestrictedAccessException(J3dI18N.getString("Node0")); + + NodeRetained nr = ((NodeRetained)this.retained).getParent(); + return (nr == null ? null : (Node) nr.getSource()); + } + + /** + * Sets the geometric bounds of a node. + * @param bounds the bounding object for a node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setBounds(Bounds bounds) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Node1")); + + ((NodeRetained)this.retained).setBounds(bounds); + } + + /** + * Returns the bounding object of a node. + * @return the node's bounding object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + */ + public Bounds getBounds() { + + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_BOUNDS_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("Node2")); + } + } + else { + // this will throw a SceneGraphCycleException if there is + // a cycle + checkForCycle(); + } + + return ((NodeRetained)this.retained).getBounds(); + } + + /** + * Returns the collidable value; this value determines whether this node + * and it's children, if a group node, can be considered for collision + * purposes; if it is set to false, then neither this node nor any + * children nodes will be traversed for collision purposes; the default + * value is true. The collidable setting is the way that an + * application can perform collision culling. + * @return the present collidable value for this node + */ + public boolean getCollidable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLLIDABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Node16")); + + return ((NodeRetained)retained).getCollidable(); + } + + /** + * Sets the collidable value; determines whether this node and any of its + * children, if a group node, can be considered for collision purposes. + * @param collidable the new collidable value for this node + */ + public void setCollidable( boolean collidable ) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_COLLIDABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Node4")); + + ((NodeRetained)retained).setCollidable(collidable); + } + + /** + * Turns the automatic calcuation of geometric bounds of a node on/off. + * @param autoCompute indicates if the node's bounding object is + * automatically computed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setBoundsAutoCompute(boolean autoCompute) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_AUTO_COMPUTE_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Node5")); + + ((NodeRetained)this.retained).setBoundsAutoCompute(autoCompute); + } + + /** + * Gets the value indicating if the automatic calcuation of geometric bounds of a node is on/off. + * @return the node's auto compute flag for the geometric bounding object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getBoundsAutoCompute() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_AUTO_COMPUTE_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Node6")); + + return ((NodeRetained)this.retained).getBoundsAutoCompute(); + } + + /** + * Retrieves the local coordinates to virtual world coordinates + * transform for this node in the scene graph. This is the composite + * of all transforms in the scene graph from the root down to + * <code>this</code> node. It is only valid + * for nodes that are part of a live scene graph. + * @param t the object that will receive the local coordinates to + * Vworld coordinates transform. + * @exception RestrictedAccessException if the node is <em>not</em> + * part of a live scene graph + * @exception CapabilityNotSetException if appropriate capability is + * not set and this node is part of live scene graph + * @exception IllegalSharingException if the node is a descendant + * of a SharedGroup node. + */ + public void getLocalToVworld(Transform3D t) { + if (!isLive()) + throw new RestrictedAccessException(J3dI18N.getString("Node7")); + + if(!this.getCapability(ALLOW_LOCAL_TO_VWORLD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Node8")); + + ((NodeRetained)this.retained).getLocalToVworld(t); + } + + /** + * Retrieves the local coordinates to virtual world coordinates + * transform for the particular path in the scene graph ending with + * this node. This is the composite + * of all transforms in the scene graph from the root down to + * <code>this</code> node via the specified Link nodes. It is + * only valid for nodes that are part of a live scene graph. + * @param path the specific path from the node to the Locale + * @param t the object that will receive the local coordinates to + * Vworld coordinates transform. + * @exception RestrictedAccessException if the node is <em>not</em> + * part of a live scene graph + * @exception CapabilityNotSetException if appropriate capability is + * not set and this node is part of live scene graph + * @exception IllegalArgumentException if the specified path does + * not contain a valid Locale, or if the last node in the path is + * different from this node + */ + public void getLocalToVworld(SceneGraphPath path, Transform3D t) { + if (!isLive()) + throw new RestrictedAccessException(J3dI18N.getString("Node7")); + + if(!this.getCapability(ALLOW_LOCAL_TO_VWORLD_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Node8")); + + ((NodeRetained)this.retained).getLocalToVworld(path,t); + } + + /** + * Duplicates all the nodes of the specified sub-graph. For Group Nodes + * the group node is duplicated via a call to <code>cloneNode</code> + * and then <code>cloneTree</code> + * is called for each child node. For Leaf Nodes, component + * data can either be duplicated or be made a reference to the original + * data. Leaf Node cloneTree behavior is determined by the + * <code>duplicateOnCloneTree</code> flag found in every Leaf Node's + * component data class and by the <code>forceDuplicate</code> paramter. + * @return a reference to the cloned sub-graph. + * @exception DanglingReferenceException When a dangling reference is + * discovered during the cloneTree operation. + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneTree() { + return cloneTree(new NodeReferenceTable(), false, false); + } + + /** + * Duplicates all the nodes of the specified sub-graph. For Group Nodes + * the group node is duplicated via a call to <code>cloneNode</code> + * and then <code>cloneTree</code> is called for each child node. + * For Leaf Nodes, component + * data can either be duplicated or be made a reference to the original + * data. Leaf Node cloneTree behavior is determined by the + * <code>duplicateOnCloneTree</code> flag found in every Leaf Node's + * component data class and by the <code>forceDuplicate</code> paramter. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> determines whether data is + * duplicated or copied. + * @return a reference to the cloned scene graph. + * @exception DanglingReferenceException When a dangling reference is + * discovered during the cloneTree operation. + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneTree(boolean forceDuplicate) { + return cloneTree(new NodeReferenceTable(), forceDuplicate, false); + } + + /** + * Duplicates all the nodes of the specified sub-graph. For Group Nodes + * the group node is duplicated via a call to <code>cloneNode</code> and + * then <code>cloneTree</code> is called for each child node. For + * Leaf Nodes, component + * data can either be duplicated or be made a reference to the original + * data. Leaf Node cloneTree behavior is determined by the + * <code>duplicateOnCloneTree</code> flag found in every Leaf Node's + * component data class and by the <code>forceDuplicate</code> paramter. + * + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> + * flag to be ignored. When <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> determines whether data is + * duplicated or copied. + * + * @param allowDanglingReferences when set to <code>true</code> allows + * the <code>cloneTree</code> + * method to complete even whan a dangling reference is discovered. When + * this parameter is <code>false</code> a + * <code>DanglingReferenceException</code> is generated as + * soon as cloneTree detects this situation. + * + * @return a reference to the cloned scene graph. + * + * @exception DanglingReferenceException When a dangling reference is + * discovered during the cloneTree operation and the + * <code>allowDanglingReference</code> parameter is </code>false</code>. + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + * + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneTree(boolean forceDuplicate, + boolean allowDanglingReferences) { + return cloneTree(new NodeReferenceTable(), + forceDuplicate, allowDanglingReferences); + } + + + /** + * Duplicates all the nodes of the specified sub-graph. For Group Nodes + * the group node is duplicated via a call to <code>cloneNode</code> + * and then <code>cloneTree</code> + * is called for each child node. For Leaf Nodes, component + * data can either be duplicated or be made a reference to the original + * data. Leaf Node cloneTree behavior is determined by the + * <code>duplicateOnCloneTree</code> flag found in every Leaf Node's + * component data class and by the <code>forceDuplicate</code> paramter. + * @param referenceTable table that stores the mapping between + * original and cloned nodes. All previous values in the + * referenceTable will be cleared before the clone is made. + * @return a reference to the cloned sub-graph. + * @exception DanglingReferenceException When a dangling reference is + * discovered during the cloneTree operation. + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + * @see NodeComponent#setDuplicateOnCloneTree + * @since Java 3D 1.2 + */ + public Node cloneTree(NodeReferenceTable referenceTable) { + return cloneTree(referenceTable, false, false); + } + + + /** + * Duplicates all the nodes of the specified sub-graph. For Group Nodes + * the group node is duplicated via a call to <code>cloneNode</code> + * and then <code>cloneTree</code> is called for each child node. + * For Leaf Nodes, component + * data can either be duplicated or be made a reference to the original + * data. Leaf Node cloneTree behavior is determined by the + * <code>duplicateOnCloneTree</code> flag found in every Leaf Node's + * component data class and by the <code>forceDuplicate</code> paramter. + * @param referenceTable table that stores the mapping between + * original and cloned nodes. All previous values in the + * referenceTable will be cleared before the clone is made. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> determines whether data is + * duplicated or copied. + * @return a reference to the cloned scene graph. + * @exception DanglingReferenceException When a dangling reference is + * discovered during the cloneTree operation. + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + * @see NodeComponent#setDuplicateOnCloneTree + * @since Java 3D 1.2 + */ + public Node cloneTree(NodeReferenceTable referenceTable, + boolean forceDuplicate) { + return cloneTree(referenceTable, forceDuplicate, false); + } + + /** + * Duplicates all the nodes of the specified sub-graph. For Group Nodes + * the group node is duplicated via a call to <code>cloneNode</code> + * and then <code>cloneTree</code> is called for each child node. + * For Leaf Nodes, component + * data can either be duplicated or be made a reference to the original + * data. Leaf Node cloneTree behavior is determined by the + * <code>duplicateOnCloneTree</code> flag found in every Leaf Node's + * component data class and by the <code>forceDuplicate</code> paramter. + * @param referenceTable table that stores the mapping between + * original and cloned nodes. All previous values in the + * referenceTable will be cleared before the clone is made. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> determines whether data is + * duplicated or copied. + * + * @param allowDanglingReferences when set to <code>true</code> allows + * the <code>cloneTree</code> + * method to complete even whan a dangling reference is discovered. When + * this parameter is <code>false</code> a + * <code>DanglingReferenceException</code> is generated as + * soon as cloneTree detects this situation. + * + * @return a reference to the cloned scene graph. + * @exception DanglingReferenceException When a dangling reference is + * discovered during the cloneTree operation. + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + * @see NodeComponent#setDuplicateOnCloneTree + * @since Java 3D 1.2 + */ + public Node cloneTree(NodeReferenceTable referenceTable, + boolean forceDuplicate, + boolean allowDanglingReferences) { + + if (!isLiveOrCompiled()) { + // this will throw a SceneGraphCycleException if there is + // a cycle + checkForCycle(); + } + + referenceTable.set(allowDanglingReferences, new Hashtable()); + Node n = cloneTree(forceDuplicate, referenceTable.objectHashtable); + + // go through hash table looking for Leaf nodes. + // call updateNodeReferences for each. + Enumeration e = referenceTable.objectHashtable.elements(); + + while (e.hasMoreElements()) { + SceneGraphObject o = (SceneGraphObject) e.nextElement(); + o.updateNodeReferences(referenceTable); + } + return n; + } + + /** + * Duplicates all the nodes of the specified sub-graph. For Group Nodes + * the group node is duplicated via a call to <code>cloneNode</code> and + * then <code>cloneTree</code> is called for each child node. For + * Leaf Nodes, component + * data can either be duplicated or be made a reference to the original + * data. Leaf Node cloneTree behavior is determined by the + * <code>duplicateOnCloneTree</code> flag found in every Leaf Node's + * component data class and by the <code>forceDuplicate</code> paramter. + * + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> + * flag to be ignored. When <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> determines whether data is + * duplicated or copied. + * + * @param nodeHashtable a hashtable used to map orignal node references to + * their cloned counterpart. + * + * @return a reference to the cloned scene graph. + * + * @see NodeComponent#setDuplicateOnCloneTree + */ + Node cloneTree(boolean forceDuplicate, Hashtable nodeHashtable) { + Node l; + this.nodeHashtable = nodeHashtable; + try { + l = cloneNode(forceDuplicate); + } catch (RuntimeException e) { + this.nodeHashtable = null; + throw e; + } + // must reset to null so that we can tell whether the call is from user + // or cloneTree + this.nodeHashtable = null; + nodeHashtable.put(this, l); + return l; + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * <code>cloneNode</code> should be overridden by any user subclassed + * objects. All subclasses must have their <code>cloneNode</code> + * method consist of the following lines: + * <P><blockquote><pre> + * public Node cloneNode(boolean forceDuplicate) { + * UserSubClass usc = new UserSubClass(); + * usc.duplicateNode(this, forceDuplicate); + * return usc; + * } + * </pre></blockquote> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + * + * @see Node#cloneTree + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + throw new RuntimeException(J3dI18N.getString("Node12")); + } + + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Group#cloneNode + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, + boolean forceDuplicate) { + duplicateAttributes(originalNode, forceDuplicate); + } + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from subclass of + * <code>duplicateNode</code> method which is, in turn, called by the + * <code>cloneNode</code> method. + * <P> + * For any <i>NodeComponent</i> objects + * contained by the object being duplicated, each <i>NodeComponent</i> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <i>NodeComponent<i> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Group#cloneNode + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + final void checkDuplicateNode(Node originalNode, + boolean forceDuplicate) { + if (originalNode.nodeHashtable != null) { + duplicateAttributes(originalNode, forceDuplicate); + } else { + // user call cloneNode() or duplicateNode() directly + // instead of via cloneTree() + originalNode.nodeHashtable = new Hashtable(); + duplicateAttributes(originalNode, forceDuplicate); + originalNode.nodeHashtable = null; + } + } + + + /** + * Copies all Node information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if originalNode object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + + if (originalNode.isLiveOrCompiled()) { + throw new RestrictedAccessException(J3dI18N.getString("Node13")); + } + super.duplicateSceneGraphObject(originalNode); + NodeRetained attr = (NodeRetained) originalNode.retained; + NodeRetained rt = (NodeRetained) retained; + + rt.setPickable(attr.getPickable()); + rt.setCollidable(attr.getCollidable()); + } + + + /** + * When set to <code>true</code> this <code>Node</code> can be Picked. + * Setting to false indicates that this node and it's children + * are ALL unpickable. + * + * @param pickable Indicates if this node should be pickable or not + */ + public void setPickable( boolean pickable ) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PICKABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Node14")); + + ((NodeRetained)retained).setPickable(pickable); + } + + /** + * Returns true if this <code>Node</code> is pickable, + * false if it is not pickable. + */ + public boolean getPickable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PICKABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Node3")); + + return ((NodeRetained)retained).getPickable(); + } + + /** + * checks for cycles in the scene graph + */ + void checkForCycle() { + if (visited) { + throw new SceneGraphCycleException(J3dI18N.getString("Node15")); + } + visited = true; + Node parent = getParent(); + if (parent != null) { + parent.checkForCycle(); + } + visited = false; + } + +} diff --git a/src/classes/share/javax/media/j3d/NodeComponent.java b/src/classes/share/javax/media/j3d/NodeComponent.java new file mode 100644 index 0000000..bd3779c --- /dev/null +++ b/src/classes/share/javax/media/j3d/NodeComponent.java @@ -0,0 +1,264 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.Hashtable; + +/** + * NodeComponent is a common superclass for all scene graph node + * component objects such as: Geometry, Appearance, Material, Texture, etc. + */ +public abstract class NodeComponent extends SceneGraphObject { + + // This is use for cloneTree only, set to false after the operation + boolean forceDuplicate = false; + /** + * Constructs a NodeComponent object with default parameters. + * The default values are as follows: + * <ul> + * duplicate on clone tree : false<br> + * </ul> + */ + public NodeComponent() { + } + + /** + * Sets this node's duplicateOnCloneTree value. The + * <i>duplicateOnCloneTree</i> value is used to determine if NodeComponent + * objects are to be duplicated or referenced during a + * <code>cloneTree</code> operation. A value of <code>true</code> means + * that this NodeComponent object should be duplicated, while a value + * of <code>false</code> indicates that this NodeComponent object's + * reference will be copied into the newly cloned object. This value + * can be overriden via the <code>forceDuplicate</code> parameter of + * the <code>cloneTree</code> method. + * @param duplicate the value to set. + * @see Node#cloneTree + */ + public void setDuplicateOnCloneTree(boolean duplicate) { + ((NodeComponentRetained)retained).setDuplicateOnCloneTree(duplicate); + } + + /** + * Returns this node's duplicateOnCloneTree value. The + * <i>duplicateOnCloneTree</i> value is used to determine if NodeComponent + * objects are to be duplicated or referenced during a + * <code>cloneTree</code> operation. A value of <code>true</code> means + * that this NodeComponent object should be duplicated, while a value + * of <code>false</code> indicates that this NodeComponent object's + * reference will be copied into the newly cloned object. This value + * can be overriden via the <code>forceDuplicate</code> parameter of + * the <code>cloneTree</code> method. + * @return the value of this node's duplicateOnCloneTree + * @see Node#cloneTree + */ + public boolean getDuplicateOnCloneTree() { + return ((NodeComponentRetained)retained).getDuplicateOnCloneTree(); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>cloneNodeComponent(boolean forceDuplicate)</code> + */ + public NodeComponent cloneNodeComponent() { + throw new RuntimeException(J3dI18N.getString("NodeComponent0")); + } + + + /** + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @deprecated As of Java 3D version 1.2, replaced by + * <code>duplicateNodeComponent(NodeComponent + * originalNodeComponent, boolean forceDuplicate)</code> + */ + public void duplicateNodeComponent(NodeComponent originalNodeComponent) { + duplicateAttributes(originalNodeComponent, + originalNodeComponent.forceDuplicate); + } + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node component. This method is called from subclass of + * <code>duplicateNodeComponent</code> method which is, in turn, called by the + * <code>cloneNodeComponent</code> method. + * + * For any <i>NodeComponent</i> objects + * contained by the object being duplicated, each <i>NodeComponent</i> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <i>NodeComponent<i> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * + * @param originalNodeComponent the original node component to duplicate. + */ + final void checkDuplicateNodeComponent( + NodeComponent originalNodeComponent) { + + if (originalNodeComponent.nodeHashtable != null) { + duplicateAttributes(originalNodeComponent, + originalNodeComponent.forceDuplicate); + } else { + // user call cloneNodeComponent() or duplicateNodeComponent() + // directly instead of via cloneTree() + originalNodeComponent.nodeHashtable = new Hashtable(); + duplicateAttributes(originalNodeComponent, + originalNodeComponent.forceDuplicate); + originalNodeComponent.nodeHashtable = null; + } + } + + /** + * Copies all node information from <code>originalNodeComponent</code> + * into the current node. This method is called from the + * <code>cloneNodeComponent</code> method which is, in turn, called + * by the <code>cloneNode</code> method. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNodeComponent the node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if forceDuplicate is set and + * this object is part of a compiled scenegraph + * + * @see NodeComponent#cloneNodeComponent + * @see Node#cloneNode + * @see Node#cloneTree + * + * @since Java 3D 1.2 + */ + public void duplicateNodeComponent(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + originalNodeComponent.forceDuplicate = forceDuplicate; + try { + duplicateNodeComponent(originalNodeComponent); + } catch (RuntimeException e) { + originalNodeComponent.forceDuplicate = false; + throw e; + } + originalNodeComponent.forceDuplicate = false; + } + + /** + * Used to create a new instance of a NodeComponent object. This + * routine is called by <code>cloneNode</code> to duplicate the + * current node. <br> + * + * <code>cloneNodeComponent</code> should be overridden by any user + * subclassed <i>NodeComponent</i> objects. All subclasses must have their + * <code>cloneNodeComponent</code> + * method consist of the following lines: + * <P><blockquote><pre> + * public NodeComponent cloneNodeComponent(boolean forceDuplicate) { + * UserNodeComponent unc = new UserNodeComponent(); + * unc.duplicateNodeComponent(this, forceDuplicate); + * return unc; + * } + * </pre></blockquote> + * + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if forceDuplicate is set and + * this object is part of a compiled scenegraph + * + * @see NodeComponent#duplicateNodeComponent + * @see Node#cloneNode + * @see Node#cloneTree + * + * @since Java 3D 1.2 + */ + public NodeComponent cloneNodeComponent(boolean forceDuplicate) { + // For backward compatibility ! + // + // If user did not overwrite this procedure, it will fall back + // to call cloneNodeComponent() + // So for core API, + // don't implement cloneNodeComponent(boolean forceDuplicate) + // otherwise this prcedure will not call and the user + // cloneNodeComponent() will not invoke. + NodeComponent nc; + this.forceDuplicate = forceDuplicate; + try { + nc = cloneNodeComponent(); + } catch (RuntimeException e) { + this.forceDuplicate = false; + throw e; + } + this.forceDuplicate = false; + return nc; + } + + + /** + * Copies all NodeComponent information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Group#cloneNode + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNode, + boolean forceDuplicate) { + + if (forceDuplicate && originalNode.isCompiled()) { + throw new RestrictedAccessException( + J3dI18N.getString("NodeComponent1")); + } + + super.duplicateSceneGraphObject(originalNode); + setDuplicateOnCloneTree(originalNode.getDuplicateOnCloneTree()); + } + + /** + * Creates the retained mode NodeComponentRetained object that this + * NodeComponent object will point to. + */ + void createRetained() { + this.retained = new NodeComponentRetained(); + this.retained.setSource(this); + } + + /** + * This function is called from getNodeComponent() to see if any of + * the sub-NodeComponents duplicateOnCloneTree flag is true. + * If it is the case, current NodeComponent needs to + * duplicate also even though current duplicateOnCloneTree flag is false. + * This should be overwrite by NodeComponent which contains sub-NodeComponent. + */ + boolean duplicateChild() { + return getDuplicateOnCloneTree(); + } +} diff --git a/src/classes/share/javax/media/j3d/NodeComponentRetained.java b/src/classes/share/javax/media/j3d/NodeComponentRetained.java new file mode 100644 index 0000000..a7e6b8a --- /dev/null +++ b/src/classes/share/javax/media/j3d/NodeComponentRetained.java @@ -0,0 +1,241 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * Retained version of NodeComponent + */ + +class NodeComponentRetained extends SceneGraphObjectRetained { + + // duplicate or make a reference when cloneTree() is called + // on this object. + boolean duplicateOnCloneTree = false; + + // This keeps track of how many times this NodeComponent is referenced in + // the Scene Graph + int refCount = 0; // this is used in setLive + int refCnt = 0; // this is used in compile + + // This is true when this appearance is referenced in an immediate mode context + boolean inImmCtx = false; + + // A list of NodeRetained Objects that refer, directly or indirectly, to this + // NodeComponentRetained + ArrayList users = new ArrayList(1); + + // Mirror object of this node compoenent object + NodeComponentRetained mirror = null; + + // Sole User FrequencyBit + // In the case of Appearance, its a bitmask of all components + int changedFrequent = 0; + int compChanged = 0; + + // Increment the refcount. If this is the first, mark it as live. + void doSetLive(boolean inBackgroundGroup, int refCount) { + int oldRefCount = this.refCount; + this.refCount += refCount; + if (oldRefCount <= 0) { + super.doSetLive(inBackgroundGroup); + + // Create and init a mirror object if not already there + // The two procedures is combined since it is redunctant to + // call initMirrorObject() if mirror == this (static object). + createMirrorObject(); + } + } + + void setLive(boolean inBackgroundGroup, int refCount) { + int oldRefCount = this.refCount; + doSetLive(inBackgroundGroup, refCount); + if (oldRefCount <= 0) { + super.markAsLive(); + } + } + + + + // Decrement the refcount. If this is the last, mark it as not live. + void clearLive(int refCount) { + this.refCount -= refCount; + + if (this.refCount <= 0) { + super.clearLive(); + } + } + + // increment the compile reference count + synchronized void incRefCnt() { + refCnt++; + } + + // decrement the compile reference count + synchronized void decRefCnt() { + refCnt--; + } + + // remove mirror shape from the list of users + void removeAMirrorUser(Shape3DRetained ms) { + synchronized(mirror.users) { + mirror.users.remove(ms); + } + } + + // Add a mirror shape to the list of users + void addAMirrorUser(Shape3DRetained ms) { + synchronized(mirror.users) { + mirror.users.add(ms); + } + } + + // Copy the list of useres passed in into this + void copyMirrorUsers(NodeComponentRetained node) { + synchronized(mirror.users) { + synchronized(node.mirror.users) { + int size = node.mirror.users.size(); + for (int i=0; i<size ; i++) { + mirror.users.add(node.mirror.users.get(i)); + } + } + } + } + + + // Remove the users of "node" from "this" node compoenent + void removeMirrorUsers(NodeComponentRetained node) { + + synchronized(mirror.users) { + synchronized(node.mirror.users) { + for (int i=node.mirror.users.size()-1; i>=0; i--) { + mirror.users.remove(mirror.users.indexOf(node.mirror.users.get(i))); + } + } + } + } + + // Add a user to the list of users + synchronized void removeUser(NodeRetained node) { + if (node.source.isLive()) + users.remove(users.indexOf(node)); + } + + // Add a user to the list of users + synchronized void addUser(NodeRetained node) { + if (node.source.isLive()) + users.add(node); + } + + + // Add a user to the list of users + synchronized void notifyUsers() { + + if (source == null || !source.isLive()) { + return; + } + + for (int i=users.size()-1; i >=0; i--) { + ((NodeRetained)users.get(i)).notifySceneGraphChanged(false); + } + } + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return (inImmCtx); + } + + /** + * Sets this node's duplicateOnCloneTree value. The + * <i>duplicateOnCloneTree</i> value is used to determine if NodeComponent + * objects are to be duplicated or referenced during a + * <code>cloneTree</code> operation. A value of <code>true</code> means + * that this NodeComponent object should be duplicated, while a value + * of <code>false</code> indicates that this NodeComponent object's + * reference will be copied into the newly cloned object. This value + * can be overriden via the <code>forceDuplicate</code> parameter of + * the <code>cloneTree</code> method. + * @param duplicate the value to set. + * @see Node#cloneTree + */ + void setDuplicateOnCloneTree(boolean duplicate) { + duplicateOnCloneTree = duplicate; + } + + /** + * Returns this node's duplicateOnCloneTree value. The + * <i>duplicateOnCloneTree</i> value is used to determine if NodeComponent + * objects are to be duplicated or referenced during a + * <code>cloneTree</code> operation. A value of <code>true</code> means + * that this NodeComponent object should be duplicated, while a value + * of <code>false</code> indicates that this NodeComponent object's + * reference will be copied into the newly cloned object. This value + * can be overriden via the <code>forceDuplicate</code> parameter of + * the <code>cloneTree</code> method. + * @return the value of this node's duplicateOnCloneTree + * @see Node#cloneTree + */ + boolean getDuplicateOnCloneTree() { + return duplicateOnCloneTree; + } + + + void initMirrorObject() { + } + + void updateMirrorObject(int component, Object obj) { + } + + void createMirrorObject() { + // Overridden by appearance and other classes + initMirrorObject(); + mirror = null; + } + + // Evaluate state based on the following extensions + void evaluateExtensions(int extensions) { + } + + + + + void setFrequencyChangeMask(int bit, int mask) { + // Record only the inf->frequent change + if (source.getCapabilityIsFrequent(bit)) + changedFrequent |= mask; + else if (!source.isLive()) { + changedFrequent &= ~mask; + } + + } + + protected Object clone() { + NodeComponentRetained ncr = (NodeComponentRetained)super.clone(); + ncr.changedFrequent = changedFrequent; + return ncr; + } + + protected void set(NodeComponentRetained nc) { + changedFrequent = nc.changedFrequent; + } +} diff --git a/src/classes/share/javax/media/j3d/NodeComponentUpdate.java b/src/classes/share/javax/media/j3d/NodeComponentUpdate.java new file mode 100644 index 0000000..b0aba77 --- /dev/null +++ b/src/classes/share/javax/media/j3d/NodeComponentUpdate.java @@ -0,0 +1,26 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A Node Component Update interface. Any object that can be put in the + * node component updateCheck list must implement this interface. + */ + +interface NodeComponentUpdate { + + /** + * The actual update function. + */ + abstract void updateNodeComponentCheck(); +} diff --git a/src/classes/share/javax/media/j3d/NodeData.java b/src/classes/share/javax/media/j3d/NodeData.java new file mode 100644 index 0000000..930d7a3 --- /dev/null +++ b/src/classes/share/javax/media/j3d/NodeData.java @@ -0,0 +1,21 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +class NodeData { + // per path node data + // TODO: replace per path mirror objects with node data + // TODO: move other basic node's data here + SwitchState switchState = null; +} diff --git a/src/classes/share/javax/media/j3d/NodeReferenceTable.java b/src/classes/share/javax/media/j3d/NodeReferenceTable.java new file mode 100644 index 0000000..b425f2e --- /dev/null +++ b/src/classes/share/javax/media/j3d/NodeReferenceTable.java @@ -0,0 +1,121 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Hashtable; + +/** + * The NodeReferenceTable object is used by a leaf node's + * <code>updateNodeReferences</code> method called by the + * <code>cloneTree</code> method. + * The NodeReferenceTable maps nodes from the original subgraph + * to the new nodes in the cloned subgraph. This information + * can then be used to update any cloned leaf node references + * to reference nodes in the cloned subgraph. + * <P> + * During a <code>cloneTree</code> call, after all nodes have been duplicated, + * each SceneGraphObject's <code>updateNodeReferences</code> method is called. + * This method takes a NodeReferenceTable object as a parameter. The + * SceneGraphObject's <code>updateNodeReferences</code> method can then use the + * <code>getNewObjectReference</code> method from this object to get updated + * references to objects that have been duplicated in the new cloneTree + * sub-graph. If a match is found, a + * reference to the corresponding SceneGraphObject in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown by the <code>cloneTree</code> + * method or a reference to the original + * SceneGraphObject is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * @see SceneGraphObject#updateNodeReferences + * @see Node#cloneTree + * @see DanglingReferenceException + */ +public class NodeReferenceTable extends Object { + + Hashtable objectHashtable; + boolean allowDanglingReferences; + + /** + * Constructs an empty NodeReferenceTable. + * @since Java 3D 1.2 + */ + public NodeReferenceTable() { + } + + // Constructor - this is NOT public! + NodeReferenceTable(boolean allowDanglingReferences, + Hashtable objectHashtable) { + set(allowDanglingReferences, objectHashtable); + } + + void set(boolean allowDanglingReferences, + Hashtable objectHashtable) { + this.objectHashtable = objectHashtable; + this.allowDanglingReferences = allowDanglingReferences; + } + + + /** + * This method is used in conjunction with the <code>cloneTree</code> + * method. It can be used by the <code>updateNodeReferences</code> + * method to see if a SceneGraphObject that is being referenced has been duplicated + * in the new cloneTree sub-graph. + * <P> + * A SceneGraphObject's <code>updateNodeReferences</code> method would use this + * method by calling it with the reference to the old (existed before + * the cloneTree operation) object. If the object has been duplicated + * in the cloneTree sub-graph, the corresponding object in the cloned + * sub-graph is returned. If no corresponding reference is found, either + * a DanglingReferenceException is thrown or a reference to the original + * SceneGraphObject is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * + * @param oldReference the reference to the object in + * the original sub-graph. + * + * @return A reference to the corresponding object in the cloned + * sub-graph. If no corresponding object exists, either a + * DanglingReferenceException will be generated by the + * <code>cloneTree</code> method or a reference to the original object + * is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * + * @see SceneGraphObject#updateNodeReferences + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public final SceneGraphObject + getNewObjectReference(SceneGraphObject oldReference) { + + // look up original SceneGraphObject in hash table + SceneGraphObject newObject = + (SceneGraphObject)objectHashtable.get(oldReference); + + if (newObject != null) { + // found new reference + return newObject; + } + + // dangling reference found! + if (allowDanglingReferences == true) { + return oldReference; + } + + // dangling references not allowed + throw new DanglingReferenceException(); + } + +} diff --git a/src/classes/share/javax/media/j3d/NodeRetained.java b/src/classes/share/javax/media/j3d/NodeRetained.java new file mode 100644 index 0000000..3ddf7eb --- /dev/null +++ b/src/classes/share/javax/media/j3d/NodeRetained.java @@ -0,0 +1,914 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +import java.util.Vector; +import java.util.Hashtable; +import java.util.ArrayList; + +/** + * The Node class provides an abstract class for all Group and Leaf + * Nodes. It provides a common framework for constructing a Java 3D + * scene graph, including bounding volumes and parent pointers. + */ +abstract class NodeRetained extends SceneGraphObjectRetained implements NnuId { + + // All the node types in the scene graph + static final int BACKGROUND = 1; + static final int CLIP = 2; + static final int LINEARFOG = 3; + static final int EXPONENTIALFOG = 4; + static final int AMBIENTLIGHT = 5; + static final int DIRECTIONALLIGHT = 6; + static final int POINTLIGHT = 7; + static final int SPOTLIGHT = 8; + static final int LINK = 9; + static final int MORPH = 10; + static final int SHAPE = 11; + static final int BACKGROUNDSOUND = 12; + static final int POINTSOUND = 13; + static final int CONESOUND = 14; + static final int SOUNDSCAPE = 15; + static final int VIEWPLATFORM = 16; + static final int BEHAVIOR = 17; + + static final int SWITCH = 18; + static final int BRANCHGROUP = 19; + static final int ORDEREDGROUP = 20; + static final int DECALGROUP = 21; + static final int SHAREDGROUP = 22; + static final int GROUP = 23; + static final int TRANSFORMGROUP = 24; + static final int BOUNDINGLEAF = 25; + static final int MODELCLIP = 26; + static final int ALTERNATEAPPEARANCE= 27; + static final int ORIENTEDSHAPE3D = 28; + static final int VIEWSPECIFICGROUP = 29; + static final int NUMNODES = 29; + + // traverse flags + static final int CONTAINS_VIEWPLATFORM = 0x1; + + + /** + * The universe that we are in + */ + VirtualUniverse universe = null; + + /** + * The locale that this node is attatched to. This is only non-null + * if this instance is directly linked into a locale. + */ + Locale locale = null; + + /** + * The node's parent. + */ + NodeRetained parent = null; + + /** + * The node's internal identifier. + */ + String nodeId = null; + + /** + * An int that represents the nodes type. Used for quick if tests + * in the traverser. + */ + int nodeType; + + // This keeps track of how many times this Node is refernced, refCount > 1 + // if node is in a shared group + int refCount = 0; + + /** + * This is the index for the child, as seen by its parent. + */ + int childIndex = -1; + + /** + * This boolean is true when the node is in a sharedGroup + */ + boolean inSharedGroup = false; + + /** + * This indicates if the node is pickable. If this node is not + * pickable then neither are any children + */ + boolean pickable = true; + + /** + * The collidable setting; see getCollidable and setCollidable. + */ + boolean collidable = true; + + // A list of localToVworld transforms. If inSharedGroup is false, + // then only localToVworld[0][] is valid. + // Note: this contains reference to the actual transforms in the + // TransformGroupRetained + Transform3D localToVworld[][] = null; + int localToVworldIndex[][] = null; + + static final int LAST_LOCAL_TO_VWORLD = 0; + static final int CURRENT_LOCAL_TO_VWORLD = 1; + + // A parallel array to localToVworld. This is the keys for + // localToVworld transforms in shared groups. + HashKey localToVworldKeys[] = null; + + /** + * This boolean is true when the geometric bounds for the node is + * automatically updated + */ + boolean boundsAutoCompute = true; + + // "effective" bounds in local coordinate if boundsAutoCompute == F, + // used for internal operations, not used if boundsAutoCompute == T + Bounds localBounds; + + // Bounds set by the API + Bounds apiBounds; + + /** + * Each element, p, of branchGroupPaths is a list of BranchGroup from + * root of the tree to this. + * For BranchGroup under a non-shared group this size of + * branchGroupPaths is always 1. Otherwise, the size is equal to + * the number of possible paths to reach this node. + * This variable is used to cached BranchGroup for fast picking. + * For non BranchGroupRetained class this is a reference to + * the previous BranchGroupRetained branchGroupPaths. + */ + ArrayList branchGroupPaths = new ArrayList(1); + + // background node whose geometry branch contains this node + BackgroundRetained geometryBackground = null; + + // closest parent which is a TransformGroupRetained or sharedGroupRetained + GroupRetained parentTransformLink = null; + + // closest parent which is a SwitchRetained or sharedGroupRetained + GroupRetained parentSwitchLink = null; + + // static transform if a parent transform group is merged during compile. + TransformGroupRetained staticTransform = null; + + // orderedId assigned by OrderedGroup parent + Integer orderedId = null; + + // Id use for quick search. + int nnuId; + + NodeRetained() { + // Get a not necessary unique Id. + nnuId = NnuIdManager.getId(); + + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setUpper(-1.0, -1.0, -1.0); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + } + + + public int getId() { + return nnuId; + } + + public int equal(NnuId obj) { + int keyId = obj.getId(); + if(nnuId < keyId) { + return -1; + } + else if(nnuId > keyId) { + return 1; + } + else { // Found it! + return 0; + } + } + + Bounds getLocalBounds(Bounds bounds) { + return (Bounds)bounds.clone(); + } + + /** + * Sets the geometric bounds of a node. + * @param bounds the bounding object for the node + */ + void setBounds(Bounds bounds) { + apiBounds = bounds; + if (source.isLive()) { + if (!boundsAutoCompute) { + if (bounds != null) { + localBounds = getLocalBounds(bounds); + if (staticTransform != null) { + localBounds.transform(staticTransform.transform); + } + } else { + if(localBounds != null) { + localBounds.set((Bounds)null); + } + else { + localBounds = new BoundingBox((Bounds)null); + } + } + } + } else { + if (bounds != null) { + localBounds = getLocalBounds(bounds); + if (staticTransform != null) { + localBounds.transform(staticTransform.transform); + } + } else { + if(localBounds != null) { + localBounds.set((Bounds)null); + } + else { + localBounds = new BoundingBox((Bounds)null); + } + } + } + } + + /** + * Gets the bounding object of a node. + * @return the node's bounding object + */ + Bounds getEffectiveBounds() { + Bounds b = null; + if (localBounds != null && !localBounds.isEmpty()) { + b = (Bounds) localBounds.clone(); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + b.transform(invTransform); + } + } + return b; + } + + Bounds getBounds() { + return apiBounds; + } + + /** + * ONLY needed for SHAPE, MORPH, and LINK node type. + * Compute the combine bounds of bounds and its localBounds. + */ + void computeCombineBounds(Bounds bounds) { + // Do nothing except for Group, Shape3D, Morph, and Link node. + } + + + /** + * Sets the automatic calcuation of geometric bounds of a node. + * @param autoCompute is a boolean value indicating if automatic calcuation + * of bounds + */ + void setBoundsAutoCompute(boolean autoCompute) { + this.boundsAutoCompute = autoCompute; + } + + /** + * Gets the auto Compute flag for the geometric bounds. + * @return the node's auto Compute flag for the geometric bounding object + */ + boolean getBoundsAutoCompute() { + return boundsAutoCompute; + } + + /** + * Replaces the specified parent by a new parent. + * @param parent the new parent + */ + void setParent(NodeRetained parent) { + this.parent = parent; + } + + /** + * Returns the parent of the node. + * @return the parent. + */ + NodeRetained getParent() { + return (NodeRetained)parent; + } + + // Transform the input bound by the current LocalToVWorld + void transformBounds(SceneGraphPath path, Bounds bound) { + if (!((NodeRetained) path.item.retained).inSharedGroup) { + bound.transform(getCurrentLocalToVworld()); + } else { + HashKey key = new HashKey(""); + path.getHashKey(key); + bound.transform(getCurrentLocalToVworld(key)); + } + } + + + // Note : key will get modified in this method. + private void computeLocalToVworld( NodeRetained caller, NodeRetained nodeR, + HashKey key, Transform3D l2Vw) { + int i; + + // To handle localToVworld under a SG. + if(nodeR instanceof SharedGroupRetained) { + // Get the immediate parent's id and remove last id from key. + String nodeId = key.getLastNodeId(); + + SharedGroupRetained sgRetained = (SharedGroupRetained) nodeR; + + // Search for the right parent. + for(i=0; i<sgRetained.parents.size(); i++) { + + if(nodeId.equals((String)(((NodeRetained) + (sgRetained.parents.elementAt(i))).nodeId))) { + // Found the right link. Now traverse upward. + + computeLocalToVworld(caller, + (NodeRetained)(sgRetained.parents.elementAt(i)), + key, l2Vw); + return; + } + } + // Problem ! + throw new RuntimeException(J3dI18N.getString("NodeRetained4")); + } + else { + + NodeRetained nodeParentR =(NodeRetained)nodeR.getParent(); + + if(nodeParentR == null) { + // Base case. It has to be a BG attached to a locale. + if(((BranchGroupRetained)(nodeR)).locale != null) { + l2Vw.setIdentity(); + } + else { + throw new RuntimeException(J3dI18N.getString("NodeRetained5")); + } + } + else { + computeLocalToVworld(caller, (NodeRetained)nodeParentR, key, l2Vw); + + } + + } + + if((nodeR instanceof TransformGroupRetained) && (nodeR != caller)) { + Transform3D t1 = VirtualUniverse.mc.getTransform3D(null); + ((TransformGroupRetained)(nodeR)).transform.getWithLock(t1); + l2Vw.mul(t1); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t1); + } else if ((nodeR == caller) && (staticTransform != null)) { + l2Vw.mul(staticTransform.transform); + } + + return; + } + + + /** + * Get the localToVworld transform for a node. + */ + void getLocalToVworld(Transform3D t) { + if (inSharedGroup) { + throw new IllegalSharingException(J3dI18N.getString("NodeRetained0")); + } + + // Lock the object while writing into t. + if (localToVworld == null) { + t.setIdentity(); + } else { + computeLocalToVworld(this, this, null, t); + } + } + + + /** + * Get the localToVworld transform for a node. + */ + void getLocalToVworld(SceneGraphPath path, Transform3D t) { + HashKey key = new HashKey(""); + + if (inSharedGroup == false) { + throw new IllegalSharingException(J3dI18N.getString("NodeRetained1")); + } + path.validate(key); + computeLocalToVworld(this, this, key, t); + + } + + /** + * Get the localToVworld transform for a node + */ + void getLocalToVworld(Transform3D t, HashKey key) { + HashKey newKey = new HashKey(key); + computeLocalToVworld(this, this, newKey, t); + } + + + /** + * Get the current localToVworld transform for a node + */ + Transform3D getCurrentLocalToVworld() { + + if (localToVworld != null) { + return localToVworld[0][localToVworldIndex[0][CURRENT_LOCAL_TO_VWORLD]]; + } else { + return new Transform3D(); + } + } + + // this method expects that localToVworld is not null + + Transform3D getCurrentLocalToVworld(int index) { + return localToVworld[index][localToVworldIndex[index][CURRENT_LOCAL_TO_VWORLD]]; + } + + Transform3D getCurrentLocalToVworld(HashKey key) { + + if (localToVworld != null) { + if (!inSharedGroup) { + return localToVworld[0][localToVworldIndex[0][CURRENT_LOCAL_TO_VWORLD]]; + } else { + int i = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + if(i>= 0) { + return localToVworld[i][localToVworldIndex[i][CURRENT_LOCAL_TO_VWORLD]]; + } + } + } + return new Transform3D(); + } + + /** + * Get the last localToVworld transform for a node + */ + Transform3D getLastLocalToVworld() { + + if (localToVworld != null) { + return localToVworld[0][localToVworldIndex[0][LAST_LOCAL_TO_VWORLD]]; + } else { + return new Transform3D(); + } + } + + Transform3D getLastLocalToVworld(int index) { + return localToVworld[index][localToVworldIndex[index][LAST_LOCAL_TO_VWORLD]]; + } + + Transform3D getLastLocalToVworld(HashKey key) { + + if (localToVworld != null) { + if (!inSharedGroup) { + return localToVworld[0][localToVworldIndex[0][LAST_LOCAL_TO_VWORLD]]; + } else { + int i = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + if(i>= 0) { + return localToVworld[i][localToVworldIndex[i][LAST_LOCAL_TO_VWORLD]]; + } + } + } + return new Transform3D(); + } + + // Do nothing for NodeRetained. + void setAuxData(SetLiveState s, int index, int hkIndex) { + + } + + void setNodeData(SetLiveState s) { + localToVworld = s.localToVworld; + localToVworldIndex = s.localToVworldIndex; + localToVworldKeys = s.localToVworldKeys; + + // reference to the last branchGroupPaths + branchGroupPaths = s.parentBranchGroupPaths; + + parentTransformLink = s.parentTransformLink; + parentSwitchLink = s.parentSwitchLink; + } + + + // set pickable, recursively update cache result + void setPickable(boolean pickable) { + if (this.pickable == pickable) + return; + + this.pickable = pickable; + + if (source.isLive()) { + synchronized(universe.sceneGraphLock) { + boolean pick[]; + if (!inSharedGroup) { + pick = new boolean[1]; + } else { + pick = new boolean[localToVworldKeys.length]; + } + + findPickableFlags(pick); + updatePickable(localToVworldKeys, pick); + } + } + } + + void updatePickable(HashKey pickKeys[], boolean pick[]) { + for (int i=0; i < pick.length; i++) { + if (!pickable) { + pick[i] = false; + } + } + } + + // get pickable + boolean getPickable() { + return pickable; + } + + + // set collidable, recursively update cache result + void setCollidable(boolean collidable) { + if (this.collidable == collidable) + return; + + this.collidable = collidable; + + if (source.isLive()) { + synchronized(universe.sceneGraphLock) { + boolean collide[]; + if (!inSharedGroup) { + collide = new boolean[1]; + } else { + collide = new boolean[localToVworldKeys.length]; + } + + findCollidableFlags(collide); + updateCollidable(localToVworldKeys, collide); + } + } + } + + + // get collidable + boolean getCollidable() { + return collidable; + } + + + void updateCollidable(HashKey keys[], boolean collide[]) { + for (int i=0; i < collide.length; i++) { + if (!collidable) { + collide[i] = false; + } + } + } + + /** + * For the default, just pass up to parent + */ + void notifySceneGraphChanged(boolean globalTraverse){} + + void recombineAbove() {} + + synchronized void updateLocalToVworld() {} + + + void setLive(SetLiveState s) { + int oldrefCount = refCount; + + doSetLive(s); + if (oldrefCount <= 0) + super.markAsLive(); + } + + // The default set of setLive actions. + void doSetLive(SetLiveState s) { + int i; + int oldrefCount = refCount; + + refCount += s.refCount; + if(!(locale == null || universe == s.universe)) + throw new IllegalSharingException(J3dI18N.getString("NodeRetained3")); + if(s.locale == null) + System.out.println("NodeRetained.setLive() locale is null"); + + + locale = s.locale; + inSharedGroup = s.inSharedGroup; + + if (oldrefCount <= 0) { + if (listIdx == null) { + universe = s.universe; + } else { + // sync with getIdxUsed() + if (s.universe != universe) { + synchronized (this) { + universe = s.universe; + incIdxUsed(); + } + } + } + } + s.universe.numNodes++; + + // pickable & collidable array have the same length + for (i=0; i < s.pickable.length; i++) { + if (!pickable) { + s.pickable[i] = false; + } + if (!collidable) { + s.collidable[i] = false; + } + } + + + if (oldrefCount <= 0) + super.doSetLive(s); + + if (inBackgroundGroup) { + geometryBackground = s.geometryBackground; + } + + setNodeData(s); + } + + + /** + * remove the localToVworld transform for this node. + */ + void removeNodeData(SetLiveState s) { + + if (refCount <= 0) { + localToVworld = null; + localToVworldIndex = null; + localToVworldKeys = null; + // restore to default and avoid calling clear() + // that may clear parent reference branchGroupPaths + branchGroupPaths = new ArrayList(1); + parentTransformLink = null; + parentSwitchLink = null; + } + else { + // Set it back to its parent localToVworld data. This is b/c the parent has + // changed it localToVworld data arrays. + localToVworld = s.localToVworld; + localToVworldIndex = s.localToVworldIndex; + localToVworldKeys = s.localToVworldKeys; + + // Reference of parent branchGroupPaths will not change + + // no need to reset parentSwitchLink or parentTransformLink + // because there are not per path data + } + + } + + // The default set of clearLive actions + void clearLive(SetLiveState s) { + + refCount-=s.refCount; + + if (refCount <= 0) { + super.clearLive(); + + // don't remove the nodeId unless there are no more references + if (nodeId != null) { + universe.nodeIdFreeList.addElement(nodeId); + nodeId = null; + } + } + + universe.numNodes--; + + + removeNodeData(s); + + if(refCount <= 0) { + locale = null; + geometryBackground = null; + } + } + + // search up the parent to determine if this node is pickable + void findPickableFlags(boolean pick[]) { + NodeRetained nodeR = this; + + + if (!inSharedGroup) { + pick[0] = true; + nodeR = nodeR.parent; + while (nodeR != null) { + if (!nodeR.pickable) { + pick[0] = false; + break; + } + nodeR = nodeR.parent; + } + } else { + HashKey key; + for (int i=0; i < pick.length; i++) { + nodeR = this; + pick[i] = true; + key = new HashKey(localToVworldKeys[i]); + + do { + if (nodeR instanceof SharedGroupRetained) { + String nodeId = key.getLastNodeId(); + Vector parents = ((SharedGroupRetained) nodeR).parents; + int sz = parents.size(); + NodeRetained prevNodeR = nodeR; + for(int j=0; j< sz; j++) { + NodeRetained linkR = (NodeRetained) parents.elementAt(j); + if (linkR.nodeId.equals(nodeId)) { + nodeR = linkR; + break; + } + } + if (prevNodeR == nodeR) { + // branch is already detach + return; + } + } else { + nodeR = nodeR.parent; + } + if (nodeR == null) + break; + if (!nodeR.pickable) { + pick[i] = false; + break; + } + } while (true); + } + } + } + + + // search up the parent to determine if this node is collidable + void findCollidableFlags(boolean collide[]) { + NodeRetained nodeR = this; + + if (!inSharedGroup) { + collide[0] = true; + nodeR = nodeR.parent; + while (nodeR != null) { + if (!nodeR.collidable) { + collide[0] = false; + break; + } + nodeR = nodeR.parent; + } + } else { + HashKey key; + for (int i=0; i < collide.length; i++) { + nodeR = this; + collide[i] = true; + key = new HashKey(localToVworldKeys[i]); + + do { + if (nodeR instanceof SharedGroupRetained) { + String nodeId = key.getLastNodeId(); + Vector parents = ((SharedGroupRetained) nodeR).parents; + int sz = parents.size(); + NodeRetained prevNodeR = nodeR; + for(int j=0; j< sz; j++) { + NodeRetained linkR = (NodeRetained) parents.elementAt(j); + if (linkR.nodeId.equals(nodeId)) { + nodeR = linkR; + break; + } + } + if (nodeR == prevNodeR) { + return; + } + } else { + nodeR = nodeR.parent; + } + if (nodeR == null) + break; + if (!nodeR.collidable) { + collide[i] = false; + break; + } + } while (true); + } + } + } + + void findTransformLevels(int transformLevels[]) { + NodeRetained nodeR = this; + TransformGroupRetained tg; + + if (!inSharedGroup) { + transformLevels[0] = -1; + while (nodeR != null) { + if (nodeR.nodeType == NodeRetained.TRANSFORMGROUP) { + tg = (TransformGroupRetained)nodeR; + transformLevels[0] = tg.transformLevels[0]; + break; + } + nodeR = nodeR.parent; + } + } else { + HashKey key; + int i,j; + for (i=0; i < transformLevels.length; i++) { + nodeR = this; + transformLevels[i] = -1; + key = new HashKey(localToVworldKeys[i]); + + do { + if (nodeR == null) + break; + else if (nodeR instanceof SharedGroupRetained) { + // note that key is truncated after getLastNodeId + String nodeId = key.getLastNodeId(); + Vector parents = ((SharedGroupRetained) nodeR).parents; + int sz = parents.size(); + NodeRetained prevNodeR = nodeR; + for (j=0; j< sz; j++) { + NodeRetained linkR = + (NodeRetained) parents.elementAt(j); + if (linkR.nodeId.equals(nodeId)) { + nodeR = linkR; + break; + } + } + if (prevNodeR == nodeR) { + // branch is already detach + return; + } + } + else if (nodeR.nodeType == NodeRetained.TRANSFORMGROUP) { + tg = (TransformGroupRetained)nodeR; + if (tg.inSharedGroup) { + + j = key.equals(tg.localToVworldKeys, 0, + tg.localToVworldKeys.length); + + transformLevels[i] = tg.transformLevels[j]; + } else { + transformLevels[i] = tg.transformLevels[0]; + } + break; + } + + nodeR = nodeR.parent; + } while (true); + } + } + } + + + boolean isStatic() { + if (source.getCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ) || + source.getCapability(Node.ENABLE_PICK_REPORTING) || + source.getCapability(Node.ENABLE_COLLISION_REPORTING) || + source.getCapability(Node.ALLOW_BOUNDS_READ) || + source.getCapability(Node.ALLOW_BOUNDS_WRITE) || + source.getCapability(Node.ALLOW_PICKABLE_READ) || + source.getCapability(Node.ALLOW_PICKABLE_WRITE) || + source.getCapability(Node.ALLOW_COLLIDABLE_READ) || + source.getCapability(Node.ALLOW_COLLIDABLE_WRITE) || + source.getCapability(Node.ALLOW_AUTO_COMPUTE_BOUNDS_READ) || + source.getCapability(Node.ALLOW_AUTO_COMPUTE_BOUNDS_WRITE)) { + return false; + } + return true; + } + + void merge(CompileState compState) { + staticTransform = compState.staticTransform; + if (compState.parentGroup != null) { + compState.parentGroup.compiledChildrenList.add(this); + } + parent = compState.parentGroup; + if (staticTransform != null) { + mergeTransform(staticTransform); + } + } + + void mergeTransform(TransformGroupRetained xform) { + if (localBounds != null) { + localBounds.transform(xform.transform); + } + } + int[] processViewSpecificInfo(int mode, HashKey k, View v, ArrayList vsgList, int[] keyList, + ArrayList leafList) { + return keyList; + + } + + VirtualUniverse getVirtualUniverse() { + return universe; + } + + void searchGeometryAtoms(UnorderList list) {} +} + diff --git a/src/classes/share/javax/media/j3d/ObjectUpdate.java b/src/classes/share/javax/media/j3d/ObjectUpdate.java new file mode 100644 index 0000000..eb109b6 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ObjectUpdate.java @@ -0,0 +1,26 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/* + * A Object Update interface. Any object that can be put in the ObjectUpdate list + * must implement this interface. + */ + +interface ObjectUpdate { + + /** + * The actual update function. + */ + abstract void updateObject(); +} diff --git a/src/classes/share/javax/media/j3d/OrderedBin.java b/src/classes/share/javax/media/j3d/OrderedBin.java new file mode 100644 index 0000000..b38d4b1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrderedBin.java @@ -0,0 +1,109 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * An OrderedBin contains an array of OrderedCollection, each represents + * a child of the OrderedGroup + */ +class OrderedBin extends Object { + // ArrayList of orderedCollection, one for each child of the orderedGroup + ArrayList orderedCollections = new ArrayList(); + + // orderedGroup source + OrderedGroupRetained source; + OrderedChildInfo childInfoList= null; + OrderedChildInfo lastChildInfo = null; + + boolean onUpdateList = false; + + // Value of already existing orderedCollection + ArrayList setOCForCI = new ArrayList(); + ArrayList valueOfSetOCForCI = new ArrayList(); + + // Value of orderedCollection based on oi, these arrays + // have size > 0 only during update_view; + ArrayList setOCForOI = new ArrayList(); + ArrayList valueOfSetOCForOI = new ArrayList(); + + OrderedBin(int nchildren, OrderedGroupRetained src){ + int i; + for (i=0; i< nchildren; i++) { + orderedCollections.add(null); + } + source = src; + } + + void addRemoveOrderedCollection() { + int i, index; + + // Add the setValues first, since they reflect already existing + // orderedCollection + for (i = 0; i < setOCForCI.size(); i++) { + index = ((Integer)setOCForCI.get(i)).intValue(); + OrderedCollection oc = (OrderedCollection)valueOfSetOCForCI.get(i); + orderedCollections.set(index, oc); + } + + setOCForCI.clear(); + valueOfSetOCForCI.clear(); + + while (childInfoList != null) { + if (childInfoList.type == OrderedChildInfo.ADD) { + orderedCollections.add(childInfoList.childId, childInfoList.value); + } + else if (childInfoList.type == OrderedChildInfo.REMOVE) { + orderedCollections.remove(childInfoList.childId); + } + childInfoList = childInfoList.next; + } + + // Now update the sets based on oi, since the og.orderedChildIdTable reflects + // the childIds for the next frame, use the table to set the oc at the + // correct place + for (i = 0; i < setOCForOI.size(); i++) { + index = ((Integer)setOCForOI.get(i)).intValue(); + OrderedCollection oc = (OrderedCollection)valueOfSetOCForOI.get(i); + int ci = source.orderedChildIdTable[index]; + orderedCollections.set(ci, oc); + } + setOCForOI.clear(); + valueOfSetOCForOI.clear(); + + onUpdateList = false; + lastChildInfo = null; + + + } + void addChildInfo(OrderedChildInfo cinfo) { + // Add this cinfo at the end + if (childInfoList == null) { + childInfoList = cinfo; + lastChildInfo = cinfo; + } + else { + // Add at the end + cinfo.prev = lastChildInfo; + lastChildInfo.next = cinfo; + cinfo.next = null; + // Update this to be the last child + lastChildInfo = cinfo; + } + + } + +} + + diff --git a/src/classes/share/javax/media/j3d/OrderedChildInfo.java b/src/classes/share/javax/media/j3d/OrderedChildInfo.java new file mode 100644 index 0000000..f07dfe2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrderedChildInfo.java @@ -0,0 +1,64 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * List of orderedGroup children that needs to be added/removed for + * the next frame. Note that the order in which they are removed and + * added should be maintained after the renderer is done to get the + * correct order of rendering. + */ +class OrderedChildInfo extends Object { + + static int ADD = 0x1; + static int REMOVE = 0x2; + + + /** + * Type of operation, could be add/remove or set + */ + int type; + + /** + * Ordered index at which this operation takes place + */ + int orderedId; + + /** + * Child index at which this operation takes place + */ + int childId; + + /** + * Value of the orderedCollection, only relavent for + * add and set + */ + OrderedCollection value; + + + // Maintains the order in which the ordered children + // were added and removed + OrderedChildInfo next; + OrderedChildInfo prev; + + OrderedChildInfo(int t, int cid, int oid, OrderedCollection val) { + type = t; + orderedId = oid; + childId = cid; + value = val; + prev = null; + next = null; + + } + +} diff --git a/src/classes/share/javax/media/j3d/OrderedCollection.java b/src/classes/share/javax/media/j3d/OrderedCollection.java new file mode 100644 index 0000000..5b35d2d --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrderedCollection.java @@ -0,0 +1,58 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + + +/** + * An OrderCollections contains a LightBin and an ArrayList of + * of all top level OrderedGroups under this OrderCollection + */ +class OrderedCollection extends Object implements ObjectUpdate{ + + LightBin lightBin = null; + + // a list of top level orderedBins under this orderedCollection + ArrayList childOrderedBins = new ArrayList(); + + // LightBin used for next frame + LightBin nextFrameLightBin = null; + + // LightBins to be added for this frame + LightBin addLightBins = null; + + boolean onUpdateList = false; + + public void updateObject() { + int i; + LightBin lb; + lightBin = nextFrameLightBin; + if (addLightBins != null) { + if (lightBin != null) { + addLightBins.prev = lightBin; + lightBin.next = addLightBins; + } + else { + lightBin = addLightBins; + nextFrameLightBin = lightBin; + } + } + addLightBins = null; + onUpdateList = false; + } + + + +} + diff --git a/src/classes/share/javax/media/j3d/OrderedGroup.java b/src/classes/share/javax/media/j3d/OrderedGroup.java new file mode 100644 index 0000000..dcbc85d --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrderedGroup.java @@ -0,0 +1,458 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Arrays; + +/** + * The OrderedGroup node is a Group that ensures its children render + * in a specified order. In addition to the list of children + * inherited from the base Group class, OrderedGroup defines an + * integer array of child indices that specify the order in which its + * children are rendered. This provides a level of indirection in + * determining the rendering order of its children. By default, the + * child index order array is null, and the children are rendered in + * increasing index order. + * + * <p> + * When the child index order array is non-null, it must be the same + * length as the number of children. Every entry in the array must + * have a unique value between 0 and <code>numChildren-1</code> (i.e., + * there must be no duplicate values and no missing indices). The + * order that the child indices appear in the child index order array + * determines the order in which the children are rendered. The child + * at <code>childIndexOrder[0]</code> is rendered first, followed by + * <code>childIndexOrder[1]</code>, and so on, with the child at + * <code>childIndexOrder[numChildren-1]</code> rendered + * last. + * + * <p> + * The child index order array only affects rendering. List + * operations that refer to a child by index, such as getChild(index), + * will not be altered by the entries in this array. They will get, + * enumerate, add, remove, etc., the children based on the actual + * index in the group node. However, some of the list operations, + * such as addChild, removeChild, etc., will update the child index + * order array as a result of their operation. For example, + * removeChild will remove the entry in the child index order array + * corresponding to the removed child's index and adjust the remaining + * entries accordingly. See the documentation for these methods for + * more details. + */ + +public class OrderedGroup extends Group { + + private boolean checkArr[] = null; + + /** + * Specifies that this OrderedGroup node + * allows reading its child index order information. + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_CHILD_INDEX_ORDER_READ = + CapabilityBits.ORDERED_GROUP_ALLOW_CHILD_INDEX_ORDER_READ; + + /** + * Specifies that this OrderedGroup node + * allows writing its child index order information. + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_CHILD_INDEX_ORDER_WRITE = + CapabilityBits.ORDERED_GROUP_ALLOW_CHILD_INDEX_ORDER_WRITE; + + + /** + * Constructs and initializes a new OrderedGroup node object. + * The childIndexOrder array is initialized to null, meaning + * that its children are rendered in increasing index order. + */ + public OrderedGroup() { + } + + + /** + * Sets the childIndexOrder array. If the specified array is + * null, this node's childIndexOrder array is set to null. Its + * children will then be rendered in increasing index order. If + * the specified array is not null, the entire array is copied to + * this node's childIndexOrder array. In this case, the length of + * the array must be equal to the number of children in this + * group, and every entry in the array must have a unique value + * between 0 and <code>numChildren-1</code> (i.e., there must be + * no duplicate values and no missing indices). + * + * @param childIndexOrder the array that is copied into this + * node's child index order array; this can be null + * + * @exception IllegalArgumentException if the specified array is + * non-null and any of the following are true: + * <ul> + * <li><code>childIndexOrder.length != numChildren</code>;</li> + * <li><code>childIndexOrder[</code><i>i</i><code>] < 0</code>, + * for <i>i</i> in <code>[0, numChildren-1]</code>;</li> + * <li><code>childIndexOrder[</code><i>i</i><code>] >= numChildren</code>, + * for <i>i</i> in <code>[0, numChildren-1]</code>;</li> + * <li><code>childIndexOrder[</code><i>i</i><code>] == + * childIndexOrder[</code><i>j</i><code>]</code>, + * for <i>i</i>,<i>j</i> in <code>[0, numChildren-1]</code>, + * <i>i</i> <code>!=</code> <i>j</i>;</li> + * </ul> + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setChildIndexOrder(int[] childIndexOrder) { + verifyChildIndexOrderArray(childIndexOrder, 0); + + ((OrderedGroupRetained)retained).setChildIndexOrder(childIndexOrder); + } + + + /** + * Retrieves the current childIndexOrder array. + * + * @return a copy of this node's childIndexOrder array; this + * can be null. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int[] getChildIndexOrder() { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHILD_INDEX_ORDER_READ)) + throw new + CapabilityNotSetException(J3dI18N.getString("OrderedGroup5")); + + return ((OrderedGroupRetained)this.retained).getChildIndexOrder(); + } + + /** + * Appends the specified child node to this group node's list of + * children, and sets the child index order array to the specified + * array. If the specified array is null, this node's + * childIndexOrder array is set to null. Its children will then + * be rendered in increasing index order. If the specified array + * is not null, the entire array is copied to this node's + * childIndexOrder array. In this case, the length of the array + * must be equal to the number of children in this group after the + * new child has been added, and every entry in the array must + * have a unique value between 0 and <code>numChildren-1</code> + * (i.e., there must be no duplicate values and no missing + * indices). + * + * @param child the child to add to this node's list of children + * + * @param childIndexOrder the array that is copied into this + * node's child index order array; this can be null + * + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * + * @exception RestrictedAccessException if this group node is part + * of live + * or compiled scene graph and the child node being added is not + * a BranchGroup node + * + * @exception MultipleParentException if <code>child</code> has already + * been added as a child of another group node. + * + * @exception IllegalArgumentException if the specified array is + * non-null and any of the following are true: + * <ul> + * <li><code>childIndexOrder.length != numChildren</code>;</li> + * <li><code>childIndexOrder[</code><i>i</i><code>] < 0</code>, + * for <i>i</i> in <code>[0, numChildren-1]</code>;</li> + * <li><code>childIndexOrder[</code><i>i</i><code>] >= numChildren</code>, + * for <i>i</i> in <code>[0, numChildren-1]</code>;</li> + * <li><code>childIndexOrder[</code><i>i</i><code>] == + * childIndexOrder[</code><i>j</i><code>]</code>, + * for <i>i</i>,<i>j</i> in <code>[0, numChildren-1]</code>, + * <i>i</i> <code>!=</code> <i>j</i>;</li> + * </ul> + * + * @since Java 3D 1.3 + */ + public void addChild(Node child, int[] childIndexOrder) { + + verifyAddStates(child); + verifyChildIndexOrderArray(childIndexOrder, 1); + + ((OrderedGroupRetained)retained).addChild(child, childIndexOrder); + + } + + + // Overridden methods from Group + + /** + * Appends the specified child node to this group node's list of children. + * + * <p> + * If the current child index order array is non-null, the array + * is increased in size by one element, and a new element + * containing the index of the new child is added to the end of + * the array. Thus, this new child will be rendered last. + * + * @param child the child to add to this node's list of children + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part + * of live + * or compiled scene graph and the child node being added is not + * a BranchGroup node + * @exception MultipleParentException if <code>child</code> has already + * been added as a child of another group node. + * + * @since Java 3D 1.3 + */ + public void addChild(Node child) { + // Just call super -- the extra work is done by the retained class + super.addChild(child); + } + + /** + * Inserts the specified child node in this group node's list of + * children at the specified index. + * This method is only supported when the child index order array + * is null. + * + * @param child the new child + * @param index at which location to insert. The <code>index</code> + * must be a value + * greater than or equal to 0 and less than or equal to + * <code>numChildren()</code>. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live + * or compiled scene graph and the child node being inserted is not + * a BranchGroup node + * @exception MultipleParentException if <code>child</code> has already + * been added as a child of another group node. + * @exception IndexOutOfBoundsException if <code>index</code> is invalid. + * @exception IllegalStateException if the childIndexOrder array is + * not null. + * + * @since Java 3D 1.3 + */ + public void insertChild(Node child, int index) { + if (((OrderedGroupRetained)retained).userChildIndexOrder != null) { + throw new IllegalStateException(J3dI18N.getString("OrderedGroup6")); + } + + // Just call super -- the extra work is done by the retained class + super.insertChild(child, index); + } + + /** + * Removes the child node at the specified index from this group node's + * list of children. + * + * <p> + * If the current child index order array is non-null, the element + * containing the removed child's index will be removed from the + * child index order array, and the array will be reduced in size + * by one element. If the child removed was not the last child in + * the Group, the values of the child index order array will be + * updated to reflect the indices that were renumbered. More + * formally, each child whose index in the Group node was greater + * than the removed element (before removal) will have its index + * decremented by one. + * + * @param index which child to remove. The <code>index</code> + * must be a value + * greater than or equal to 0 and less than <code>numChildren()</code>. + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live or compiled scene graph and the child node being removed is not + * a BranchGroup node + * @exception IndexOutOfBoundsException if <code>index</code> is invalid. + * + * @since Java 3D 1.3 + */ + public void removeChild(int index) { + // Just call super -- the extra work is done by the retained class + super.removeChild(index); + } + + + /** + * Moves the specified branch group node from its existing location to + * the end of this group node's list of children. + * + * <p> + * If the current child index order array is non-null, the array + * is increased in size by one element, and a new element + * containing the index of the new child is added to the end of + * the array. Thus, this new child will be rendered last. + * + * @param branchGroup the branch group node to move to this node's list + * of children + * @exception CapabilityNotSetException if the appropriate capability is + * not set and this group node is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void moveTo(BranchGroup branchGroup) { + // Just call super -- the extra work is done by the retained class + super.moveTo(branchGroup); + } + + /** + * Removes the specified child node from this group node's + * list of children. + * If the specified object is not in the list, the list is not modified. + * + * <p> + * If the current child index order array is non-null, the element + * containing the removed child's index will be removed from the + * child index order array, and the array will be reduced in size + * by one element. If the child removed was not the last child in + * the Group, the values of the child index order array will be + * updated to reflect the indices that were renumbered. More + * formally, each child whose index in the Group node was greater + * than the removed element (before removal) will have its index + * decremented by one. + * + * @param child the child node to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live or compiled scene graph and the child node being removed is not + * a BranchGroup node + * + * @since Java 3D 1.3 + */ + public void removeChild(Node child) { + // Just call super -- the extra work is done by the retained class + super.removeChild(child); + } + + /** + * Removes all children from this Group node. + * + * <p> + * If the current child index order array is non-null, then it is set to + * a zero-length array (the empty set). Note that a zero-length + * child index order array is not the same as a null array in that + * as new elements are added, the child index order array will grow + * and will be used instead of the Group's natural child order. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception RestrictedAccessException if this group node is part of + * live or compiled scene graph and any of the children being removed are + * not BranchGroup nodes + * + * @since Java 3D 1.3 + */ + public void removeAllChildren() { + // Just call super -- the extra work is done by the retained class + super.removeAllChildren(); + } + + + /** + * Creates the retained mode OrderedGroupRetained object that this + * OrderedGroup component object will point to. + */ + void createRetained() { + this.retained = new OrderedGroupRetained(); + this.retained.setSource(this); + } + + void verifyAddStates(Node child) { + + if (child instanceof SharedGroup) { + throw new IllegalArgumentException(J3dI18N.getString("Group2")); + } + + if (isLiveOrCompiled()) { + if (! (child instanceof BranchGroup)) + throw new RestrictedAccessException(J3dI18N.getString("Group12")); + + if(!this.getCapability(ALLOW_CHILDREN_EXTEND)) + throw new CapabilityNotSetException(J3dI18N.getString("Group16")); + } + } + + void verifyChildIndexOrderArray(int[] cIOArr, int plus) { + + if (isLiveOrCompiled()) { + + if(!this.getCapability(ALLOW_CHILD_INDEX_ORDER_WRITE)) + throw new + CapabilityNotSetException(J3dI18N.getString("OrderedGroup4")); + } + + if(cIOArr != null) { + + if(cIOArr.length != (((GroupRetained)retained).children.size() + plus)) { + throw new + IllegalArgumentException(J3dI18N.getString("OrderedGroup0")); + } + + if((checkArr == null) || (checkArr.length != cIOArr.length)) { + checkArr = new boolean[cIOArr.length]; + } + + Arrays.fill(checkArr, false); + + for(int i=0; i<cIOArr.length; i++) { + if(cIOArr[i] < 0) { + throw new + IllegalArgumentException(J3dI18N.getString("OrderedGroup1")); + } + else if(cIOArr[i] >= cIOArr.length) { + throw new + IllegalArgumentException(J3dI18N.getString("OrderedGroup2")); + } + else if(checkArr[cIOArr[i]]) { + throw new + IllegalArgumentException(J3dI18N.getString("OrderedGroup3")); + } + else { + checkArr[cIOArr[i]] = true; + } + } + } + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + OrderedGroup og = new OrderedGroup(); + og.duplicateNode(this, forceDuplicate); + return og; + } +} diff --git a/src/classes/share/javax/media/j3d/OrderedGroupRetained.java b/src/classes/share/javax/media/j3d/OrderedGroupRetained.java new file mode 100644 index 0000000..8ee1f8f --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrderedGroupRetained.java @@ -0,0 +1,502 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * The OrderedGroup is a group node that ensures its children rendered + * in index increasing order. + */ + +class OrderedGroupRetained extends GroupRetained { + // mapping of ordered child id to child index + int orderedChildIdTable[]; + + // This is a counter for ordered child id + private int orderedChildIdCount = 0; + + // This is a vector of free orderedChildId + private ArrayList orderedChildIdFreeList = new ArrayList(); + + // One OrderedBin per view + OrderedBin[] orderedBin = new OrderedBin[0]; + + // child id of the newly added child + Integer newChildId; + + // ChildCount used by renderBin to initialize the + // orderedCollection in each orderedBin (per view) + int childCount = 0; + + // per children ordered path data + ArrayList childrenOrderedPaths = new ArrayList(1); + + + // child index order - set by the user. + int[] userChildIndexOrder = null; + + // child index order - use by j3d internal. + int[] childIndexOrder = null; + + + OrderedGroupRetained() { + this.nodeType = NodeRetained.ORDEREDGROUP; + } + + + void setChildIndexOrder(int[] cIOArr) { + if(cIOArr != null) { + if((userChildIndexOrder == null) || + (userChildIndexOrder.length != cIOArr.length)) { + userChildIndexOrder = new int[cIOArr.length]; + } + + System.arraycopy(cIOArr, 0, userChildIndexOrder, + 0, userChildIndexOrder.length); + } + else { + userChildIndexOrder = null; + } + + if (source.isLive()) { + int[] newArr = new int[cIOArr.length]; + System.arraycopy(cIOArr, 0, newArr, + 0, newArr.length); + J3dMessage m; + m = VirtualUniverse.mc.getMessage(); + m.threads = J3dThread.UPDATE_RENDER; + m.type = J3dMessage.ORDERED_GROUP_TABLE_CHANGED; + m.universe = universe; + m.args[3] = this; + m.args[4] = newArr; + VirtualUniverse.mc.processMessage(m); + } + } + + int[] getChildIndexOrder() { + if (userChildIndexOrder == null) { + return null; + } + + int[] newArr = new int[userChildIndexOrder.length]; + System.arraycopy(userChildIndexOrder, 0, + newArr, 0, userChildIndexOrder.length); + return newArr; + + } + + Integer getOrderedChildId() { + Integer orderedChildId; + synchronized(orderedChildIdFreeList) { + if (orderedChildIdFreeList.size() == 0) { + orderedChildId = new Integer(orderedChildIdCount); + orderedChildIdCount++; + } else { + orderedChildId = (Integer)orderedChildIdFreeList.remove(0); + } + } + return(orderedChildId); + } + + void freeOrderedChildId(int id) { + synchronized(orderedChildIdFreeList) { + orderedChildIdFreeList.add(new Integer(id)); + } + } + + int getOrderedChildCount() { + int count; + + synchronized (orderedChildIdFreeList) { + count = orderedChildIdCount; + } + return count; + } + + void addChild(Node child) { + if(userChildIndexOrder != null) { + doAddChildIndexEntry(); + } + + // GroupRetained.addChild have to check for case of non-null child index order + // array and handle it. + super.addChild(child); + + } + + void addChild(Node child, int[] cIOArr) { + if(cIOArr != null) { + userChildIndexOrder = new int[cIOArr.length]; + + System.arraycopy(cIOArr, 0, userChildIndexOrder, + 0, userChildIndexOrder.length); + } + else { + userChildIndexOrder = null; + } + + // GroupRetained.addChild have to check for case of non-null child + // index order array and handle it. + super.addChild(child); + + } + + void moveTo(BranchGroup bg) { + if(userChildIndexOrder != null) { + doAddChildIndexEntry(); + } + + // GroupRetained.moveto have to check for case of non-null child + // index order array and handle it. + super.moveTo(bg); + } + + + void doRemoveChildIndexEntry(int index) { + + int[] newArr = new int[userChildIndexOrder.length - 1]; + + for(int i=0, j=0; i<userChildIndexOrder.length; i++) { + if(userChildIndexOrder[i] > index) { + newArr[j] = userChildIndexOrder[i] - 1; + j++; + } + else if(userChildIndexOrder[i] < index) { + newArr[j] = userChildIndexOrder[i]; + j++; + } + } + + userChildIndexOrder = newArr; + + } + + void doAddChildIndexEntry() { + int[] newArr = new int[userChildIndexOrder.length + 1]; + + System.arraycopy(userChildIndexOrder, 0, newArr, + 0, userChildIndexOrder.length); + + newArr[userChildIndexOrder.length] = userChildIndexOrder.length; + + userChildIndexOrder = newArr; + } + + /** + * Compiles the children of the OrderedGroup, preventing shape merging at + * this level or above + */ + void compile(CompileState compState) { + + super.compile(compState); + + // don't remove this group node + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numOrderedGroups++; + } + } + + void setOrderedBin(OrderedBin ob, int index) { + synchronized (orderedBin) { + orderedBin[index] = ob; + } + } + + + // Get the orderedBin for this view index + OrderedBin getOrderedBin(int index) { + OrderedBin ob; + synchronized (orderedBin) { + if (index >= orderedBin.length) { + OrderedBin[] newList = new OrderedBin[index+1]; + for (int i= 0; i < orderedBin.length; i++) { + newList[i] = orderedBin[i]; + } + newList[index] = null; + orderedBin = newList; + } + } + return (orderedBin[index]); + } + + void updateChildIdTableInserted(int childId, int orderedId) { + int size = 0; + int i; + + //System.out.println("updateChildIdTableInserted childId " + childId + " orderedId " + orderedId + " " + this); + if (orderedChildIdTable != null) { + size = orderedChildIdTable.length; + for (i=0; i<size; i++) { + if (orderedChildIdTable[i] != -1) { + if (orderedChildIdTable[i] >= childId) { + orderedChildIdTable[i]++; // shift upward + } + } + } + } + if (orderedId >= size) { + int newTable[]; + newTable = new int[orderedId+1]; + if (size > 0) { + System.arraycopy(orderedChildIdTable,0,newTable,0, + orderedChildIdTable.length); + } + else { + for (i = 0; i < newTable.length; i++) { + newTable[i] = -1; + } + } + orderedChildIdTable = newTable; + } + orderedChildIdTable[orderedId] = childId; + //printTable(orderedChildIdTable); + + } + + void updateChildIdTableRemoved(int childId ) { + // If the orderedGroup itself has been clearLived, then the ids + // have been returned, if only some of the children of the + // OGs is removed, then removed the specific entries + // from the table + if (orderedChildIdTable == null) + return; + + for (int i=0; i<orderedChildIdTable.length; i++) { + if (orderedChildIdTable[i] != -1) { + if (orderedChildIdTable[i] > childId) { + orderedChildIdTable[i]--; // shift downward + } + else if (orderedChildIdTable[i] == childId) { + orderedChildIdTable[i] = -1; + //System.out.println("og.updateChildIdTableRemoved freeId " + i); + freeOrderedChildId(i); + } + } + } + + } + + void setAuxData(SetLiveState s, int index, int hkIndex) { + OrderedPath setLiveStateOrderedPath, newOrderedPath; + ArrayList childOrderedPaths; + NodeRetained child; + + setLiveStateOrderedPath = (OrderedPath) s.orderedPaths.get(hkIndex); + for (int i=0; i<children.size(); i++) { + child = (NodeRetained)children.get(i); + if (refCount == s.refCount) { + // only need to do it once if in shared group when the first + // instances is to be added + child.orderedId = getOrderedChildId(); + } + + newOrderedPath = setLiveStateOrderedPath.clonePath(); + newOrderedPath.addElementToPath(this, child.orderedId); + childOrderedPaths = (ArrayList)childrenOrderedPaths.get(i); + childOrderedPaths.add(hkIndex, newOrderedPath); + } + } + + + void setLive(SetLiveState s) { + super.setLive(s); + s.orderedPaths = orderedPaths; + if((userChildIndexOrder != null) && (refCount == 1)) { + + // Don't send a message for initial set live. + int[]newArr = new int[userChildIndexOrder.length]; + System.arraycopy(userChildIndexOrder, 0, newArr, + 0, userChildIndexOrder.length); + childIndexOrder = newArr; + } + } + + void clearLive(SetLiveState s) { + super.clearLive(s); + // This is used to clear the childIdTable and set the orderedBin + // for all views to be null + + if (refCount == 0) { + s.notifyThreads |= J3dThread.UPDATE_RENDERING_ENVIRONMENT; + // only need to do it once if in shared group + s.nodeList.add(this); + s.ogCIOList.add(this); + s.ogCIOTableList.add(null); + userChildIndexOrder = null; + } + s.orderedPaths = orderedPaths; + } + + void setNodeData(SetLiveState s) { + super.setNodeData(s); + if (!inSharedGroup) { + setAuxData(s, 0, 0); + } else { + // For inSharedGroup case. + int j, hkIndex; + + for(j=0; j<s.keys.length; j++) { + hkIndex = s.keys[j].equals(localToVworldKeys, 0, + localToVworldKeys.length); + + if(hkIndex >= 0) { + setAuxData(s, j, hkIndex); + + } else { + System.out.println("Can't Find matching hashKey in setNodeData."); + System.out.println("We're in TROUBLE!!!"); + } + } + } + // Note s.orderedPaths is to be updated in GroupRetained.setLive + // for each of its children + } + + void removeNodeData(SetLiveState s) { + + if((inSharedGroup) && (s.keys.length != localToVworld.length)) { + int i, index; + ArrayList childOrderedPaths; + + // Must be in reverse, to preserve right indexing. + for (i = s.keys.length-1; i >= 0; i--) { + index = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + if(index >= 0) { + for (int j=0; j<children.size(); j++) { + childOrderedPaths = (ArrayList)childrenOrderedPaths.get(j); + childOrderedPaths.remove(index); + } + } + } + // Note s.orderedPaths is to be updated in GroupRetained.clearLive + // for each of its children + } + super.removeNodeData(s); + } + + + // This node has been cleared, so + void clearDerivedDataStructures() { + int i; + + //System.out.println("og clearDerivedDataStructures " + this); + // Clear the orderedBin and childId table for all views + // since this orderedGroup has been clearLived! + for (i = 0; i < orderedBin.length; i++) { + if (orderedBin[i] != null) { + orderedBin[i].source = null; + orderedBin[i] = null; + } + } + if (orderedChildIdTable != null) { + for (i=0; i<orderedChildIdTable.length; i++) { + if (orderedChildIdTable[i] != -1) { + orderedChildIdTable[i] = -1; + //System.out.println("og.clearDerivedDataStructures freeId " + i); + freeOrderedChildId(i); + } + } + orderedChildIdTable = null; + } + } + + void incrChildCount() { + childCount++; + } + + + void decrChildCount() { + childCount--; + } + + void printTable(int[] table) { + for (int i=0; i<table.length; i++) { + System.out.print(" " + table[i]); + } + System.out.println(""); + } + + void insertChildrenData(int index) { + childrenOrderedPaths.add(index, new ArrayList(1)); + } + + void appendChildrenData() { + childrenOrderedPaths.add(new ArrayList(1)); + } + + void doRemoveChild(int index, J3dMessage messages[], int messageIndex) { + + if(userChildIndexOrder != null) { + doRemoveChildIndexEntry(index); + } + + super.doRemoveChild(index, messages, messageIndex); + + } + + void removeChildrenData(int index) { + childrenOrderedPaths.remove(index); + } + + void childDoSetLive(NodeRetained child, int childIndex, SetLiveState s) { + if (refCount == s.refCount) { + s.ogList.add(this); + s.ogChildIdList.add(new Integer(childIndex)); + s.ogOrderedIdList.add(child.orderedId); + } + s.orderedPaths = (ArrayList)childrenOrderedPaths.get(childIndex); + if(child!=null) + child.setLive(s); + } + + void childCheckSetLive(NodeRetained child, int childIndex, + SetLiveState s, NodeRetained linkNode) { + OrderedPath childOrderedPath; + ArrayList childOrderedPaths; + + if (linkNode != null) { + int ci = children.indexOf(linkNode); + childOrderedPaths = (ArrayList)childrenOrderedPaths.get(ci); + } else { + child.orderedId = getOrderedChildId(); + // set this regardless of refCount + s.ogList.add(this); + s.ogChildIdList.add(new Integer(childIndex)); + s.ogOrderedIdList.add(child.orderedId); + + if(userChildIndexOrder != null) { + s.ogCIOList.add(this); + int[] newArr = new int[userChildIndexOrder.length]; + System.arraycopy(userChildIndexOrder, 0, newArr, + 0, userChildIndexOrder.length); + + s.ogCIOTableList.add(newArr); + } + + childOrderedPaths = (ArrayList)childrenOrderedPaths.get(childIndex); + + for(int i=0; i< orderedPaths.size();i++){ + childOrderedPath = + ((OrderedPath)orderedPaths.get(i)).clonePath(); + childOrderedPath.addElementToPath(this, child.orderedId); + childOrderedPaths.add(childOrderedPath); + } + } + s.orderedPaths = childOrderedPaths; + child.setLive(s); + } +} diff --git a/src/classes/share/javax/media/j3d/OrderedPath.java b/src/classes/share/javax/media/j3d/OrderedPath.java new file mode 100644 index 0000000..8da3fe8 --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrderedPath.java @@ -0,0 +1,40 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +class OrderedPath extends Object { + ArrayList pathElements = new ArrayList(1); + + + void addElementToPath(OrderedGroupRetained og, Integer orderedId) { + pathElements.add(new OrderedPathElement(og, orderedId)); + } + + OrderedPath clonePath() { + OrderedPath path = new OrderedPath(); + path.pathElements = (ArrayList)pathElements.clone(); + return path; + } + + void printPath() { + System.out.println("orderedPath: ["); + OrderedPathElement ope; + for (int i=0; i<pathElements.size(); i++) { + ope = (OrderedPathElement)pathElements.get(i); + System.out.println("(" + ope.orderedGroup + "," + ope.childId); + } + System.out.println("]"); + } +} diff --git a/src/classes/share/javax/media/j3d/OrderedPathElement.java b/src/classes/share/javax/media/j3d/OrderedPathElement.java new file mode 100644 index 0000000..cce1d77 --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrderedPathElement.java @@ -0,0 +1,25 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +class OrderedPathElement extends Object { + OrderedGroupRetained orderedGroup; + Integer childId; + + OrderedPathElement(OrderedGroupRetained og, Integer orderedId) { + orderedGroup = og; + childId = orderedId; + } +} diff --git a/src/classes/share/javax/media/j3d/OrientedShape3D.java b/src/classes/share/javax/media/j3d/OrientedShape3D.java new file mode 100644 index 0000000..edfbe3a --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrientedShape3D.java @@ -0,0 +1,645 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * The OrientedShape3D leaf node is a Shape3D node that is oriented + * along a specified axis or about a specified point. It defines an + * alignment mode and a rotation point or axis. This will cause + * the local +<i>z</i> axis of the object to point at the viewer's eye + * position. This is done regardless of the transforms above this + * OrientedShape3D node in the scene graph. It optionally defines a + * scale value along with a constant scale enable flag that causes + * this node to be scale invariant, subject only to its scale. + * + * <p> + * OrientedShape3D is similar in functionality to the Billboard + * behavior, but OrientedShape3D nodes will orient themselves + * correctly for each view, and they can be used within a SharedGroup. + * + * <p> + * If the alignment mode is ROTATE_ABOUT_AXIS, then the rotation will be + * around the specified axis. If the alignment mode is + * ROTATE_ABOUT_POINT, then the rotation will be about the specified + * point, with an additional rotation to align the +<i>y</i> axis of the + * TransformGroup with the +<i>y</i> axis in the View. + * + * <p> + * If the constant scale enable flag is set, the object will be drawn + * the same size in absolute screen coordinates (meters), regardless + * of the following: any transforms above this OrientedShape3D node in + * the scene graph, the view scale, the window scale, or the effects + * of perspective correction. This is done by scaling the geometry + * about the local origin of this node, such that 1 unit in local + * coordinates is equal to the number of meters specified by this + * node's scale value. If the constant scale enable flag is set to + * false, then the scale is not used. The default scale is 1.0 + * meters. + * + * <p> + * OrientedShape3D nodes are ideal for drawing screen-aligned text or + * for drawing roughly-symmetrical objects. A typical use might + * consist of a quadrilateral that contains a texture of a tree. + * + * @see Billboard + * + * @since Java 3D 1.2 + */ + +public class OrientedShape3D extends Shape3D { + + /** + * Specifies that rotation should be about the specified axis. + * @see #setAlignmentMode + */ + public static final int ROTATE_ABOUT_AXIS = 0; + + /** + * Specifies that rotation should be about the specified point and + * that the children's Y-axis should match the view object's Y-axis. + * @see #setAlignmentMode + */ + public static final int ROTATE_ABOUT_POINT = 1; + + /** + * Specifies that no rotation is done. The OrientedShape3D will + * not be aligned to the view. + * @see #setAlignmentMode + * + * @since Java 3D 1.3 + */ + public static final int ROTATE_NONE = 2; + + + /** + * Specifies that this OrientedShape3D node + * allows reading its alignment mode information. + */ + public static final int ALLOW_MODE_READ = + CapabilityBits.ORIENTED_SHAPE3D_ALLOW_MODE_READ; + + /** + * Specifies that this OrientedShape3D node + * allows writing its alignment mode information. + */ + public static final int ALLOW_MODE_WRITE = + CapabilityBits.ORIENTED_SHAPE3D_ALLOW_MODE_WRITE; + + /** + * Specifies that this OrientedShape3D node + * allows reading its alignment axis information. + */ + public static final int ALLOW_AXIS_READ = + CapabilityBits.ORIENTED_SHAPE3D_ALLOW_AXIS_READ; + + /** + * Specifies that this OrientedShape3D node + * allows writing its alignment axis information. + */ + public static final int ALLOW_AXIS_WRITE = + CapabilityBits.ORIENTED_SHAPE3D_ALLOW_AXIS_WRITE; + + /** + * Specifies that this OrientedShape3D node + * allows reading its rotation point information. + */ + public static final int ALLOW_POINT_READ = + CapabilityBits.ORIENTED_SHAPE3D_ALLOW_POINT_READ; + + /** + * Specifies that this OrientedShape3D node + * allows writing its rotation point information. + */ + public static final int ALLOW_POINT_WRITE = + CapabilityBits.ORIENTED_SHAPE3D_ALLOW_POINT_WRITE; + + /** + * Specifies that this OrientedShape3D node + * allows reading its scale and constant scale enable information. + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_SCALE_READ = + CapabilityBits.ORIENTED_SHAPE3D_ALLOW_SCALE_READ; + + /** + * Specifies that this OrientedShape3D node + * allows writing its scale and constant scale enable information. + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_SCALE_WRITE = + CapabilityBits.ORIENTED_SHAPE3D_ALLOW_SCALE_WRITE; + + + /** + * Constructs an OrientedShape3D node with default parameters. + * The default values are as follows: + * <ul> + * alignment mode : ROTATE_ABOUT_AXIS<br> + * alignment axis : Y-axis (0,1,0)<br> + * rotation point : (0,0,1)<br> + * constant scale enable : false<br> + * scale : 1.0<br> + *</ul> + */ + public OrientedShape3D() { + super(); + } + + + /** + * Constructs an OrientedShape3D node with the specified geometry + * component, appearance component, mode, and axis. + * The specified axis must not be parallel to the <i>Z</i> + * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not + * possible for the +<i>Z</i> axis to point at the viewer's eye + * position by rotating about itself. The target transform will + * be set to the identity if the axis is (0,0,<i>z</i>). + * + * @param geometry the geometry component with which to initialize + * this shape node + * @param appearance the appearance component of the shape node + * @param mode alignment mode, one of: ROTATE_ABOUT_AXIS, + * ROTATE_ABOUT_POINT, or ROTATE_NONE + * @param axis the ray about which the OrientedShape3D rotates + */ + public OrientedShape3D(Geometry geometry, + Appearance appearance, + int mode, + Vector3f axis) { + + super(geometry, appearance); + ((OrientedShape3DRetained)retained).initAlignmentMode(mode); + ((OrientedShape3DRetained)retained).initAlignmentAxis(axis); + } + + /** + * Constructs an OrientedShape3D node with the specified geometry + * component, appearance component, mode, and rotation point. + * + * @param geometry the geometry component with which to initialize + * this shape node + * @param appearance the appearance component of the shape node + * @param mode alignment mode, one of: ROTATE_ABOUT_AXIS, + * ROTATE_ABOUT_POINT, or ROTATE_NONE + * @param point the position about which the OrientedShape3D rotates + */ + public OrientedShape3D(Geometry geometry, + Appearance appearance, + int mode, + Point3f point) { + + super(geometry, appearance); + ((OrientedShape3DRetained)retained).initAlignmentMode(mode); + ((OrientedShape3DRetained)retained).initRotationPoint(point); + + } + + + /** + * Constructs an OrientedShape3D node with the specified geometry + * component, appearance component, mode, axis, constant scale + * enable flag, and scale + * The specified axis must not be parallel to the <i>Z</i> + * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not + * possible for the +<i>Z</i> axis to point at the viewer's eye + * position by rotating about itself. The target transform will + * be set to the identity if the axis is (0,0,<i>z</i>). + * + * @param geometry the geometry component with which to initialize + * this shape node + * @param appearance the appearance component of the shape node + * @param mode alignment mode, one of: ROTATE_ABOUT_AXIS, + * ROTATE_ABOUT_POINT, or ROTATE_NONE + * @param axis the ray about which the OrientedShape3D rotates + * @param constantScaleEnable a flag indicating whether to enable + * constant scale + * @param scale scale value used when constant scale is enabled + * + * @since Java 3D 1.3 + */ + public OrientedShape3D(Geometry geometry, + Appearance appearance, + int mode, + Vector3f axis, + boolean constantScaleEnable, + double scale) { + + super(geometry, appearance); + + ((OrientedShape3DRetained)retained).initAlignmentMode(mode); + ((OrientedShape3DRetained)retained).initAlignmentAxis(axis); + ((OrientedShape3DRetained)retained). + initConstantScaleEnable(constantScaleEnable); + ((OrientedShape3DRetained)retained).initScale(scale); + } + + /** + * Constructs an OrientedShape3D node with the specified geometry + * component, appearance component, mode, and rotation point. + * + * @param geometry the geometry component with which to initialize + * this shape node + * @param appearance the appearance component of the shape node + * @param mode alignment mode, one of: ROTATE_ABOUT_AXIS, + * ROTATE_ABOUT_POINT, or ROTATE_NONE + * @param point the position about which the OrientedShape3D rotates + * @param constantScaleEnable a flag indicating whether to enable + * constant scale + * @param scale scale value used when constant scale is enabled + * + * @since Java 3D 1.3 + */ + public OrientedShape3D(Geometry geometry, + Appearance appearance, + int mode, + Point3f point, + boolean constantScaleEnable, + double scale) { + + super(geometry, appearance); + + ((OrientedShape3DRetained)retained).initAlignmentMode(mode); + ((OrientedShape3DRetained)retained).initRotationPoint(point); + ((OrientedShape3DRetained)retained). + initConstantScaleEnable(constantScaleEnable); + ((OrientedShape3DRetained)retained).initScale(scale); + } + + + /** + * Creates the retained mode OrientedShape3DRetained object that this + * OrientedShape3D object will point to. + */ + void createRetained() { + retained = new OrientedShape3DRetained(); + retained.setSource(this); + } + + + /** + * Sets the alignment mode. + * + * @param mode alignment mode, one of: ROTATE_ABOUT_AXIS, + * ROTATE_ABOUT_POINT, or ROTATE_NONE + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAlignmentMode(int mode) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MODE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D0")); + if (isLive()) + ((OrientedShape3DRetained)retained).setAlignmentMode(mode); + else + ((OrientedShape3DRetained)retained).initAlignmentMode(mode); + } + + + /** + * Retrieves the alignment mode. + * + * @return one of: ROTATE_ABOUT_AXIS, ROTATE_ABOUT_POINT, + * or ROTATE_NONE + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getAlignmentMode() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D1")); + return((OrientedShape3DRetained)retained).getAlignmentMode(); + } + + + /** + * Sets the new alignment axis. This is the ray about which this + * OrientedShape3D rotates when the mode is ROTATE_ABOUT_AXIS. + * The specified axis must not be parallel to the <i>Z</i> + * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not + * possible for the +<i>Z</i> axis to point at the viewer's eye + * position by rotating about itself. The target transform will + * be set to the identity if the axis is (0,0,<i>z</i>). + * + * @param axis the new alignment axis + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAlignmentAxis(Vector3f axis) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_AXIS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D2")); + if (isLive()) + ((OrientedShape3DRetained)retained).setAlignmentAxis(axis); + else + ((OrientedShape3DRetained)retained).initAlignmentAxis(axis); + } + + + /** + * Sets the new alignment axis. This is the ray about which this + * OrientedShape3D rotates when the mode is ROTATE_ABOUT_AXIS. + * The specified axis must not be parallel to the <i>Z</i> + * axis--(0,0,<i>z</i>) for any value of <i>z</i>. It is not + * possible for the +<i>Z</i> axis to point at the viewer's eye + * position by rotating about itself. The target transform will + * be set to the identity if the axis is (0,0,<i>z</i>). + * + * @param x the x component of the alignment axis + * @param y the y component of the alignment axis + * @param z the z component of the alignment axis + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAlignmentAxis(float x, float y, float z) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_AXIS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D2")); + if (isLive()) + ((OrientedShape3DRetained)retained).setAlignmentAxis(x,y,z); + else + ((OrientedShape3DRetained)retained).initAlignmentAxis(x,y,z); + } + + + /** + * Retrieves the alignment axis of this OrientedShape3D node, + * and copies it into the specified vector. + * + * @param axis the vector that will contain the alignment axis + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getAlignmentAxis(Vector3f axis) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_AXIS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D3")); + ((OrientedShape3DRetained)retained).getAlignmentAxis(axis); + } + + /** + * Sets the new rotation point. This is the point about which the + * OrientedShape3D rotates when the mode is ROTATE_ABOUT_POINT. + * + * @param point the new rotation point + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setRotationPoint(Point3f point) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_POINT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D4")); + if (isLive()) + ((OrientedShape3DRetained)retained).setRotationPoint(point); + else + ((OrientedShape3DRetained)retained).initRotationPoint(point); + } + + + /** + * Sets the new rotation point. This is the point about which the + * OrientedShape3D rotates when the mode is ROTATE_ABOUT_POINT. + * + * @param x the x component of the rotation point + * @param y the y component of the rotation point + * @param z the z component of the rotation point + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setRotationPoint(float x, float y, float z) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_POINT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D4")); + if (isLive()) + ((OrientedShape3DRetained)retained).setRotationPoint(x,y,z); + else + ((OrientedShape3DRetained)retained).initRotationPoint(x,y,z); + } + + + /** + * Retrieves the rotation point of this OrientedShape3D node, + * and copies it into the specified vector. + * + * @param point the point that will contain the rotation point + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getRotationPoint(Point3f point) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_POINT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D5")); + ((OrientedShape3DRetained)retained).getRotationPoint(point); + } + + + /** + * Sets the constant scale enable flag. + * + * @param constantScaleEnable a flag indicating whether to enable + * constant scale + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setConstantScaleEnable(boolean constantScaleEnable) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SCALE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D6")); + + if (isLive()) + ((OrientedShape3DRetained)retained). + setConstantScaleEnable(constantScaleEnable); + else + ((OrientedShape3DRetained)retained). + initConstantScaleEnable(constantScaleEnable); + } + + + /** + * Retrieves the constant scale enable flag. + * + * @return the current constant scale enable flag + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public boolean getConstantScaleEnable() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SCALE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D7")); + + return ((OrientedShape3DRetained)retained).getConstantScaleEnable(); + } + + + /** + * Sets the scale for this OrientedShape3D. This scale is used when + * the constant scale enable flag is set to true. + * + * @param scale the scale value + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setScale(double scale) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SCALE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D8")); + + if (isLive()) + ((OrientedShape3DRetained)retained).setScale(scale); + else + ((OrientedShape3DRetained)retained).initScale(scale); + } + + + /** + * Retrieves the scale value for this OrientedShape3D. + * + * @return the current scale value + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public double getScale() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_SCALE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("OrientedShape3D9")); + + return ((OrientedShape3DRetained)retained).getScale(); + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * <code>cloneNode</code> should be overridden by any user subclassed + * objects. All subclasses must have their <code>cloneNode</code> + * method consist of the following lines: + * <P><blockquote><pre> + * public Node cloneNode(boolean forceDuplicate) { + * UserSubClass usc = new UserSubClass(); + * usc.duplicateNode(this, forceDuplicate); + * return usc; + * } + * </pre></blockquote> + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + OrientedShape3D s = new OrientedShape3D(); + s.duplicateNode(this, forceDuplicate); + return s; + } + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @exception ClassCastException if originalNode is not an instance of + * <code>Shape3D</code> + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + checkDuplicateNode(originalNode, forceDuplicate); + } + + + + /** + * Copies all Shape3D information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + + super.duplicateAttributes(originalNode, forceDuplicate); + OrientedShape3DRetained attr = (OrientedShape3DRetained) + originalNode.retained; + OrientedShape3DRetained rt = (OrientedShape3DRetained) retained; + + rt.setAlignmentMode(attr.getAlignmentMode()); + Vector3f axis = new Vector3f(); + attr.getAlignmentAxis(axis); + rt.setAlignmentAxis(axis); + Point3f point = new Point3f(); + attr.getRotationPoint(point); + rt.setRotationPoint(point); + } +} diff --git a/src/classes/share/javax/media/j3d/OrientedShape3DRenderMethod.java b/src/classes/share/javax/media/j3d/OrientedShape3DRenderMethod.java new file mode 100644 index 0000000..bfffdd3 --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrientedShape3DRenderMethod.java @@ -0,0 +1,111 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The OrientedShape3DRenderMethod provides a render method to render + * OrientedShape3D nodes. + * The RenderMethod interface is used to create various ways to render + * different geometries. + */ + +class OrientedShape3DRenderMethod implements RenderMethod { + + public boolean render(RenderMolecule rm, Canvas3D cv, int pass, + RenderAtomListInfo ra, int dirtyBits) { + boolean useAlpha; + boolean isNonUniformScale; + Transform3D trans=null; + + useAlpha = rm.useAlpha; + + GeometryArrayRetained geo = (GeometryArrayRetained)ra.geometry(); + geo.setVertexFormat((rm.useAlpha && + ((geo.vertexFormat & GeometryArray.COLOR) != 0)), + rm.textureBin.attributeBin.ignoreVertexColors, cv.ctx); + + if (rm.doInfinite) { + cv.updateState(pass, dirtyBits); + while (ra != null) { + trans = ra.infLocalToVworld; + isNonUniformScale = !trans.isCongruent(); + + cv.setModelViewMatrix(cv.ctx, cv.vworldToEc.mat, trans); + ra.geometry().execute(cv, ra.renderAtom, isNonUniformScale, + (useAlpha && ra.geometry().noAlpha), + rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin.ignoreVertexColors, + pass); + ra = ra.next; + } + return true; + } + + boolean isVisible = false; // True if any of the RAs is visible. + while (ra != null) { + if (cv.ra == ra.renderAtom) { + if (cv.raIsVisible) { + cv.updateState(pass, dirtyBits); + trans = ra.localToVworld; + isNonUniformScale = !trans.isCongruent(); + + cv.setModelViewMatrix(cv.ctx, cv.vworldToEc.mat, trans); + ra.geometry().execute(cv, ra.renderAtom, isNonUniformScale, + (useAlpha && ra.geometry().noAlpha), + rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin. + ignoreVertexColors, + pass); + isVisible = true; + } + } + else { + if (ra.renderAtom.localeVwcBounds.intersect(cv.viewFrustum)) { + cv.updateState(pass, dirtyBits); + cv.raIsVisible = true; + trans = ra.localToVworld; + isNonUniformScale = !trans.isCongruent(); + + cv.setModelViewMatrix(cv.ctx, cv.vworldToEc.mat, trans); + ra.geometry().execute(cv, ra.renderAtom, isNonUniformScale, + (useAlpha && ra.geometry().noAlpha), + rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin. + ignoreVertexColors, + pass); + isVisible = true; + } + else { + cv.raIsVisible = false; + } + cv.ra = ra.renderAtom; + + } + ra = ra.next; + + } + + + geo.disableGlobalAlpha(cv.ctx, + (rm.useAlpha && + ((geo.vertexFormat & GeometryArray.COLOR) != 0)), + rm.textureBin.attributeBin.ignoreVertexColors); + return isVisible; + } +} diff --git a/src/classes/share/javax/media/j3d/OrientedShape3DRetained.java b/src/classes/share/javax/media/j3d/OrientedShape3DRetained.java new file mode 100644 index 0000000..a8fbd3a --- /dev/null +++ b/src/classes/share/javax/media/j3d/OrientedShape3DRetained.java @@ -0,0 +1,592 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; + +class OrientedShape3DRetained extends Shape3DRetained { + + static final int ALIGNMENT_CHANGED = LAST_DEFINED_BIT << 1; + static final int AXIS_CHANGED = LAST_DEFINED_BIT << 2; + static final int ROTATION_CHANGED = LAST_DEFINED_BIT << 3; + static final int CONSTANT_SCALE_CHANGED = LAST_DEFINED_BIT << 4; + static final int SCALE_FACTOR_CHANGED = LAST_DEFINED_BIT << 5; + + + int mode = OrientedShape3D.ROTATE_ABOUT_AXIS; + + // Axis about which to rotate. + Vector3f axis = new Vector3f(0.0f, 1.0f, 0.0f); + Point3f rotationPoint = new Point3f(0.0f, 0.0f, 1.0f); + private Vector3d nAxis = new Vector3d(0.0, 1.0, 0.0); // normalized axis + + // reused temporaries + private Point3d viewPosition = new Point3d(); + private Point3d yUpPoint = new Point3d(); + + private Vector3d eyeVec = new Vector3d(); + private Vector3d yUp = new Vector3d(); + private Vector3d zAxis = new Vector3d(); + private Vector3d yAxis = new Vector3d(); + private Vector3d vector = new Vector3d(); + + private AxisAngle4d aa = new AxisAngle4d(); + + private Transform3D xform = new Transform3D(); // used several times + private Transform3D zRotate = new Transform3D(); + + // For scale invariant mode + boolean constantScale = false; + double scaleFactor = 1.0; + + // Frequently used variables for scale invariant computation + // Left and right Vworld to Clip coordinates transforms + private Transform3D left_xform = new Transform3D(); + private Transform3D right_xform = new Transform3D(); + + // Transform for scaling the OrientedShape3D to correct for + // perspective foreshortening + Transform3D scaleXform = new Transform3D(); + + // Variables for converting between clip to local world coords + private Vector4d im_vec[] = {new Vector4d(), new Vector4d()}; + private Vector4d lvec = new Vector4d(); + + boolean orientedTransformDirty = true; + + Transform3D[] orientedTransforms = new Transform3D[1]; + static final double EPSILON = 1.0e-6; + + + /** + * Constructs a OrientedShape3D node with default parameters. + * The default values are as follows: + * <ul> + * alignment mode : ROTATE_ABOUT_AXIS<br> + * alignment axis : Y-axis (0,1,0)<br> + * rotation point : (0,0,1)<br> + *</ul> + */ + public OrientedShape3DRetained() { + super(); + this.nodeType = NodeRetained.ORIENTEDSHAPE3D; + } + + // initializes alignment mode + void initAlignmentMode(int mode) { + this.mode = mode; + } + + /** + * Sets the alignment mode. + * @param mode one of: ROTATE_ABOUT_AXIS or ROTATE_ABOUT_POINT + */ + void setAlignmentMode(int mode) { + if (this.mode != mode) { + initAlignmentMode(mode); + sendChangedMessage(ALIGNMENT_CHANGED, new Integer(mode)); + } + } + + /** + * Retrieves the alignment mode. + * @return one of: ROTATE_ABOUT_AXIS or ROTATE_ABOUT_POINT + */ + int getAlignmentMode() { + return(mode); + } + + // initializes alignment axis + void initAlignmentAxis(Vector3f axis) { + initAlignmentAxis(axis.x, axis.y, axis.z); + } + + // initializes alignment axis + void initAlignmentAxis(float x, float y, float z) { + this.axis.set(x,y,z); + double invMag; + invMag = 1.0/Math.sqrt(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + nAxis.x = (double)axis.x*invMag; + nAxis.y = (double)axis.y*invMag; + nAxis.z = (double)axis.z*invMag; + } + + /** + * Sets the new alignment axis. This is the ray about which this + * OrientedShape3D rotates when the mode is ROTATE_ABOUT_AXIS. + * @param axis the new alignment axis + */ + void setAlignmentAxis(Vector3f axis) { + setAlignmentAxis(axis.x, axis.y, axis.z); + } + + /** + * Sets the new alignment axis. This is the ray about which this + * OrientedShape3D rotates when the mode is ROTATE_ABOUT_AXIS. + * @param x the x component of the alignment axis + * @param y the y component of the alignment axis + * @param z the z component of the alignment axis + */ + void setAlignmentAxis(float x, float y, float z) { + initAlignmentAxis(x,y,z); + + if (mode == OrientedShape3D.ROTATE_ABOUT_AXIS) { + sendChangedMessage(AXIS_CHANGED, new Vector3f(x,y,z)); + } + } + + /** + * Retrieves the alignment axis of this OrientedShape3D node, + * and copies it into the specified vector. + * @param axis the vector that will contain the alignment axis + */ + void getAlignmentAxis(Vector3f axis) { + axis.set(this.axis); + } + + // initializes rotation point + void initRotationPoint(Point3f point) { + rotationPoint.set(point); + } + + // initializes rotation point + void initRotationPoint(float x, float y, float z) { + rotationPoint.set(x,y,z); + } + + /** + * Sets the new rotation point. This is the point about which the + * OrientedShape3D rotates when the mode is ROTATE_ABOUT_POINT. + * @param point the new rotation point + */ + void setRotationPoint(Point3f point) { + setRotationPoint(point.x, point.y, point.z); + } + + /** + * Sets the new rotation point. This is the point about which the + * OrientedShape3D rotates when the mode is ROTATE_ABOUT_POINT. + * @param x the x component of the rotation point + * @param y the y component of the rotation point + * @param z the z component of the rotation point + */ + void setRotationPoint(float x, float y, float z) { + initRotationPoint(x,y,z); + + if (mode == OrientedShape3D.ROTATE_ABOUT_POINT) { + sendChangedMessage(ROTATION_CHANGED, new Point3f(x,y,z)); + } + } + + /** + * Retrieves the rotation point of this OrientedShape3D node, + * and copies it into the specified vector. + * @param axis the point that will contain the rotation point + */ + void getRotationPoint(Point3f point) { + point.set(rotationPoint); + } + + void setConstantScaleEnable(boolean enable) { + if(constantScale != enable) { + initConstantScaleEnable(enable); + sendChangedMessage(CONSTANT_SCALE_CHANGED, new Boolean(enable)); + } + } + + boolean getConstantScaleEnable() { + return constantScale; + } + + void initConstantScaleEnable(boolean cons_scale) { + constantScale = cons_scale; + } + + void setScale(double scale) { + initScale(scale); + if(constantScale) + sendChangedMessage(SCALE_FACTOR_CHANGED, new Double(scale)); + } + + void initScale(double scale) { + scaleFactor = scale; + } + + double getScale() { + return scaleFactor; + } + + void sendChangedMessage(int component, Object attr) { + J3dMessage changeMessage = VirtualUniverse.mc.getMessage(); + changeMessage.type = J3dMessage.ORIENTEDSHAPE3D_CHANGED; + changeMessage.threads = targetThreads ; + changeMessage.universe = universe; + changeMessage.args[0] = getGeomAtomsArray(mirrorShape3D); + changeMessage.args[1] = new Integer(component); + changeMessage.args[2] = attr; + OrientedShape3DRetained[] o3dArr = + new OrientedShape3DRetained[mirrorShape3D.size()]; + mirrorShape3D.toArray(o3dArr); + changeMessage.args[3] = o3dArr; + changeMessage.args[4] = this; + VirtualUniverse.mc.processMessage(changeMessage); + } + + void updateImmediateMirrorObject(Object[] args) { + int component = ((Integer)args[1]).intValue(); + if ((component & (ALIGNMENT_CHANGED | + AXIS_CHANGED | + ROTATION_CHANGED | + CONSTANT_SCALE_CHANGED | + SCALE_FACTOR_CHANGED)) != 0) { + OrientedShape3DRetained[] msArr = (OrientedShape3DRetained[])args[3]; + Object obj = args[2]; + if ((component & ALIGNMENT_CHANGED) != 0) { + int mode = ((Integer)obj).intValue(); + for (int i=0; i< msArr.length; i++) { + msArr[i].initAlignmentMode(mode); + } + } + else if ((component & AXIS_CHANGED) != 0) { + Vector3f axis =(Vector3f) obj; + for (int i=0; i< msArr.length; i++) { + msArr[i].initAlignmentAxis(axis); + } + } + else if ((component & ROTATION_CHANGED) != 0) { + Point3f point =(Point3f) obj; + for (int i=0; i< msArr.length; i++) { + msArr[i].initRotationPoint(point); + } + } + else if((component & CONSTANT_SCALE_CHANGED) != 0) { + boolean bool = ((Boolean)obj).booleanValue(); + for (int i=0; i< msArr.length; i++) { + msArr[i].initConstantScaleEnable(bool); + } + } + else if((component & SCALE_FACTOR_CHANGED) != 0) { + double scale = ((Double)obj).doubleValue(); + for (int i=0; i< msArr.length; i++) { + msArr[i].initScale(scale); + } + } + } + else { + super.updateImmediateMirrorObject(args); + } + } + + + Transform3D getOrientedTransform(int viewIndex) { + synchronized(orientedTransforms) { + if (viewIndex >= orientedTransforms.length) { + Transform3D xform = new Transform3D(); + Transform3D[] newList = new Transform3D[viewIndex+1]; + for (int i = 0; i < orientedTransforms.length; i++) { + newList[i] = orientedTransforms[i]; + } + newList[viewIndex] = xform; + orientedTransforms = newList; + } + else { + if (orientedTransforms[viewIndex] == null) { + orientedTransforms[viewIndex] = new Transform3D(); + } + } + } + return orientedTransforms[viewIndex]; + } + + // called on the parent object + // Should be synchronized so that the user thread does not modify the + // OrientedShape3D params while computing the transform + synchronized void updateOrientedTransform(Canvas3D canvas, int viewIndex) { + double angle = 0.0; + double sign; + boolean status; + + Transform3D orientedxform = getOrientedTransform(viewIndex); + // get viewplatforms's location in virutal world + if (mode == OrientedShape3D.ROTATE_ABOUT_AXIS) { // rotate about axis + canvas.getCenterEyeInImagePlate(viewPosition); + canvas.getImagePlateToVworld(xform); // xform is imagePlateToLocal + xform.transform(viewPosition); + + // get billboard's transform + xform.set(getCurrentLocalToVworld()); + xform.invert(); // xform is now vWorldToLocal + + // transform the eye position into the billboard's coordinate system + xform.transform(viewPosition); + + + // eyeVec is a vector from the local origin to the eye pt in local + eyeVec.set(viewPosition); + eyeVec.normalize(); + + // project the eye into the rotation plane + status = projectToPlane(eyeVec, nAxis); + + if (status) { + // project the z axis into the rotation plane + zAxis.x = 0.0; + zAxis.y = 0.0; + zAxis.z = 1.0; + status = projectToPlane(zAxis, nAxis); + } + if (status) { + + // compute the sign of the angle by checking if the cross product + // of the two vectors is in the same direction as the normal axis + vector.cross(eyeVec, zAxis); + if (vector.dot(nAxis) > 0.0) { + sign = 1.0; + } else { + sign = -1.0; + } + + // compute the angle between the projected eye vector and the + // projected z + + double dot = eyeVec.dot(zAxis); + if (dot > 1.0f) { + dot = 1.0f; + } else if (dot < -1.0f) { + dot = -1.0f; + } + + angle = sign*Math.acos(dot); + + // use -angle because xform is to *undo* rotation by angle + aa.x = nAxis.x; + aa.y = nAxis.y; + aa.z = nAxis.z; + aa.angle = -angle; + orientedxform.set(aa); + } + else { + orientedxform.setIdentity(); + } + + } else { // rotate about point + // Need to rotate Z axis to point to eye, and Y axis to be + // parallel to view platform Y axis, rotating around rotation pt + + // get the eye point + canvas.getCenterEyeInImagePlate(viewPosition); + + // derive the yUp point + yUpPoint.set(viewPosition); + yUpPoint.y += 0.01; // one cm in Physical space + + // transform the points to the Billboard's space + canvas.getImagePlateToVworld(xform); // xform is ImagePlateToVworld + xform.transform(viewPosition); + xform.transform(yUpPoint); + + // get billboard's transform + xform.set(getCurrentLocalToVworld()); + xform.invert(); // xform is vWorldToLocal + + // transfom points to local coord sys + xform.transform(viewPosition); + xform.transform(yUpPoint); + + // Make a vector from viewPostion to 0,0,0 in the BB coord sys + eyeVec.set(viewPosition); + eyeVec.normalize(); + + // create a yUp vector + yUp.set(yUpPoint); + yUp.sub(viewPosition); + yUp.normalize(); + + + // find the plane to rotate z + zAxis.x = 0.0; + zAxis.y = 0.0; + zAxis.z = 1.0; + + // rotation axis is cross product of eyeVec and zAxis + vector.cross(eyeVec, zAxis); // vector is cross product + + // if cross product is non-zero, vector is rotation axis and + // rotation angle is acos(eyeVec.dot(zAxis))); + double length = vector.length(); + if (length > 0.0001) { + double dot = eyeVec.dot(zAxis); + if (dot > 1.0f) { + dot = 1.0f; + } else if (dot < -1.0f) { + dot = -1.0f; + } + angle = Math.acos(dot); + aa.x = vector.x; + aa.y = vector.y; + aa.z = vector.z; + aa.angle = -angle; + zRotate.set(aa); + } else { + // no rotation needed, set to identity (scale = 1.0) + zRotate.set(1.0); + } + + // Transform the yAxis by zRotate + yAxis.x = 0.0; + yAxis.y = 1.0; + yAxis.z = 0.0; + zRotate.transform(yAxis); + + // project the yAxis onto the plane perp to the eyeVec + status = projectToPlane(yAxis, eyeVec); + + + if (status) { + // project the yUp onto the plane perp to the eyeVec + status = projectToPlane(yUp, eyeVec); + } + + if (status) { + // rotation angle is acos(yUp.dot(yAxis)); + double dot = yUp.dot(yAxis); + + // Fix numerical error, otherwise acos return NULL + if (dot > 1.0f) { + dot = 1.0f; + } else if (dot < -1.0f) { + dot = -1.0f; + } + + angle = Math.acos(dot); + + // check the sign by looking a the cross product vs the eyeVec + vector.cross(yUp, yAxis); // vector is cross product + if (eyeVec.dot(vector) < 0) { + angle *= -1; + } + aa.x = eyeVec.x; + aa.y = eyeVec.y; + aa.z = eyeVec.z; + aa.angle = -angle; + xform.set(aa); // xform is now yRotate + + // rotate around the rotation point + vector.x = rotationPoint.x; + vector.y = rotationPoint.y; + vector.z = rotationPoint.z; // vector to translate to RP + orientedxform.set(vector); // translate to RP + orientedxform.mul(xform); // yRotate + orientedxform.mul(zRotate); // zRotate + vector.scale(-1.0); // vector to translate back + xform.set(vector); // xform to translate back + orientedxform.mul(xform); // translate back + } + else { + orientedxform.setIdentity(); + } + + } + //Scale invariant computation + if(constantScale) { + // Back Xform a unit vector to local world coords + canvas.getInverseVworldProjection(left_xform, right_xform); + + // the two endpts of the vector have to be transformed + // individually because the Xform is not affine + im_vec[0].set(0.0, 0.0, 0.0, 1.0); + im_vec[1].set(1.0, 0.0, 0.0, 1.0); + left_xform.transform(im_vec[0]); + left_xform.transform(im_vec[1]); + + left_xform.set(getCurrentLocalToVworld()); + left_xform.invert(); + left_xform.transform(im_vec[0]); + left_xform.transform(im_vec[1]); + lvec.set(im_vec[1]); + lvec.sub(im_vec[0]); + + // We simply need the direction of this vector + lvec.normalize(); + im_vec[0].set(0.0, 0.0, 0.0, 1.0); + im_vec[1].set(lvec); + im_vec[1].w = 1.0; + + // Forward Xfrom to clip coords + left_xform.set(getCurrentLocalToVworld()); + left_xform.transform(im_vec[0]); + left_xform.transform(im_vec[1]); + + canvas.getVworldProjection(left_xform, right_xform); + left_xform.transform(im_vec[0]); + left_xform.transform(im_vec[1]); + + // Perspective division + im_vec[0].x /= im_vec[0].w; + im_vec[0].y /= im_vec[0].w; + im_vec[0].z /= im_vec[0].w; + + im_vec[1].x /= im_vec[1].w; + im_vec[1].y /= im_vec[1].w; + im_vec[1].z /= im_vec[1].w; + + lvec.set(im_vec[1]); + lvec.sub(im_vec[0]); + + // Use the length of this vector to determine the scaling + // factor + double scale = 1/lvec.length(); + + // Convert to meters + scale *= scaleFactor*canvas.getPhysicalWidth()/2; + + // Scale object so that it appears the same size + scaleXform.setScale(scale); + orientedxform.mul(scaleXform); + } + + } + + + private boolean projectToPlane(Vector3d projVec, Vector3d planeVec) { + double dis = planeVec.dot(projVec); + projVec.x = projVec.x - planeVec.x*dis; + projVec.y = projVec.y - planeVec.y*dis; + projVec.z = projVec.z - planeVec.z*dis; + + double length = projVec.length(); + if (length < EPSILON) { // projVec is parallel to planeVec + return false; + } + projVec.scale(1 / length); + return true; + } + + void compile(CompileState compState) { + + super.compile(compState); + + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + + // don't push the static transform to orientedShape3D + // because orientedShape3D is rendered using vertex array; + // it's not worth pushing the transform here + + compState.keepTG = true; + } + + void searchGeometryAtoms(UnorderList list) { + list.add(getGeomAtom(getMirrorShape(key))); + } +} diff --git a/src/classes/share/javax/media/j3d/PathInterpolator.java b/src/classes/share/javax/media/j3d/PathInterpolator.java new file mode 100644 index 0000000..f775459 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PathInterpolator.java @@ -0,0 +1,278 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; + +/** + * PathInterpolator behavior. This class defines the base class for + * all Path Interpolators. Subclasses have access to the + * computePathInterpolation() method, which computes the + * currentInterpolationValue given the current time and alpha. + * The method also computes the currentKnotIndex, which is based on + * the currentInterpolationValue. + * The currentInterpolationValue is calculated + * by linearly interpolating among a series of predefined knots + * (using the value generated by the specified Alpha object). + * The first knot must have a value of 0.0 and the last knot must have a + * value of 1.0. An intermediate knot with index k must have a value + * strictly greater than any knot with index less than k. + */ + +public abstract class PathInterpolator extends TransformInterpolator { + + // Array of knots + private float knots[]; + + /** + * This value is the ratio between knot values indicated by + * the currentKnotIndex variable. So if a subclass wanted to + * interpolate between knot values, it would use the currentKnotIndex + * to get the bounding knots for the "real" value, then use the + * currentInterpolationValue to interpolate between the knots. + * To calculate this variable, a subclass needs to call + * the <code>computePathInterpolation(alphaValue)</code> method from the subclass's + * computeTransform() method. Then this variable will hold a valid + * value which can be used in further calculations by the subclass. + */ + protected float currentInterpolationValue; + + /** + * This value is the index of the current base knot value, as + * determined by the alpha function. A subclass wishing to + * interpolate between bounding knots would use this index and + * the one following it, and would use the currentInterpolationValue + * variable as the ratio between these indices. + * To calculate this variable, a subclass needs to call + * the <code>computePathInterpolation(alphaValue)</code> method from the subclass's + * computeTransform() method. Then this variable will hold a valid + * value which can be used in further calculations by the subclass. + */ + protected int currentKnotIndex; + + + /** + * Constructs a PathInterpolator node with a null alpha value and + * a null target of TransformGroup + * + * since Java 3D 1.3 + */ + PathInterpolator() { + } + + + /** + * Constructs a new PathInterpolator object that interpolates + * between the knot values in the knots array. The array of knots + * is copied into this PathInterpolator object. + * @param alpha the alpha object for this interpolator. + * @param knots an array of knot values that specify interpolation + * points. + * + * @deprecated As of Java 3D version 1.3, replaced by + * <code>PathInterpolator(Alpha, TransformGroup, float[]) </code> + */ + public PathInterpolator(Alpha alpha, float[] knots) { + this(alpha, null, knots); + } + + /** + * Constructs a new PathInterpolator object that interpolates + * between the knot values in the knots array. The array of knots + * is copied into this PathInterpolator object. + * @param alpha the alpha object for this interpolator. + * @param target the transformgroup node effected by this pathInterpolator + * @param knots an array of knot values that specify interpolation + * points. + * + * @since Java 3D 1.3 + */ + + public PathInterpolator(Alpha alpha,TransformGroup target, + float[] knots) { + super(alpha, target); + setKnots(knots); + } + + + /** + * Constructs a new PathInterpolator object that interpolates + * between the knot values in the knots array. The array of knots + * is copied into this PathInterpolator object. + * @param alpha the alpha object for this interpolator. + * @param target the transform node effected by this positionInterpolator + * @param axisOfTransform the transform that defines the local coordinate + * @param knots an array of knot values that specify interpolation + * points. + * + * @since Java 3D 1.3 + */ + public PathInterpolator(Alpha alpha,TransformGroup target, Transform3D axisOfTransform, + float[] knots) { + super(alpha, target, axisOfTransform); + setKnots(knots); + } + + /** + * Retrieves the length of the knots array. + * @return the array length + */ + public int getArrayLengths(){ + return knots.length; + } + + + /** + * Sets the knot at the specified index for this interpolator. + * @param index the index to be changed + * @param knot the new knot value + */ + public void setKnot(int index, float knot) { + this.knots[index] = knot; + } + + + /** + * Retrieves the knot at the specified index. + * @param index the index of the value requested + * @return the interpolator's knot value at the associated index + */ + public float getKnot(int index) { + return this.knots[index]; + } + + + /** + * Replaces the existing array of knot values with + * the specified array. The array of knots is copied into this + * interpolator object. Prior to calling this method, + * subclasses should verify that the lengths of the new knots array + * and subclass-specific parameter arrays are the same. + * @param knots a new array of knot values that specify + * interpolation points. + * + * @since Java 3D 1.2 + */ + protected void setKnots(float[] knots) { + if (knots[0] < -0.0001 || knots[0] > 0.0001) { + throw new IllegalArgumentException(J3dI18N.getString("PathInterpolator0")); + } + + if ((knots[knots.length-1] - 1.0f) < -0.0001 || (knots[knots.length-1] - 1.0f) > 0.0001) { + throw new IllegalArgumentException(J3dI18N.getString("PathInterpolator1")); + } + + this.knots = new float[knots.length]; + for (int i = 0; i < knots.length; i++) { + if (i>0 && knots[i] < knots[i-1]) { + throw new IllegalArgumentException(J3dI18N.getString("PathInterpolator2")); + } + this.knots[i] = knots[i]; + } + } + + + /** + * Copies the array of knots from this interpolator + * into the specified array. + * The array must be large enough to hold all of the knots. + * @param knots array that will receive the knots. + * + * @since Java 3D 1.2 + */ + public void getKnots(float[] knots) { + for (int i = 0; i < this.knots.length; i++) { + knots[i] = this.knots[i]; + } + } + + /** + * Computes the base knot index and interpolation value + * given the specified value of alpha and the knots[] array. If + * the index is 0 and there should be no interpolation, both the + * index variable and the interpolation variable are set to 0. + * Otherwise, currentKnotIndex is set to the lower index of the + * two bounding knot points and the currentInterpolationValue + * variable is set to the ratio of the alpha value between these + * two bounding knot points. + * @param alphaValue alpha value between 0.0 and 1.0 + * + * @since Java 3D 1.3 + */ + protected void computePathInterpolation(float alphaValue ) { + + int i; + + for (i = 0; i < knots.length; i++) { + if ((i == 0 && alphaValue <= knots[i]) || + (i > 0 && alphaValue >= knots[i-1] && alphaValue <= knots[i])) { + + if (i==0) { + currentInterpolationValue = 0f; + currentKnotIndex = 0; + } + else { + currentInterpolationValue = + (alphaValue - knots[i-1])/(knots[i] - knots[i-1]); + currentKnotIndex = i - 1; + } + break; + } + } + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>computePathInterpolation(float)</code> + */ + protected void computePathInterpolation() { + float value = this.alpha.value(); + computePathInterpolation(value); + } + + + /** + * Copies all PathInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + PathInterpolator pi = (PathInterpolator) originalNode; + + int len = pi.getArrayLengths(); + + // No API available to set knots size + knots = new float[len]; + + for (int i = 0; i < len; i++) + setKnot(i, pi.getKnot(i)); + } +} diff --git a/src/classes/share/javax/media/j3d/PhysicalBody.java b/src/classes/share/javax/media/j3d/PhysicalBody.java new file mode 100644 index 0000000..1f08fb7 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PhysicalBody.java @@ -0,0 +1,356 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; + +/** + * This object contains a specification of the user's head. + * Attributes of this object are defined in the head coordinate system. + * The orgin is defined to be halfway between the left and right eye + * in the plane of the face. + * The x-axis extends to the right (of the head looking out from the head). + * The y-axis extends up. The z-axis extends to the rear of the head. + * + * @see View + */ + +public class PhysicalBody extends Object { + // The X offset for each eye is 1/2 of the inter-pupilary distance + // This constant specifies the default IPD. + private static final double HALF_IPD = 0.033; + + // These offsets specify the default ear positions relative to the + // "center eye". + private static final double EAR_X = 0.080; + private static final double EAR_Y = -0.030; + private static final double EAR_Z = 0.095; + + /** + * The user's left eye's position in head coordinates. + */ + Point3d leftEyePosition = new Point3d(-HALF_IPD, 0.0, 0.0); + + /** + * The user's right eye's position in head coordinates. + */ + Point3d rightEyePosition = new Point3d(HALF_IPD, 0.0, 0.0); + + /** + * The user's left ear's position in head coordinates. + */ + Point3d leftEarPosition = new Point3d(-EAR_X, EAR_Y, EAR_Z); + + /** + * The user's right ear's position in head coordinates. + */ + Point3d rightEarPosition = new Point3d(EAR_X, EAR_Y, EAR_Z); + + /** + * The user's nominal eye height as measured + * from the ground plane. + */ + double nominalEyeHeightFromGround = 1.68; + + /** + * The amount to offset the system's + * viewpoint from the user's current eye-point. This offset + * distance allows an "Over the shoulder" view of the scene + * as seen by the user. + * + * By default, we will use a Z value of 0.4572 meters (18 inches). + */ + double nominalEyeOffsetFromNominalScreen = 0.4572; + + // Head to head-tracker coordinate system transform. + // If head tracking is enabled, this transform is a calibration + // constant. If head tracking is not enabled, this transform is + // not used. + // This is used in both SCREEN_VIEW and HMD_VIEW modes. + Transform3D headToHeadTracker = new Transform3D(); + + // A list of View Objects that refer to this + ArrayList users = new ArrayList(); + + // Mask that indicates this PhysicalBody's view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + int pbDirtyMask = (View.PB_EYE_POSITION_DIRTY + | View.PB_EAR_POSITION_DIRTY + | View.PB_NOMINAL_EYE_HEIGHT_FROM_GROUND_DIRTY + | View.PB_NOMINAL_EYE_OFFSET_FROM_NOMINAL_SCREEN_DIRTY); + + /** + * Constructs a PhysicalBody object with default parameters. + * The default values are as follows: + * <ul> + * left eye position : (-0.033, 0.0, 0.0)<br> + * right eye position : (0.033, 0.0, 0.0)<br> + * left ear position : (-0.080, -0.030, 0.095)<br> + * right ear position : (0.080, -0.030, 0.095)<br> + * nominal eye height from ground : 1.68<br> + * nominal eye offset from nominal screen : 0.4572<br> + * head to head tracker transform : identity<br> + * </ul> + */ + public PhysicalBody() { + // Just use the defaults + initHeadToHeadTracker(); + } + + // Add a user to the list of users + synchronized void removeUser(View view) { + int idx = users.indexOf(view); + if (idx >= 0) { + users.remove(idx); + } + } + + // Add a user to the list of users + synchronized void addUser(View view) { + int idx = users.indexOf(view); + if (idx < 0) { + users.add(view); + } + } + + // Add a user to the list of users + synchronized void notifyUsers() { + for (int i=users.size()-1; i>=0; i--) { + View view = (View)users.get(i); + // TODO: notifyUsers should have a parameter denoting field changed + if (view.soundScheduler != null) { + view.soundScheduler.setListenerFlag( + SoundScheduler.EAR_POSITIONS_CHANGED | + SoundScheduler.EYE_POSITIONS_CHANGED ); + } + view.repaint(); + } + } + + /** + * Constructs and initializes a PhysicalBody object from the + * specified parameters. + * @param leftEyePosition the user's left eye position + * @param rightEyePosition the user's right eye position + */ + public PhysicalBody(Point3d leftEyePosition, Point3d rightEyePosition) { + this.leftEyePosition.set(leftEyePosition); + this.rightEyePosition.set(rightEyePosition); + initHeadToHeadTracker(); + } + + /** + * Constructs and initializes a PhysicalBody object from the + * specified parameters. + * @param leftEyePosition the user's left eye position + * @param rightEyePosition the user's right eye position + * @param leftEarPosition the user's left ear position + * @param rightEarPosition the user's right ear position + */ + public PhysicalBody(Point3d leftEyePosition, + Point3d rightEyePosition, + Point3d leftEarPosition, + Point3d rightEarPosition) { + + this.leftEyePosition.set(leftEyePosition); + this.rightEyePosition.set(rightEyePosition); + this.leftEarPosition.set(leftEarPosition); + this.rightEarPosition.set(rightEarPosition); + initHeadToHeadTracker(); + } + + /** + * Returns a string representation of this PhysicalBody's values. + */ + + public String toString() { + return "eyePosition = (" + this.leftEyePosition + ", " + + this.rightEyePosition + ")\n" + + "earPosition = (" + this.leftEarPosition + ", " + + this.rightEarPosition + ")"; + } + + /** + * Retrieves the user head object's left eye position and places + * that value in the specified object. + * @param position the object that will receive the left-eye's position + * in head coordinates + */ + public void getLeftEyePosition(Point3d position) { + position.set(this.leftEyePosition); + } + + /** + * Sets the user head object's left eye position. + * @param position the left-eye's position in head coordinates + */ + public void setLeftEyePosition(Point3d position) { + synchronized(this) { + this.leftEyePosition.set(position); + pbDirtyMask |= View.PB_EYE_POSITION_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the user head object's right eye position and places + * that value in the specified object. + * @param position the object that will receive the right-eye's position + * in head coordinates + */ + public void getRightEyePosition(Point3d position) { + position.set(this.rightEyePosition); + } + + /** + * Sets the user head object's right eye position. + * @param position the right-eye's position in head coordinates + */ + public void setRightEyePosition(Point3d position) { + synchronized(this) { + this.rightEyePosition.set(position); + pbDirtyMask |= View.PB_EYE_POSITION_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the user head object's left ear position and places + * that value in the specified object. + * @param position the object that will receive the left-ear's position + * in head coordinates + */ + public void getLeftEarPosition(Point3d position) { + position.set(this.leftEarPosition); + } + + /** + * Sets the user head object's left ear position. + * @param position the left-ear's position in head coordinates + */ + public void setLeftEarPosition(Point3d position) { + synchronized(this) { + this.leftEarPosition.set(position); + pbDirtyMask |= View.PB_EAR_POSITION_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the user head object's right ear position and places + * that value in the specified object. + * @param position the object that will receive the right-ear's position + * in head coordinates + */ + public void getRightEarPosition(Point3d position) { + position.set(this.rightEarPosition); + } + + /** + * Sets the user head object's right ear position. + * @param position the right-ear's position in head coordinates + */ + public void setRightEarPosition(Point3d position) { + synchronized(this) { + this.rightEarPosition.set(position); + pbDirtyMask |= View.PB_EAR_POSITION_DIRTY; + } + notifyUsers(); + } + + /** + * Sets the nominal eye height from the ground plane. + * This parameter defines + * the distance from the origin of the user's head (the eyepoint) to + * the ground. + * It is used when the view attach policy is NOMINAL_FEET. + * @param height the nominal height of the eye above the ground plane + */ + public void setNominalEyeHeightFromGround(double height) { + synchronized(this) { + nominalEyeHeightFromGround = height; + pbDirtyMask |= View.PB_NOMINAL_EYE_HEIGHT_FROM_GROUND_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the nominal eye height from the ground plane. + * @return the current nominal eye height above the ground plane + */ + public double getNominalEyeHeightFromGround() { + return nominalEyeHeightFromGround; + } + + /** + * Sets the nominal eye offset from the display screen. + * This parameter defines + * the distance from the origin of the user's head (the eyepoint), in it's + * nominal position, to + * the screen. + * It is used when the view attach policy is NOMINAL_HEAD or NOMINAL_FEET. + * This value is overridden to be the actual eyepoint when the window + * eyepoint policy is RELATIVE_TO_FIELD_OF_VIEW. + * @param offset the nominal offset from the eye to the screen + */ + public void setNominalEyeOffsetFromNominalScreen(double offset) { + synchronized(this) { + nominalEyeOffsetFromNominalScreen = offset; + pbDirtyMask |= View.PB_NOMINAL_EYE_OFFSET_FROM_NOMINAL_SCREEN_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the nominal eye offset from the display screen. + * @return the current nominal offset from the eye to the display screen + */ + public double getNominalEyeOffsetFromNominalScreen() { + return nominalEyeOffsetFromNominalScreen; + } + + /** + * Sets the head to head-tracker coordinate system transform. + * If head tracking is enabled, this transform is a calibration + * constant. If head tracking is not enabled, this transform is + * not used. + * This is used in both SCREEN_VIEW and HMD_VIEW modes. + * @param t the new transform + * @exception BadTransformException if the transform is not rigid + */ + public void setHeadToHeadTracker(Transform3D t) { + if (!t.isRigid()) { + throw new BadTransformException(J3dI18N.getString("PhysicalBody0")); + } + headToHeadTracker.setWithLock(t); + notifyUsers(); + } + + /** + * Retrieves the head to head-tracker coordinate system transform. + * @param t the object that will receive the transform + */ + public void getHeadToHeadTracker(Transform3D t) { + t.set(headToHeadTracker); + } + + // Initialize the head to head-tracker transform + private void initHeadToHeadTracker() { + // By default the center of the crystal eyes tracker is 20mm down + // and 35 mm closer to the screen from the origin of head coordinates + // (the center eye). + Vector3d v = new Vector3d(0.0, 0.020, 0.035); + headToHeadTracker.set(v); + } +} diff --git a/src/classes/share/javax/media/j3d/PhysicalEnvironment.java b/src/classes/share/javax/media/j3d/PhysicalEnvironment.java new file mode 100644 index 0000000..c3cf51c --- /dev/null +++ b/src/classes/share/javax/media/j3d/PhysicalEnvironment.java @@ -0,0 +1,513 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.awt.*; +import java.util.*; + +/** + * This object contains a specification of the physical environment in + * which the view will be generated. It is used to set up input + * devices (sensors) for head-tracking and other uses, and the audio + * output device. Sensors are indexed starting at zero. + * + * @see View + */ + +public class PhysicalEnvironment extends Object { + /** + * The Sensor Index associated with the Head + */ + int HeadIndex = 0; + + // The Sensor index associated with the Right Hand + int RightHandIndex = 1; + + // The Sensor index associated with the Left Hand + int LeftHandIndex = 2; + + // The current Dominant Hand Sensor Index + int DominantHandIndex = 1; + + // The current Non Dominant Hand Sensor Index + int NonDominantHandIndex = 2; + + // + // Coexistence coordinate system to tracker-base coordinate + // system transform. If head tracking is enabled, this transform + // is a calibration constant. If head tracking is not enabled, + // this transform is not used. + // This is used in both SCREEN_VIEW and HMD_VIEW modes. + // + Transform3D coexistenceToTrackerBase = new Transform3D(); + + // + // Indicates whether the underlying hardware implementation + // supports tracking. + // + boolean trackingAvailable = false; + + // The view associated with this physical environment + // View view; + + // + // This variable specifies the policy Java 3D will use in placing + // the user's eye position relative to the user's head position + // (NOMINAL_SCREEN, NOMINAL_HEAD, or NOMINAL_FEET). + // It is used in the calibration process. + // + // TODO: this needs better explanation in the spec + int coexistenceCenterInPworldPolicy = View.NOMINAL_SCREEN; + + // Mask that indicates this PhysicalEnv's view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + int peDirtyMask = (View.PE_COE_TO_TRACKER_BASE_DIRTY + | View.PE_TRACKING_AVAILABLE_DIRTY + | View.PE_COE_CENTER_IN_PWORLD_POLICY_DIRTY); + + +//// /** +//// * The offset in the user's dominant-hand-tracker coordinates +//// * to that hand's hot spot. This value is a calibration constant. +//// */ +//// Vector3d dominantHandTrackerHotspotOffset; +//// +//// /** +//// * The offset in the user's non-dominant-hand-tracker coordinates +//// * to that hand's hot spot. This value is a calibration constant. +//// */ +//// Vector3d nondominantHandTrackerHotspotOffset; + + // + // The number of sensor stored within the PhysicalEnvironment + // + int sensorCount; + + // + // Array of sensors + // + Sensor[] sensors; + + // Audio device associated with this PhysicalEnvironment + AudioDevice audioDevice = null; + + boolean sensorListChanged = false; + + Sensor[] sensorList = null; + + // A list of View Objects that refer to this + ArrayList users = new ArrayList(); + + // Scheduler for input devices + InputDeviceScheduler inputsched; + + // store all inputDevices + Vector devices = new Vector(1); + + // Number of active view users + int activeViewRef = 0; + + // Hashtable that maps a PhysicalEnvironment to its InputDeviceScheduler + static Hashtable physicalEnvMap = new Hashtable(); + + /** + * Constructs a PhysicalEnvironment object with default parameters. + * The default values are as follows: + * <ul> + * sensor count : 3<br> + * sensors : null (for all array elements)<br> + * head index : 0<br> + * right hand index : 1<br> + * left hand index : 2<br> + * dominant hand index : 1<br> + * nondominant hand index : 2<br> + * tracking available : false<br> + * audio device : null<br> + * input device list : empty<br> + * coexistence to tracker base transform : identity<br> + * coexistence center in pworld policy : View.NOMINAL_SCREEN<br> + * </ul> + */ + public PhysicalEnvironment() { + this(3); + } + + // Add a user to the list of users + synchronized void removeUser(View view) { + int idx = users.indexOf(view); + if (idx >= 0) { + users.remove(idx); + } + } + + // Add a user to the list of users + synchronized void addUser(View view) { + int idx = users.indexOf(view); + if (idx < 0) { + users.add(view); + } + } + + // Add a user to the list of users + synchronized void notifyUsers() { + for (int i=users.size()-1; i>=0; i--) { + View view = (View)users.get(i); + view.repaint(); + } + } + + /** + * Constructs and initializes a PhysicalEnvironment object with + * the specified number of sensors. + * @param sensorCount the number of sensors to create. + */ + public PhysicalEnvironment(int sensorCount) { + this.sensorCount = sensorCount; + sensors = new Sensor[sensorCount]; + for(int i=sensorCount-1; i>=0; i--) { + sensors[i] = null; + } + } + + + + /** + * Returns copy of Sensor references. Returns null for zero + * sensors, so user of method must check for null. Also, any of + * these sensors could be null. + */ + Sensor[] getSensorList() { + synchronized(sensors) { + if(sensorListChanged) { // note: this is a rare case + sensorList = new Sensor[sensors.length]; + for(int i=0 ; i<sensors.length ; i++) { + sensorList[i] = sensors[i]; + } + sensorListChanged = false; + + } + return sensorList; + } + } + + + /** + * Sets the specified AudioDevice object as the device through + * which audio rendering for this PhysicalEnvironment will be + * performed. + * @param device audio device object to be associated with this + * PhysicalEnvironment + */ + public void setAudioDevice(AudioDevice device) { + audioDevice = device; + } + + /** + * Gets the audioDevice for this PhysicalEnvironment. + * @return audio device object associated with this PhysicalEnvironment + */ + public AudioDevice getAudioDevice(){ + return audioDevice; + } + + /** + * Create an enumerator that produces all input devices. + * @return an enumerator of all available devices + */ + public Enumeration getAllInputDevices() { + return devices.elements(); + } + + /** + * Add an input device to the list of input devices. User is + * responsible for initializing the device and setting the + * processing mode (streaming or polling). + * @param device the device to be added to the list of input devices + * @exception IllegalArgumentException if InputDevice.getProcessingMode() + * does not return one of BLOCKING, NON_BLOCKING, or DEMAND_DRIVEN. + */ + public void addInputDevice(InputDevice device) { + + int driver_type = device.getProcessingMode(); + + if ((driver_type == InputDevice.BLOCKING) || + (driver_type == InputDevice.NON_BLOCKING) || + (driver_type == InputDevice.DEMAND_DRIVEN)) { + synchronized (devices) { + devices.add(device); + if (inputsched != null) { + inputsched.addInputDevice(device); + } + } + } else { + throw new IllegalArgumentException(J3dI18N.getString("PhysicalEnvironment0")); + } + } + + /** + * Remove an input device from the list of input devices. + * User is responsible for closing out the device and releasing + * the device resources. + * @param device the device to be removed + */ + public void removeInputDevice(InputDevice device) { + devices.remove(device); + synchronized (devices) { + if (inputsched != null) { + inputsched.removeInputDevice(device); + } + } + } + + /** + * Sets the index of the head to the specified sensor index. + * @param index the new sensor index of the head + */ + public void setHeadIndex(int index) { + HeadIndex = index; + synchronized(this) { + computeTrackingAvailable(); + peDirtyMask |= View.PE_TRACKING_AVAILABLE_DIRTY; + } + notifyUsers(); + } + + /** + * Gets the sensor index of the head. + * @return the sensor index of the head + */ + public int getHeadIndex() { + return HeadIndex; + } + + /** + * Sets the index of the right hand to the specified sensor index. + * @param index the new sensor index of the right hand + */ + public void setRightHandIndex(int index) { + RightHandIndex = index; + notifyUsers(); + } + + /** + * Gets the sensor index of the right hand. + * @return the sensor index of the right hand + */ + public int getRightHandIndex() { + return RightHandIndex; + } + + /** + * Sets the index of the left hand to the specified sensor index. + * @param index the new sensor index of the left hand + */ + public void setLeftHandIndex(int index) { + LeftHandIndex = index; + notifyUsers(); + } + + /** + * Gets the sensor index of the left hand. + * @return the sensor index of the left hand + */ + public int getLeftHandIndex() { + return LeftHandIndex; + } + + /** + * Sets the index of the dominant hand to the specified sensor index. + * @param index the new sensor index of the dominant hand + */ + public void setDominantHandIndex(int index) { + DominantHandIndex = index; + notifyUsers(); + } + + /** + * Gets the sensor index of the dominant hand. + * @return the sensor index of the dominant hand + */ + public int getDominantHandIndex() { + return DominantHandIndex; + } + + /** + * Sets the index of the non-dominant hand to the specified sensor index. + * @param index the new sensor index of the non dominant hand + */ + public void setNonDominantHandIndex(int index) { + NonDominantHandIndex = index; + notifyUsers(); + } + + /** + * Gets the sensor index of the non-dominant hand. + * @return the sensor index of the non dominant hand + */ + public int getNonDominantHandIndex() { + return NonDominantHandIndex; + } + + /** + * Set the sensor specified by the index to sensor provided; sensors are + * indexed starting at 0. All sensors must be registered via this + * method. + * @param index the sensor's index + * @param sensor the new sensor + */ + public void setSensor(int index, Sensor sensor) { + synchronized(sensors) { + sensors[index] = sensor; + sensorListChanged = true; + } + synchronized(this) { + computeTrackingAvailable(); + peDirtyMask |= View.PE_TRACKING_AVAILABLE_DIRTY; + } + + notifyUsers(); + } + + /** + * Gets the sensor specified by the index; sensors are indexed starting + * at 0. + * @param index the sensor's index + */ + public Sensor getSensor(int index){ + // not synchronized, since the only way to write to sensors is + // via a public API call, and user shouldn't call Sensor with + // two threads + return sensors[index]; + } + + /** + * Sets the coexistence coordinate system to tracker-base coordinate + * system transform. If head tracking is enabled, this transform + * is a calibration constant. If head tracking is not enabled, + * this transform is not used. + * This is used in both SCREEN_VIEW and HMD_VIEW modes. + * @param t the new transform + * @exception BadTransformException if the transform is not rigid + */ + public void setCoexistenceToTrackerBase(Transform3D t) { + if (!t.isRigid()) { + throw new BadTransformException(J3dI18N.getString("PhysicalEnvironment1")); + } + synchronized(this) { + coexistenceToTrackerBase.setWithLock(t); + peDirtyMask |= View.PE_COE_TO_TRACKER_BASE_DIRTY; + } + + notifyUsers(); + } + + /** + * Retrieves the coexistence coordinate system to tracker-base + * coordinate system transform and copies it into the specified + * Transform3D object. + * @param t the object that will receive the transform + */ + public void getCoexistenceToTrackerBase(Transform3D t) { + t.set(coexistenceToTrackerBase); + } + + /** + * Returns a status flag indicating whether or not tracking + * is available. + * @return a flag telling whether tracking is available + */ + public boolean getTrackingAvailable() { + return this.trackingAvailable; + } + + /** + * Sets the coexistence center in physical world policy. + * This setting determines how Java 3D places the + * user's eye point as a function of head position during the + * calibration process, one of View.NOMINAL_SCREEN, + * View.NOMINAL_HEAD, or View.NOMINAL_FEET. + * The default policy is View.NOMINAL_SCREEN. + * @param policy the new policy + */ + public void setCoexistenceCenterInPworldPolicy(int policy) { + switch (policy) { + case View.NOMINAL_SCREEN: + case View.NOMINAL_HEAD: + case View.NOMINAL_FEET: + break; + + default: + throw new IllegalArgumentException(J3dI18N.getString("PhysicalEnvironment2")); + } + + synchronized(this) { + this.coexistenceCenterInPworldPolicy = policy; + peDirtyMask |= View.PE_COE_CENTER_IN_PWORLD_POLICY_DIRTY; + } + notifyUsers(); + } + + /** + * Returns the current coexistence center in physical world policy. + * @return one of: View.NOMINAL_SCREEN, View.NOMINAL_HEAD, or + * View.NOMINAL_FEET + */ + public int getCoexistenceCenterInPworldPolicy() { + return this.coexistenceCenterInPworldPolicy; + } + + /** + * Get the current sensor count. + * @return the number of sensor objects per PhysicalEnvironment object + */ + public int getSensorCount() { + return sensorCount; + } + + /** + * Set the number of sensor objects per PhysicalEnvironmnet. This is a + * calibration parameter that should be set before setting any sensors + * in the PhysicalEnvironment object. This call associates 'count' + * Sensors with this object, and they are indexed from 0 to count-1. + * @param count the new sensor count + */ + public void setSensorCount(int count) { + + Sensor[] tmp = new Sensor[count]; + int i=0; + + synchronized(sensors) { + int min = Math.min(count, sensorCount); + while(i < min) { + tmp[i] = sensors[i++]; + } + while(i < count) { + tmp[i++] = null; + } + sensorCount = count; + sensorListChanged = true; + sensors = tmp; + } + notifyUsers(); + } + + // (re-)compute the tracking available flag + private void computeTrackingAvailable() { + synchronized(sensors) { + trackingAvailable = ((HeadIndex < sensors.length) && + (sensors[HeadIndex] != null)); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/PickBounds.java b/src/classes/share/javax/media/j3d/PickBounds.java new file mode 100644 index 0000000..6316749 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickBounds.java @@ -0,0 +1,89 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * PickBounds is a finite pick shape defined with a Bounds object. It can + * be used as an argument to the picking methods in BranchGroup and Locale. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + */ +public final class PickBounds extends PickShape { + + Bounds bounds; + + /** + * Constructs an empty PickBounds. The bounds object is set to null. + */ + public PickBounds() { + bounds = null; + } + + /** + * Constructs a PickBounds from the specified bounds object. + * @param boundsObject the bounds of this PickBounds. + */ + public PickBounds(Bounds boundsObject) { + bounds = boundsObject; + } + + + /** + * Sets the bounds object of this PickBounds to the specified object. + * @param boundsObject the new bounds of this PickBounds. + */ + public void set(Bounds boundsObject) { + bounds = boundsObject; + } + + /** + * Gets the bounds object from this PickBounds. + * @return the bounds. + */ + public Bounds get() { + return bounds; + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + */ + final boolean intersect(Bounds bounds, Point4d pickPos) { + return bounds.intersect(this.bounds, pickPos); + } + + // Only use within J3D. + // Return a new PickBounds that is the transformed (t3d) of this pickBounds. + PickShape transform(Transform3D t3d) { + // If the bounds is a BoundingBox, then the transformed bounds will + // get bigger. So this is a potential bug, and we'll have to deal with + // if there is a complain. + Bounds newBds = (Bounds)bounds.clone(); + newBds.transform(t3d); + PickBounds newPB = new PickBounds(newBds); + + return newPB; + } + + Point3d getStartPoint() { + return bounds.getCenter(); + } + + int getPickType() { + return (bounds != null ? bounds.getPickType() : + PickShape.PICKUNKNOWN); + } +} diff --git a/src/classes/share/javax/media/j3d/PickCone.java b/src/classes/share/javax/media/j3d/PickCone.java new file mode 100644 index 0000000..b9940bd --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickCone.java @@ -0,0 +1,89 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * PickCone is the abstract base class of all cone pick shapes. + * + * @since Java 3D 1.2 + */ +public abstract class PickCone extends PickShape { + + Point3d origin; + Vector3d direction; + double spreadAngle; + + /** + * Constructs an empty PickCone. + * The origin and direction of the cone are + * initialized to (0,0,0). The spread angle is initialized + * to <code>PI/64</code>. + */ + public PickCone() { + this.origin = new Point3d(); + this.direction = new Vector3d(); + this.spreadAngle = Math.PI / 64.0; + } + + /** + * Gets the origin of this PickCone. + * @param origin the Point3d object into which the origin will be copied. + */ + public void getOrigin(Point3d origin) { + origin.set(this.origin); + } + + /** + * Gets the direction of this PickCone. + * @param direction the Vector3d object into which the direction + * will be copied. + */ + public void getDirection(Vector3d direction) { + direction.set(this.direction); + } + + + /** + * Gets the spread angle of this PickCone. + * @return the spread angle. + */ + public double getSpreadAngle() { + return spreadAngle; + } + + /** + * Gets the radius of this PickCone at the specified distance. + * @param distance the distance from the origin at which we want + * the radius of the cone + * @return the radius at the specified distance + */ + double getRadius(double distance) { + return distance * Math.tan (spreadAngle); + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + */ + abstract boolean intersect(Bounds bounds, Point4d pickPos); + + Point3d getStartPoint() { + return origin; + } + + int getPickType() { + return PICKCONE; + } +} diff --git a/src/classes/share/javax/media/j3d/PickConeRay.java b/src/classes/share/javax/media/j3d/PickConeRay.java new file mode 100644 index 0000000..e420e59 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickConeRay.java @@ -0,0 +1,266 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import com.sun.j3d.internal.Distance; + +/** + * PickConeRay is an infinite cone ray pick shape. It can + * be used as an argument to the picking methods in BranchGroup and Locale. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + * + * @since Java 3D 1.2 + */ +public final class PickConeRay extends PickCone { + + /** + * Constructs an empty PickConeRay. + * The origin and direction of the cone are + * initialized to (0,0,0). The spread angle is initialized + * to <code>PI/64</code> radians. + */ + public PickConeRay() { + } + + /** + * Constructs an infinite cone pick shape from the specified + * parameters. + * @param origin the origin of the cone + * @param direction the direction of the cone + * @param spreadAngle the spread angle of the cone in radians + */ + public PickConeRay(Point3d origin, Vector3d direction, double spreadAngle) { + this.origin = new Point3d(origin); + this.direction = new Vector3d(direction); + this.spreadAngle = spreadAngle; + } + + /** + * Sets the parameters of this PickCone to the specified values. + * @param origin the origin of the cone + * @param direction the direction of the cone + * @param spreadAngle the spread angle of the cone in radians + */ + public void set(Point3d origin, Vector3d direction, double spreadAngle) { + this.origin.set(origin); + this.direction.set(direction); + this.spreadAngle = spreadAngle; + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + * @param bounds the bounds object to check + * @param pickPos the location of the point of intersection (not used for + * method. Provided for compatibility). + */ + final boolean intersect(Bounds bounds, Point4d pickPos) { + + Point4d iPnt = new Point4d(); + Vector3d vector = new Vector3d(); + double distance; + double radius; + Point3d rayPt = new Point3d(); + + // + // ================ BOUNDING SPHERE ================ + // + if (bounds instanceof BoundingSphere) { + Point3d sphCenter = ((BoundingSphere)bounds).getCenter(); + double sphRadius = ((BoundingSphere)bounds).getRadius(); + double sqDist = + Distance.pointToRay (sphCenter, origin, direction, rayPt, null); + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + if (sqDist <= (sphRadius+radius)*(sphRadius+radius)) { + return true; + } + return false; // we are too far to intersect + } + // + // ================ BOUNDING BOX ================ + // + else if (bounds instanceof BoundingBox) { + // Calculate radius of BoundingBox + Point3d lower = new Point3d(); + ((BoundingBox)bounds).getLower (lower); + + Point3d center = ((BoundingBox)bounds).getCenter (); + + // First, see if cone is too far away from BoundingBox + double sqDist = + Distance.pointToRay (center, origin, direction, rayPt, null); + + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + + double temp = (center.x - lower.x + radius); + double boxRadiusSquared = temp*temp; + temp = (center.y - lower.y + radius); + boxRadiusSquared += temp*temp; + temp = (center.z - lower.z + radius); + boxRadiusSquared += temp*temp; + + + if (sqDist > boxRadiusSquared) { + return false; // we are too far to intersect + } + else if (sqDist < (radius*radius)) { + return true; // center is in cone + } + + // Then, see if ray intersects + if (((BoundingBox)bounds).intersect (origin, direction, iPnt)) { + return true; + } + + // Ray does not intersect, test for distance with each edge + Point3d upper = new Point3d(); + ((BoundingBox)bounds).getUpper (upper); + + Point3d[][] edges = { + // Top horizontal 4 + {upper, new Point3d (lower.x, upper.y, upper.z)}, + {new Point3d(lower.x, upper.y, upper.z), new Point3d(lower.x, lower.y, upper.z)}, + {new Point3d(lower.x, lower.y, upper.z), new Point3d(upper.x, lower.y, upper.z)}, + {new Point3d(upper.x, lower.y, upper.z), upper}, + // Bottom horizontal 4 + {lower, new Point3d(lower.x, upper.y, lower.z)}, + {new Point3d(lower.x, upper.y, lower.z), new Point3d(upper.x, upper.y, lower.z)}, + {new Point3d(upper.x, upper.y, lower.z), new Point3d(upper.x, lower.y, lower.z)}, + {new Point3d(upper.x, lower.y, lower.z), lower}, + // Vertical 4 + {lower, new Point3d(lower.x, lower.y, upper.z)}, + {new Point3d(lower.x, upper.y, lower.z), new Point3d(lower.x, upper.y, upper.z)}, + {new Point3d(upper.x, upper.y, lower.z), new Point3d(upper.x, upper.y, upper.z)}, + {new Point3d(upper.x, lower.y, lower.z), new Point3d(upper.x, lower.y, upper.z)} + }; + + for (int i=0;i<edges.length;i++) { + // System.out.println ("Testing edge: "+edges[i][0]+" - "+edges[i][1]); + double distToEdge = + Distance.rayToSegment (origin, direction, edges[i][0], edges[i][1], + rayPt, null, null); + + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + // System.out.println ("PickConeRay: distance: "+distance+" radius:"+radius); + if (distToEdge <= radius*radius) { + // System.out.println ("Intersects!"); + return true; + } + } + return false; // Not close enough + } + // + // ================ BOUNDING POLYTOPE ================ + // + else if (bounds instanceof BoundingPolytope) { + int i, j; + + // First, check to see if we are too far to intersect the polytope's + // bounding sphere + Point3d sphCenter = new Point3d(); + BoundingSphere bsphere = new BoundingSphere (bounds); + + bsphere.getCenter (sphCenter); + double sphRadius = bsphere.getRadius(); + + double sqDist = + Distance.pointToRay (sphCenter, origin, direction, rayPt, null); + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + if (sqDist > (sphRadius+radius)*(sphRadius+radius)) { + return false; // we are too far to intersect + } + + // Now check to see if ray intersects with polytope + if (bounds.intersect (origin, direction, iPnt)) { + return true; + } + + // Now check distance to edges. Since we don't know a priori how + // the polytope is structured, we will cycle through. We discard edges + // when their center is not on the polytope surface. + BoundingPolytope ptope = (BoundingPolytope)bounds; + Point3d midpt = new Point3d(); + double distToEdge; + for (i=0;i<ptope.nVerts;i++) { + for (j=i;i<ptope.nVerts;i++) { + // TODO: make BoundingPolytope.pointInPolytope available to package + // scope + midpt.x = (ptope.verts[i].x + ptope.verts[j].x) * 0.5; + midpt.y = (ptope.verts[i].y + ptope.verts[j].y) * 0.5; + midpt.z = (ptope.verts[i].z + ptope.verts[j].z) * 0.5; + + if (! PickCylinder.pointInPolytope (ptope, + midpt.x, midpt.y, midpt.z)) { + continue; + } + distToEdge = + Distance.rayToSegment (origin, direction, + ptope.verts[i], ptope.verts[j], + rayPt, null, null); + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + if (distToEdge <= radius*radius) { + return true; + } + } + } + return false; + } + /* + else { + throw new RuntimeException("intersect method not implemented"); + } + */ + return false; + } + + // Only use within J3D. + // Return a new PickConeRay that is the transformed (t3d) of this pickConeRay. + PickShape transform(Transform3D t3d) { + + Point3d end = new Point3d(); + + PickConeRay newPCR = new PickConeRay(); + + newPCR.origin.x = origin.x; + newPCR.origin.y = origin.y; + newPCR.origin.z = origin.z; + newPCR.spreadAngle = spreadAngle; + + end.x = origin.x + direction.x; + end.y = origin.y + direction.y; + end.z = origin.z + direction.z; + + t3d.transform(newPCR.origin); + t3d.transform(end); + + newPCR.direction.x = end.x - newPCR.origin.x; + newPCR.direction.y = end.y - newPCR.origin.y; + newPCR.direction.z = end.z - newPCR.origin.z; + newPCR.direction.normalize(); + + return newPCR; + } +} diff --git a/src/classes/share/javax/media/j3d/PickConeSegment.java b/src/classes/share/javax/media/j3d/PickConeSegment.java new file mode 100644 index 0000000..f839040 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickConeSegment.java @@ -0,0 +1,292 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import com.sun.j3d.internal.Distance; + +/** + * PickConeSegment is a finite cone segment pick shape. It can + * be used as an argument to the picking methods in BranchGroup and Locale. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + * + * @since Java 3D 1.2 + */ +public final class PickConeSegment extends PickCone { + + Point3d end; + + /** + * Constructs an empty PickConeSegment. + * The origin and end point of the cone are + * initialized to (0,0,0). The spread angle is initialized + * to <code>PI/64</code> radians. + */ + public PickConeSegment() { + end = new Point3d(); + } + + /** + * Constructs a finite cone pick shape from the specified + * parameters. + * @param origin the origin of the cone + * @param end the end of the cone along the direction vector + * @param spreadAngle the spread angle of the cone in radians + */ + public PickConeSegment (Point3d origin, Point3d end, double spreadAngle) { + this.origin = new Point3d(origin); + this.end = new Point3d(end); + this.direction = new Vector3d(); + this.spreadAngle = spreadAngle; + calcDirection(); // calculate direction, based on start and end + } + + /** + * Sets the parameters of this PickCone to the specified values. + * @param origin the origin of the cone + * @param end the end of the cone + * @param spreadAngle the spread angle of the cone in radians + */ + public void set(Point3d origin, Point3d end, double spreadAngle) { + this.origin.set(origin); + this.end.set (end); + this.spreadAngle = spreadAngle; + calcDirection(); // calculate direction, based on start and end + } + + /** + * Gets the end point of this PickConeSegment. + * @param end the Point3d object into which the end point + * will be copied. + */ + public void getEnd(Point3d end) { + end.set(this.end); + } + + /** Calculates the direction for this PickCylinderSegment, based on start + and end points. + */ + private void calcDirection() { + this.direction.x = end.x - origin.x; + this.direction.y = end.y - origin.y; + this.direction.z = end.z - origin.z; + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + * @param bounds the bounds object to check + * @param pickPos the location of the point of intersection (not used for + * method. Provided for compatibility). + */ + final boolean intersect(Bounds bounds, Point4d pickPos) { + Point4d iPnt = new Point4d(); + Vector3d vector = new Vector3d(); + Point3d rayPt = new Point3d(); + + double distance; + double radius; + + // + // ================ BOUNDING SPHERE ================ + // + if (bounds instanceof BoundingSphere) { + Point3d sphCenter = ((BoundingSphere)bounds).getCenter(); + double sphRadius = ((BoundingSphere)bounds).getRadius(); + double sqDist = + Distance.pointToSegment (sphCenter, origin, end, rayPt, null); + + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + if (sqDist <= (sphRadius+radius)*(sphRadius+radius)) { + return true; + } + return false; // we are too far to intersect + } + // + // ================ BOUNDING BOX ================ + // + else if (bounds instanceof BoundingBox) { + // Calculate radius of BoundingBox + Point3d lower = new Point3d(); + ((BoundingBox)bounds).getLower (lower); + + Point3d center = ((BoundingBox)bounds).getCenter (); + + // First, see if cone is too far away from BoundingBox + double sqDist = + Distance.pointToSegment (center, origin, end, rayPt, null); + + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + + double temp = (center.x - lower.x + radius); + double boxRadiusSquared = temp*temp; + temp = (center.y - lower.y + radius); + boxRadiusSquared += temp*temp; + temp = (center.z - lower.z + radius); + boxRadiusSquared += temp*temp; + + if (sqDist > boxRadiusSquared) { + return false; // we are too far to intersect + } + else if (sqDist < (radius*radius)) { + return true; // center is in cone + } + + // Then, see if ray intersects + if (((BoundingBox)bounds).intersect (origin, direction, iPnt)) { + return true; + } + + // Ray does not intersect, test for distance with each edge + Point3d upper = new Point3d(); + ((BoundingBox)bounds).getUpper (upper); + + Point3d[][] edges = { + // Top horizontal 4 + {upper, new Point3d (lower.x, upper.y, upper.z)}, + {new Point3d(lower.x, upper.y, upper.z), new Point3d(lower.x, lower.y, upper.z)}, + {new Point3d(lower.x, lower.y, upper.z), new Point3d(upper.x, lower.y, upper.z)}, + {new Point3d(upper.x, lower.y, upper.z), upper}, + // Bottom horizontal 4 + {lower, new Point3d(lower.x, upper.y, lower.z)}, + {new Point3d(lower.x, upper.y, lower.z), new Point3d(upper.x, upper.y, lower.z)}, + {new Point3d(upper.x, upper.y, lower.z), new Point3d(upper.x, lower.y, lower.z)}, + {new Point3d(upper.x, lower.y, lower.z), lower}, + // Vertical 4 + {lower, new Point3d(lower.x, lower.y, upper.z)}, + {new Point3d(lower.x, upper.y, lower.z), new Point3d(lower.x, upper.y, upper.z)}, + {new Point3d(upper.x, upper.y, lower.z), new Point3d(upper.x, upper.y, upper.z)}, + {new Point3d(upper.x, lower.y, lower.z), new Point3d(upper.x, lower.y, upper.z)} + }; + for (int i=0;i<edges.length;i++) { + // System.out.println ("Testing edge: "+edges[i][0]+" - "+edges[i][1]); + double distToEdge = + Distance.segmentToSegment (origin, end, edges[i][0], edges[i][1], + rayPt, null, null); + + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + /* System.out.println ("PickConeSegment: distance: " + + distance+" radius: " + radius + + " distToEdge:" +Math.sqrt(distToEdge)); + */ + + if (distToEdge <= radius*radius) { + // System.out.println ("Intersects!"); + return true; + } + } + return false; // Not close enough + } + // + // ================ BOUNDING POLYTOPE ================ + // + else if (bounds instanceof BoundingPolytope) { + int i, j; + + // First, check to see if we are too far to intersect the polytope's + // bounding sphere + Point3d sphCenter = new Point3d(); + BoundingSphere bsphere = new BoundingSphere (bounds); + + bsphere.getCenter (sphCenter); + double sphRadius = bsphere.getRadius(); + + double sqDist = + Distance.pointToSegment (sphCenter, origin, end, rayPt, null); + + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + + if (sqDist > (sphRadius+radius)*(sphRadius+radius)) { + return false; // we are too far to intersect + } + + // Now check to see if ray intersects with polytope + if (bounds.intersect (origin, direction, iPnt)) { + return true; + } + + // Now check distance to edges. Since we don't know a priori how + // the polytope is structured, we will cycle through. We discard edges + // when their center is not on the polytope surface. + BoundingPolytope ptope = (BoundingPolytope)bounds; + Point3d midpt = new Point3d(); + double distToEdge; + for (i=0;i<ptope.nVerts;i++) { + for (j=i;i<ptope.nVerts;i++) { + // TODO: make BoundingPolytope.pointInPolytope available to package + // scope + midpt.x = (ptope.verts[i].x + ptope.verts[j].x) * 0.5; + midpt.y = (ptope.verts[i].y + ptope.verts[j].y) * 0.5; + midpt.z = (ptope.verts[i].z + ptope.verts[j].z) * 0.5; + + if (! PickCylinder.pointInPolytope (ptope, + midpt.x, midpt.y, midpt.z)) { + continue; + } + distToEdge = + Distance.segmentToSegment (origin, end, + ptope.verts[i], ptope.verts[j], + rayPt, null, null); + vector.sub (rayPt, origin); + distance = vector.length(); + radius = getRadius (distance); + if (distToEdge <= radius*radius) { + return true; + } + } + } + return false; + } + /* + else { + throw new RuntimeException("intersect method not implemented"); + } + */ + return false; + } + + // Only use within J3D. + // Return a new PickConeSegment that is the transformed (t3d) of this pickConeSegment. + PickShape transform(Transform3D t3d) { + + PickConeSegment newPCS = new PickConeSegment(); + + newPCS.origin.x = origin.x; + newPCS.origin.y = origin.y; + newPCS.origin.z = origin.z; + newPCS.spreadAngle = spreadAngle; + newPCS.end.x = end.x; + newPCS.end.y = end.y; + newPCS.end.z = end.z; + + t3d.transform(newPCS.origin); + t3d.transform(newPCS.end); + + newPCS.direction.x = newPCS.end.x - newPCS.origin.x; + newPCS.direction.y = newPCS.end.y - newPCS.origin.y; + newPCS.direction.z = newPCS.end.z - newPCS.origin.z; + newPCS.direction.normalize(); + + return newPCS; + } +} diff --git a/src/classes/share/javax/media/j3d/PickCylinder.java b/src/classes/share/javax/media/j3d/PickCylinder.java new file mode 100644 index 0000000..85d1161 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickCylinder.java @@ -0,0 +1,97 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * PickCylinder is the abstract base class of all cylindrical pick shapes. + * + * @since Java 3D 1.2 + */ +public abstract class PickCylinder extends PickShape { + + Point3d origin; + Vector3d direction; + double radius; + + /** + * Constructs an empty PickCylinder. + * The origin of the cylinder is + * initialized to (0,0,0). The radius is initialized + * to 0. + */ + public PickCylinder() { + origin = new Point3d(); + direction = new Vector3d(); + radius = 0.0; + } + + /** + * Gets the origin point of this cylinder object. + * @param origin the Point3d object into which the origin + * point will be copied + */ + public void getOrigin(Point3d origin) { + origin.set(this.origin); + } + + /** + * Gets the radius of this cylinder object + * @return the radius in radians + */ + public double getRadius() { + return radius; + } + + /** + * Gets the direction of this cylinder. + * @param direction the Vector3d object into which the direction + * will be copied + */ + public void getDirection(Vector3d direction) { + direction.set(this.direction); + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + */ + abstract boolean intersect(Bounds bounds, Point4d pickPos); + + // This is a duplicate of the same method, declared private inside of + // BoundingPolytope + // TODO: remove this once the original method is available (public) in + // BoundingPolytope + static boolean pointInPolytope(BoundingPolytope ptope, + double x, double y, double z ){ + Vector4d p; + int i = ptope.planes.length - 1; + + while (i >= 0) { + p = ptope.planes[i--]; + if (( x*p.x + y*p.y + z*p.z + p.w ) > Bounds.EPSILON) { + return false; + } + } + return true; + } + + Point3d getStartPoint() { + return origin; + } + + int getPickType() { + return PICKCYLINDER; + } +} diff --git a/src/classes/share/javax/media/j3d/PickCylinderRay.java b/src/classes/share/javax/media/j3d/PickCylinderRay.java new file mode 100644 index 0000000..3c58782 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickCylinderRay.java @@ -0,0 +1,248 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import com.sun.j3d.internal.Distance; + +/** + * PickCylinderRay is an infinite cylindrical ray pick shape. It can + * be used as an argument to the picking methods in BranchGroup and Locale. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + * + * @since Java 3D 1.2 + */ + +public final class PickCylinderRay extends PickCylinder { + + /** + * Constructs an empty PickCylinderRay. + * The origin and direction of the cylindrical ray are + * initialized to (0,0,0). The radius is initialized + * to 0. + */ + public PickCylinderRay() { + } + + /** + * Constructs an infinite cylindrical ray pick shape from the specified + * parameters. + * @param origin the origin of the cylindrical ray. + * @param direction the direction of the cylindrical ray. + * @param radius the radius of the cylindrical ray. + */ + public PickCylinderRay(Point3d origin, Vector3d direction, double radius) { + this.origin = new Point3d(origin); + this.direction = new Vector3d(direction); + this.radius = radius; + } + + + /** + * Sets the parameters of this PickCylinderRay to the specified values. + * @param origin the origin of the cylindrical ray. + * @param direction the direction of the cylindrical ray. + * @param radius the radius of the cylindrical ray. + */ + public void set(Point3d origin, Vector3d direction, double radius) { + this.origin.set(origin); + this.direction.set(direction); + this.radius = radius; + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + * @param bounds the bounds object to check + * @param pickPos the location of the point of intersection (not used for + * method. Provided for compatibility). + */ + final boolean intersect(Bounds bounds, Point4d pickPos) { + Point4d iPnt = new Point4d(); + + // + // ================ BOUNDING SPHERE ================ + // + if (bounds instanceof BoundingSphere) { + Point3d sphCenter = ((BoundingSphere)bounds).getCenter(); + double sphRadius = ((BoundingSphere)bounds).getRadius(); + double sqDist = + Distance.pointToRay (sphCenter, origin, direction); + if (sqDist <= (sphRadius+radius)*(sphRadius+radius)) { + return true; + } + return false; + } + // + // ================ BOUNDING BOX ================ + // + else if (bounds instanceof BoundingBox) { + // Calculate radius of BoundingBox + Point3d lower = new Point3d(); + ((BoundingBox)bounds).getLower (lower); + + Point3d center = ((BoundingBox)bounds).getCenter (); + + double temp = (center.x - lower.x + radius); + double boxRadiusSquared = temp*temp; + temp = (center.y - lower.y + radius); + boxRadiusSquared += temp*temp; + temp = (center.z - lower.z + radius); + boxRadiusSquared += temp*temp; + + // First, see if cylinder is too far away from BoundingBox + double sqDist = + Distance.pointToRay (center, origin, direction); + + if (sqDist > boxRadiusSquared ) { + return false; // we are too far to intersect + } + else if (sqDist < (radius*radius)) { + return true; // center is in cylinder + } + + // Then, see if ray intersects + if (bounds.intersect (origin, direction, iPnt)) { + return true; + } + + // Ray does not intersect, test for distance with each edge + Point3d upper = new Point3d(); + ((BoundingBox)bounds).getUpper (upper); + + Point3d[][] edges = { + // Top horizontal 4 + {upper, new Point3d (lower.x, upper.y, upper.z)}, + {new Point3d(lower.x, upper.y, upper.z), new Point3d(lower.x, lower.y, upper.z)}, + {new Point3d(lower.x, lower.y, upper.z), new Point3d(upper.x, lower.y, upper.z)}, + {new Point3d(upper.x, lower.y, upper.z), upper}, + // Bottom horizontal 4 + {lower, new Point3d(lower.x, upper.y, lower.z)}, + {new Point3d(lower.x, upper.y, lower.z), new Point3d(upper.x, upper.y, lower.z)}, + {new Point3d(upper.x, upper.y, lower.z), new Point3d(upper.x, lower.y, lower.z)}, + {new Point3d(upper.x, lower.y, lower.z), lower}, + // Vertical 4 + {lower, new Point3d(lower.x, lower.y, upper.z)}, + {new Point3d(lower.x, upper.y, lower.z), new Point3d(lower.x, upper.y, upper.z)}, + {new Point3d(upper.x, upper.y, lower.z), new Point3d(upper.x, upper.y, upper.z)}, + {new Point3d(upper.x, lower.y, lower.z), new Point3d(upper.x, lower.y, upper.z)} + }; + + for (int i=0;i<edges.length;i++) { + // System.out.println ("Testing edge: "+edges[i][0]+" - "+edges[i][1]); + double distToEdge = + Distance.rayToSegment (origin, direction, edges[i][0], edges[i][1]); + if (distToEdge <= radius*radius) { + // System.out.println ("Intersects!"); + return true; + } + } + + return false; // Not close enough + } + // + // ================ BOUNDING POLYTOPE ================ + // + else if (bounds instanceof BoundingPolytope) { + int i, j; + + // First, check to see if we are too far to intersect the polytope's + // bounding sphere + Point3d sphCenter = new Point3d(); + BoundingSphere bsphere = new BoundingSphere (bounds); + + bsphere.getCenter (sphCenter); + double sphRadius = bsphere.getRadius(); + + double sqDist = + Distance.pointToRay (sphCenter, origin, direction); + if (sqDist > (sphRadius+radius) * (sphRadius+radius)) { + return false; // we are too far to intersect + } + + // Now check to see if ray intersects with polytope + if (bounds.intersect (origin, direction, iPnt)) { + return true; + } + + // Now check distance to edges. Since we don't know a priori how + // the polytope is structured, we will cycle through. We discard edges + // when their center is not on the polytope surface. + BoundingPolytope ptope = (BoundingPolytope)bounds; + Point3d midpt = new Point3d(); + double distToEdge; + for (i=0;i<ptope.nVerts;i++) { + for (j=i;i<ptope.nVerts;i++) { + // TODO: make BoundingPolytope.pointInPolytope available to package + // scope + midpt.x = (ptope.verts[i].x + ptope.verts[j].x) * 0.5; + midpt.y = (ptope.verts[i].y + ptope.verts[j].y) * 0.5; + midpt.z = (ptope.verts[i].z + ptope.verts[j].z) * 0.5; + + if (! PickCylinder.pointInPolytope (ptope, + midpt.x, midpt.y, midpt.z)) { + continue; + } + distToEdge = + Distance.rayToSegment (origin, direction, + ptope.verts[i], ptope.verts[j]); + if (distToEdge <= radius*radius) { + return true; + } + } + } + return false; + } + /* + else { + throw new RuntimeException("intersect method not implemented"); + } + */ + return false; + } + + + // Only use within J3D. + // Return a new PickCylinderRay that is the transformed (t3d) of this pickCylinderRay. + PickShape transform(Transform3D t3d) { + + PickCylinderRay newPCR = new PickCylinderRay(); + Point3d end = new Point3d(); + /* + System.out.println("t3d : "); + System.out.println(t3d); + */ + newPCR.origin.x = origin.x; + newPCR.origin.y = origin.y; + newPCR.origin.z = origin.z; + newPCR.radius = radius * t3d.getScale(); + + end.x = origin.x + direction.x; + end.y = origin.y + direction.y; + end.z = origin.z + direction.z; + + t3d.transform(newPCR.origin); + t3d.transform(end); + + newPCR.direction.x = end.x - newPCR.origin.x; + newPCR.direction.y = end.y - newPCR.origin.y; + newPCR.direction.z = end.z - newPCR.origin.z; + newPCR.direction.normalize(); + + return newPCR; + } + +} + diff --git a/src/classes/share/javax/media/j3d/PickCylinderSegment.java b/src/classes/share/javax/media/j3d/PickCylinderSegment.java new file mode 100644 index 0000000..b6d2af8 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickCylinderSegment.java @@ -0,0 +1,262 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import com.sun.j3d.internal.Distance; + +/** + * PickCylinderSegment is a finite cylindrical segment pick shape. It can + * be used as an argument to the picking methods in BranchGroup and Locale. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + * + * @since Java 3D 1.2 + */ + +public final class PickCylinderSegment extends PickCylinder { + + Point3d end; + + /** + * Constructs an empty PickCylinderSegment. + * The origin and end points of the cylindrical segment are + * initialized to (0,0,0). The radius is initialized + * to 0. + */ + public PickCylinderSegment() { + this.end = new Point3d(); + } + + /** + * Constructs a finite cylindrical segment pick shape from the specified + * parameters. + * @param origin the origin point of the cylindrical segment. + * @param end the end point of the cylindrical segment. + * @param radius the radius of the cylindrical segment. + */ + public PickCylinderSegment(Point3d origin, Point3d end, double radius) { + this.origin = new Point3d(origin); + this.end = new Point3d(end); + this.radius = radius; + calcDirection(); // calculate direction, based on start and end + } + + /** + * Sets the parameters of this PickCylinderSegment to the specified values. + * @param origin the origin point of the cylindrical segment. + * @param end the end point of the cylindrical segment. + * @param radius the radius of the cylindrical segment. + */ + public void set(Point3d origin, Point3d end, double radius) { + this.origin.set(origin); + this.end.set(end); + this.radius = radius; + calcDirection(); // calculate direction, based on start and end + } + + /** Calculates the direction for this PickCylinderSegment, based on start + and end points. + */ + private void calcDirection() { + this.direction.x = end.x - origin.x; + this.direction.y = end.y - origin.y; + this.direction.z = end.z - origin.z; + } + + /** + * Gets the end point of this PickCylinderSegment. + * @param end the Point3d object into which the end point + * will be copied. + */ + public void getEnd(Point3d end) { + end.set(this.end); + } + + /** + * Returns true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + * @param bounds the bounds object to check + * @param pickPos the location of the point of intersection (not used for + * method. Provided for compatibility). + */ + final boolean intersect(Bounds bounds, Point4d pickPos) { + Point4d iPnt = new Point4d(); + + // + // ================ BOUNDING SPHERE ================ + // + if (bounds instanceof BoundingSphere) { + Point3d sphCenter = ((BoundingSphere)bounds).getCenter(); + double sphRadius = ((BoundingSphere)bounds).getRadius(); + double sqDist = + Distance.pointToSegment (sphCenter, origin, end); + if (sqDist <= (sphRadius+radius)*(sphRadius+radius)) { + return true; + } + return false; // we are too far to intersect + } + // + // ================ BOUNDING BOX ================ + // + else if (bounds instanceof BoundingBox) { + // Calculate radius of BoundingBox + Point3d lower = new Point3d(); + ((BoundingBox)bounds).getLower (lower); + + Point3d center = ((BoundingBox)bounds).getCenter (); + + double temp = (center.x - lower.x + radius); + double boxRadiusSquared = temp*temp; + temp = (center.y - lower.y + radius); + boxRadiusSquared += temp*temp; + temp = (center.z - lower.z + radius); + boxRadiusSquared += temp*temp; + + // First, see if cylinder is too far away from BoundingBox + double sqDist = + Distance.pointToSegment (center, origin, end); + if (sqDist > boxRadiusSquared) { + return false; // we are too far to intersect + } + else if (sqDist < (radius*radius)) { + return true; // center is in cylinder + } + + // Then, see if ray intersects + if (((BoundingBox)bounds).intersect (origin, end, iPnt)) { + return true; + } + + // Ray does not intersect, test for distance with each edge + Point3d upper = new Point3d(); + ((BoundingBox)bounds).getUpper (upper); + + Point3d[][] edges = { + // Top horizontal 4 + {upper, new Point3d (lower.x, upper.y, upper.z)}, + {new Point3d(lower.x, upper.y, upper.z), new Point3d(lower.x, lower.y, upper.z)}, + {new Point3d(lower.x, lower.y, upper.z), new Point3d(upper.x, lower.y, upper.z)}, + {new Point3d(upper.x, lower.y, upper.z), upper}, + // Bottom horizontal 4 + {lower, new Point3d(lower.x, upper.y, lower.z)}, + {new Point3d(lower.x, upper.y, lower.z), new Point3d(upper.x, upper.y, lower.z)}, + {new Point3d(upper.x, upper.y, lower.z), new Point3d(upper.x, lower.y, lower.z)}, + {new Point3d(upper.x, lower.y, lower.z), lower}, + // Vertical 4 + {lower, new Point3d(lower.x, lower.y, upper.z)}, + {new Point3d(lower.x, upper.y, lower.z), new Point3d(lower.x, upper.y, upper.z)}, + {new Point3d(upper.x, upper.y, lower.z), new Point3d(upper.x, upper.y, upper.z)}, + {new Point3d(upper.x, lower.y, lower.z), new Point3d(upper.x, lower.y, upper.z)} + }; + for (int i=0;i<edges.length;i++) { + // System.out.println ("Testing edge: "+edges[i][0]+" - "+edges[i][1]); + double distToEdge = + Distance.segmentToSegment (origin, end, + edges[i][0], edges[i][1]); + if (distToEdge <= radius*radius) { + // System.out.println ("Intersects!"); + return true; + } + } + return false; // Not close enough + } + // + // ================ BOUNDING POLYTOPE ================ + // + else if (bounds instanceof BoundingPolytope) { + int i, j; + + // First, check to see if we are too far to intersect the polytope's + // bounding sphere + Point3d sphCenter = new Point3d(); + BoundingSphere bsphere = new BoundingSphere (bounds); + + bsphere.getCenter (sphCenter); + double sphRadius = bsphere.getRadius(); + + double sqDist = + Distance.pointToSegment (sphCenter, origin, end); + if (sqDist > (sphRadius+radius)*(sphRadius+radius)) { + return false; // we are too far to intersect + } + + // Now check to see if ray intersects with polytope + if (bounds.intersect (origin, direction, iPnt)) { + return true; + } + + // Now check distance to edges. Since we don't know a priori how + // the polytope is structured, we will cycle through. We discard edges + // when their center is not on the polytope surface. + BoundingPolytope ptope = (BoundingPolytope)bounds; + Point3d midpt = new Point3d(); + double distToEdge; + for (i=0;i<ptope.nVerts;i++) { + for (j=i;i<ptope.nVerts;i++) { + // TODO: make BoundingPolytope.pointInPolytope available to package + // scope + midpt.x = (ptope.verts[i].x + ptope.verts[j].x) * 0.5; + midpt.y = (ptope.verts[i].y + ptope.verts[j].y) * 0.5; + midpt.z = (ptope.verts[i].z + ptope.verts[j].z) * 0.5; + + if (! PickCylinder.pointInPolytope (ptope, + midpt.x, midpt.y, midpt.z)) { + continue; + } + distToEdge = + Distance.segmentToSegment (origin, end, + ptope.verts[i], ptope.verts[j]); + if (distToEdge <= radius*radius) { + return true; + } + } + } + return false; + } + /* + else { + throw new RuntimeException("intersect method not implemented"); + } + */ + return false; + } + + // Only use within J3D. + // Return a new PickCylinderSegment that is the transformed (t3d) of this + // pickCylinderSegment. + PickShape transform(Transform3D t3d) { + + PickCylinderSegment newPCS = new PickCylinderSegment(); + + newPCS.origin.x = origin.x; + newPCS.origin.y = origin.y; + newPCS.origin.z = origin.z; + newPCS.radius = radius * t3d.getScale(); + newPCS.end.x = end.x; + newPCS.end.y = end.y; + newPCS.end.z = end.z; + + t3d.transform(newPCS.origin); + t3d.transform(newPCS.end); + + newPCS.direction.x = newPCS.end.x - newPCS.origin.x; + newPCS.direction.y = newPCS.end.y - newPCS.origin.y; + newPCS.direction.z = newPCS.end.z - newPCS.origin.z; + newPCS.direction.normalize(); + + return newPCS; + } + +} diff --git a/src/classes/share/javax/media/j3d/PickPoint.java b/src/classes/share/javax/media/j3d/PickPoint.java new file mode 100644 index 0000000..074233b --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickPoint.java @@ -0,0 +1,94 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * PickPoint is a pick shape defined as a single point. It can + * be used as an argument to the picking methods in BranchGroup and Locale. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + */ +public final class PickPoint extends PickShape { + + Point3d location; + + /** + * Constructs a PickPoint using a default point. + * The point is initialized to (0,0,0). + */ + public PickPoint() { + location = new Point3d(); + } + + /** + * Constructs a PickPoint from the specified parameter. + * @param location the pick point. + */ + public PickPoint(Point3d location) { + this.location = new Point3d(location); + } + + /** + * Sets the position of this PickPoint to the specified value. + * @param location the new pick point. + */ + public void set(Point3d location) { + this.location.x = location.x; + this.location.y = location.y; + this.location.z = location.z; + } + + /** + * Gets the position of this PickPoint. + * @return the current pick point. + */ + public void get(Point3d location) { + location.x = this.location.x; + location.y = this.location.y; + location.z = this.location.z; + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + */ + final boolean intersect(Bounds bounds, Point4d pickPos) { + return bounds.intersect(location, pickPos); + } + + // Only use within J3D. + // Return a new PickPoint that is the transformed (t3d) of this pickPoint. + PickShape transform(Transform3D t3d) { + + PickPoint newPPt = new PickPoint(); + + newPPt.location.x = location.x; + newPPt.location.y = location.y; + newPPt.location.z = location.z; + + t3d.transform(newPPt.location); + + return newPPt; + } + + Point3d getStartPoint() { + return location; + } + + int getPickType() { + return PICKPOINT; + } +} diff --git a/src/classes/share/javax/media/j3d/PickRay.java b/src/classes/share/javax/media/j3d/PickRay.java new file mode 100644 index 0000000..13d2eef --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickRay.java @@ -0,0 +1,119 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * PickRay is an infinite ray pick shape. It can + * be used as an argument to the picking methods in BranchGroup and Locale. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + */ +public final class PickRay extends PickShape { + + Point3d origin; + Vector3d direction; + + /** + * Constructs an empty PickRay. The origin and direction of the + * ray are initialized to (0,0,0). + */ + public PickRay() { + origin = new Point3d(); + direction = new Vector3d(); + } + + /** + * Constructs an infinite ray pick shape from the specified + * parameters. + * @param origin the origin of the ray. + * @param direction the direction of the ray. + */ + public PickRay(Point3d origin, Vector3d direction) { + this.origin = new Point3d(origin); + this.direction = new Vector3d(direction); + } + + + /** + * Sets the parameters of this PickRay to the specified values. + * @param origin the origin of the ray. + * @param direction the direction of the ray. + */ + public void set(Point3d origin, Vector3d direction) { + this.origin.x = origin.x; + this.origin.y = origin.y; + this.origin.z = origin.z; + this.direction.x = direction.x; + this.direction.y = direction.y; + this.direction.z = direction.z; + } + + /** + * Retrieves the parameters from this PickRay. + * @param origin the Point3d object into which the origin will be copied. + * @param direction the Vector3d object into which the direction + * will be copied. + */ + public void get(Point3d origin, Vector3d direction) { + origin.x = this.origin.x; + origin.y = this.origin.y; + origin.z = this.origin.z; + direction.x = this.direction.x; + direction.y = this.direction.y; + direction.z = this.direction.z; + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + */ + final boolean intersect(Bounds bounds, Point4d pickPos) { + return bounds.intersect(origin, direction, pickPos); + } + + + // Only use within J3D. + // Return a new PickRay that is the transformed (t3d) of this pickRay. + PickShape transform(Transform3D t3d) { + + Point3d end = new Point3d(); + + PickRay newPR = new PickRay(origin, direction); + + end.x = origin.x + direction.x; + end.y = origin.y + direction.y; + end.z = origin.z + direction.z; + + t3d.transform(newPR.origin); + t3d.transform(end); + + newPR.direction.x = end.x - newPR.origin.x; + newPR.direction.y = end.y - newPR.origin.y; + newPR.direction.z = end.z - newPR.origin.z; + newPR.direction.normalize(); + + return newPR; + } + + + Point3d getStartPoint() { + return origin; + } + + int getPickType() { + return PICKRAY; + } +} diff --git a/src/classes/share/javax/media/j3d/PickSegment.java b/src/classes/share/javax/media/j3d/PickSegment.java new file mode 100644 index 0000000..f3d50f2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickSegment.java @@ -0,0 +1,106 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * PickSegment is a line segment pick shape. It can + * be used as an argument to the picking methods in BranchGroup and Locale. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + */ +public final class PickSegment extends PickShape { + + Point3d start; + Point3d end; + + /** + * Constructs an empty PickSegment. + * The start and end points of the line segment are + * initialized to (0,0,0). + */ + public PickSegment() { + this.start = new Point3d(); + this.end = new Point3d(); + } + + /** + * Constructs a line segment pick shape from the specified + * parameters. + * @param start the start point of the line segment. + * @param end the end point of the line segment. + */ + public PickSegment(Point3d start, Point3d end) { + this.start = new Point3d(start); + this.end = new Point3d(end); + } + + /** + * Sets the parameters of this PickSegment to the specified values. + * @param start the start point of the line segment. + * @param end the end point of the line segment. + */ + public void set(Point3d start, Point3d end) { + this.start.x = start.x; + this.start.y = start.y; + this.start.z = start.z; + this.end.x = end.x; + this.end.y = end.y; + this.end.z = end.z; + } + + /** + * Gets the parameters from this PickSegment. + * @param start the Point3d object into which the start + * point will be copied. + * @param end the Point3d object into which the end point + * will be copied. + */ + public void get(Point3d start, Point3d end) { + start.x = this.start.x; + start.y = this.start.y; + start.z = this.start.z; + end.x = this.end.x; + end.y = this.end.y; + end.z = this.end.z; + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + */ + final boolean intersect(Bounds bounds, Point4d pickPos) { + return bounds.intersect(start, end, pickPos); + } + + + + // Only use within J3D. + // Return a new PickSegment that is the transformed (t3d) of this pickSegment. + PickShape transform(Transform3D t3d) { + PickSegment newPS = new PickSegment(start, end); + t3d.transform(newPS.start); + t3d.transform(newPS.end); + return newPS; + } + + Point3d getStartPoint() { + return start; + } + + int getPickType() { + return PICKSEGMENT; + } +} diff --git a/src/classes/share/javax/media/j3d/PickShape.java b/src/classes/share/javax/media/j3d/PickShape.java new file mode 100644 index 0000000..afad4cf --- /dev/null +++ b/src/classes/share/javax/media/j3d/PickShape.java @@ -0,0 +1,71 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.Point4d; +import javax.vecmath.Point3d; + +/** + * An abstract class for describing a pick shape that can be used with + * the BranchGroup and Locale picking methods. + * + * @see BranchGroup#pickAll + * @see Locale#pickAll + */ +public abstract class PickShape { + + // Use for picking + static final int PICKRAY = 1; + static final int PICKSEGMENT = 2; + static final int PICKPOINT = 3; + static final int PICKCYLINDER = 4; + static final int PICKCONE = 5; + static final int PICKBOUNDINGBOX = 6; + static final int PICKBOUNDINGSPHERE = 7; + static final int PICKBOUNDINGPOLYTOPE = 8; + static final int PICKUNKNOWN = 9; + + /** + * Constructs a PickShape object. + */ + public PickShape() { + } + + /** + * Return true if shape intersect with bounds. + * The point of intersection is stored in pickPos. + */ + abstract boolean intersect(Bounds bounds, Point4d pickPos); + + // Only use within J3D. + // Return a new PickShape that is the transformed (t3d) of this pickShape. + abstract PickShape transform(Transform3D t3d); + + // Get the start point use to compute the distance + // with intersect point for this shape. + abstract Point3d getStartPoint(); + + // Return the distance between the original of this + // pickShape and iPnt + double distance(Point3d iPnt) { + Point3d p = getStartPoint(); + double x = iPnt.x - p.x; + double y = iPnt.y - p.y; + double z = iPnt.z - p.z; + return Math.sqrt(x*x + y*y + z*z); + } + + // Return one of PickShape type constant define above + abstract int getPickType(); + +} + diff --git a/src/classes/share/javax/media/j3d/Picking.java b/src/classes/share/javax/media/j3d/Picking.java new file mode 100644 index 0000000..11df565 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Picking.java @@ -0,0 +1,660 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.*; + + +/** + * Internal class that implements picking functionality. + */ + +class Picking { + + static SceneGraphPath[] pickAll(Locale locale, PickShape shape) { + if(locale == null) { + return null; + } + + GeometryAtom geomAtoms[] = + locale.universe.geometryStructure.pickAll(locale, shape); + if ((geomAtoms == null) || (geomAtoms.length == 0)) { + // although getSceneGraphPath() also return null, we + // save time for synchronization + return null; + } + synchronized (locale.universe.sceneGraphLock) { + return getSceneGraphPath(null, null, geomAtoms, locale); + } + } + + + + static SceneGraphPath[] pickAll(BranchGroup node, + PickShape shape) { + if (node == null) { + return null; + } + + BranchGroupRetained nodeR = (BranchGroupRetained) node.retained; + + if (nodeR.inSharedGroup) { + throw new RestrictedAccessException(J3dI18N.getString("Picking0")); + } + + Locale locale = nodeR.locale; + + GeometryAtom geomAtoms[] = + locale.universe.geometryStructure.pickAll(locale, shape); + + if ((geomAtoms == null) || (geomAtoms.length == 0)) { + return null; + } + + synchronized (nodeR.universe.sceneGraphLock) { + return getSceneGraphPath(initSceneGraphPath(nodeR), + nodeR, geomAtoms, locale); + } + } + + + static SceneGraphPath[] pickAllSorted(Locale locale, + PickShape shape) { + if(locale == null) { + return null; + } + + GeometryAtom geomAtoms[] = + locale.universe.geometryStructure.pickAll(locale, shape); + + if ((geomAtoms == null) || (geomAtoms.length == 0)) { + return null; + } + + sortGeomAtoms(geomAtoms, shape); + + synchronized (locale.universe.sceneGraphLock) { + return getSceneGraphPath(null, null, geomAtoms, locale); + } + } + + + static SceneGraphPath[] pickAllSorted(BranchGroup node, + PickShape shape) { + if (node == null) { + return null; + } + + BranchGroupRetained nodeR = (BranchGroupRetained) node.retained; + + if (nodeR.inSharedGroup) { + throw new RestrictedAccessException(J3dI18N.getString("Picking0")); + } + + Locale locale = nodeR.locale; + + GeometryAtom geomAtoms[] = + locale.universe.geometryStructure.pickAll(locale, shape); + + if ((geomAtoms == null) || (geomAtoms.length == 0)) { + return null; + } + + // we have to sort first before eliminate duplicate Text3D + // since we want the closest geometry atoms of Text3D + sortGeomAtoms(geomAtoms, shape); + + synchronized (nodeR.universe.sceneGraphLock) { + return getSceneGraphPath(initSceneGraphPath(nodeR), + nodeR, geomAtoms, locale); + } + } + + + static SceneGraphPath pickClosest(Locale locale, + PickShape shape) { + + if(locale == null) { + return null; + } + + GeometryAtom geomAtoms[] = + locale.universe.geometryStructure.pickAll(locale, shape); + + + if ((geomAtoms == null) || (geomAtoms.length == 0)) { + return null; + } + + + GeometryAtom geomAtom = selectClosest(geomAtoms, shape); + + + synchronized (locale.universe.sceneGraphLock) { + return getSceneGraphPath(null, null, geomAtom, locale); + } + } + + + static SceneGraphPath pickClosest(BranchGroup node, + PickShape shape) { + if (node == null) { + return null; + } + + BranchGroupRetained nodeR = (BranchGroupRetained) node.retained; + + if (nodeR.inSharedGroup) { + throw new RestrictedAccessException(J3dI18N.getString("Picking0")); + } + + Locale locale = nodeR.locale; + + GeometryAtom geomAtoms[] = + locale.universe.geometryStructure.pickAll(locale, shape); + + + + if ((geomAtoms == null) || (geomAtoms.length == 0)) { + return null; + } + + + // We must sort all since the closest one in geomAtoms may not + // under the BranchGroup node + sortGeomAtoms(geomAtoms, shape); + + synchronized (nodeR.universe.sceneGraphLock) { + return getFirstSceneGraphPath(initSceneGraphPath(nodeR), + nodeR, geomAtoms, locale); + + } + } + + + static SceneGraphPath pickAny(Locale locale, PickShape shape) { + + if(locale == null) { + return null; + } + + GeometryAtom geomAtom = + locale.universe.geometryStructure.pickAny(locale, shape); + + if (geomAtom == null) { + return null; + } + + + synchronized (locale.universe.sceneGraphLock) { + return getSceneGraphPath(null, null, geomAtom, locale); + } + } + + static SceneGraphPath pickAny(BranchGroup node, PickShape shape) { + + if (node == null) { + return null; + } + + BranchGroupRetained nodeR = (BranchGroupRetained) node.retained; + + if (nodeR.inSharedGroup) { + throw new RestrictedAccessException(J3dI18N.getString("Picking0")); + } + + Locale locale = nodeR.locale; + + // since PickAny return from geometry may not lie under + // BranchGroup node, we have to use pickAll + + GeometryAtom geomAtoms[] = + locale.universe.geometryStructure.pickAll(locale, shape); + + if ((geomAtoms == null) || (geomAtoms.length == 0)) { + return null; + } + + synchronized (nodeR.universe.sceneGraphLock) { + return getFirstSceneGraphPath(initSceneGraphPath(nodeR), + nodeR, geomAtoms, locale); + } + } + + + /** + * Search the path from nodeR up to Locale. + * Return the search path as ArrayList if found. + * Note that the locale will not insert into path. + */ + static private ArrayList initSceneGraphPath(NodeRetained nodeR) { + ArrayList path = new ArrayList(5); + + do { + if (nodeR.source.getCapability(Node.ENABLE_PICK_REPORTING)){ + path.add(nodeR); + } + nodeR = nodeR.parent; + } while (nodeR != null); // reach Locale + + return path; + } + + /** + * return all SceneGraphPath[] of the geomAtoms. + * If initpath is null, the path is search from + * geomAtom Shape3D/Morph Node up to Locale + * (assume the same locale). + * Otherwise, the path is search up to node or + * null is return if it is not hit. + */ + static private SceneGraphPath[] getSceneGraphPath(ArrayList initpath, + BranchGroupRetained node, + GeometryAtom geomAtoms[], + Locale locale) { + + ArrayList paths = new ArrayList(5); + GeometryAtom geomAtom; + NodeRetained target; + ArrayList texts = null; + + if (geomAtoms == null) { + return null; + } + + + for (int i=0; i < geomAtoms.length; i++) { + geomAtom = (GeometryAtom) geomAtoms[i]; + Shape3DRetained shape = geomAtom.source; + + // isPickable and currentSwitchOn has been check in BHTree + + if (!inside(shape.branchGroupPath, node)) { + continue; + } + + target = shape.sourceNode; + + if (target == null) { + // The node is just detach from branch so sourceNode = null + continue; + } + + // Special case, for Text3DRetained, it is possible + // for different geomAtoms pointing to the same + // source Text3DRetained. So we need to combine + // those cases and report only once. + if (target instanceof Shape3DRetained) { + Shape3DRetained s3dR = (Shape3DRetained) target; + GeometryRetained geomR = null; + for(int cnt=0; cnt<s3dR.geometryList.size(); cnt++) { + geomR = (GeometryRetained) s3dR.geometryList.get(cnt); + if(geomR != null) + break; + } + + if (geomR == null) + continue; + + if (geomR instanceof Text3DRetained) { + // assume this case is not frequent, we allocate + // ArrayList only when necessary and we use ArrayList + // instead of HashMap since the case of when large + // number of distingish Text3DRetained node hit is + // rare. + if (texts == null) { + texts = new ArrayList(3); + } else { + int size = texts.size(); + boolean found = false; + for (int j=0; j < size; j++) { + if (texts.get(j) == target) { + found = true; + break; + } + } + if (found) { + continue; // try next geomAtom + } + } + texts.add(target); + } + } + + ArrayList path = retrievePath(target, node, + geomAtom.source.key); + + if (path == null) { + continue; + } + + // If target is instance of compile retained, then loop thru + // the entire source list and add it to the scene graph path + if (target instanceof Shape3DCompileRetained) { + Shape3DCompileRetained s3dCR = (Shape3DCompileRetained)target; + Node[] mpath = mergePath(path, initpath); + for (int n = 0; n < s3dCR.srcList.length; n++) { + SceneGraphPath sgpath = new SceneGraphPath(locale, + mpath, + (Node) s3dCR.srcList[n]); + sgpath.setTransform(shape.getCurrentLocalToVworld(0)); + paths.add(sgpath); + } + + } + else { + SceneGraphPath sgpath = new SceneGraphPath(locale, + mergePath(path, initpath), + (Node) target.source); + sgpath.setTransform(shape.getCurrentLocalToVworld(0)); + paths.add(sgpath); + } + + + } + SceneGraphPath pathArray[] = new SceneGraphPath[paths.size()]; + return (SceneGraphPath []) paths.toArray(pathArray); + } + + /** + * return the SceneGraphPath of the geomAtom. + * If initpath is null, the path is search from + * geomAtom Shape3D/Morph Node up to Locale + * (assume the same locale). + * Otherwise, the path is search up to node or + * null is return if it is not hit. + */ + static private SceneGraphPath getSceneGraphPath(ArrayList initpath, + BranchGroupRetained node, + GeometryAtom geomAtom, + Locale locale) { + if (geomAtom == null) { + return null; + } + + Shape3DRetained shape = geomAtom.source; + NodeRetained target = shape.sourceNode; + + if (target == null) { + // The node is just detach from branch so sourceNode = null + return null; + } + + if (!inside(shape.branchGroupPath, node)) { + return null; + } + + ArrayList path = retrievePath(target, node, shape.key); + + if (path == null) { + return null; + } + + SceneGraphPath sgpath = new SceneGraphPath(locale, + mergePath(path, initpath), + (Node) + target.source); + sgpath.setTransform(shape.getCurrentLocalToVworld(0)); + return sgpath; + } + + /** + * Return true if bg is inside cachedBG or bg is null + */ + static private boolean inside(BranchGroupRetained bgArr[], + BranchGroupRetained bg) { + + if ((bg == null) || (bgArr == null)) { + return true; + } + + for (int i=0; i < bgArr.length; i++) { + if (bgArr[i] == bg) { + return true; + } + } + return false; + } + + + /** + * return the first SceneGraphPath of the geomAtom. + * If initpath is null, the path is search from + * geomAtom Shape3D/Morph Node up to Locale + * (assume the same locale). + * Otherwise, the path is search up to node or + * null is return if it is not hit. + */ + static private SceneGraphPath getFirstSceneGraphPath(ArrayList initpath, + BranchGroupRetained node, + GeometryAtom geomAtoms[], + Locale locale) { + if (geomAtoms == null) { + return null; + } + + for (int i=0; i < geomAtoms.length; i++) { + Shape3DRetained shape = geomAtoms[i].source; + NodeRetained target = shape.sourceNode; + + if (target == null) { + // The node is just detach from branch so sourceNode = null + continue; + } + if (!inside(shape.branchGroupPath, node)) { + continue; + } + ArrayList path = retrievePath(target, node, geomAtoms[i].source.key); + + if (path == null) { + continue; + } + SceneGraphPath sgpath = new SceneGraphPath(locale, + mergePath(path, initpath), + (Node) target.source); + sgpath.setTransform(shape.getCurrentLocalToVworld(0)); + return sgpath; + } + return null; + } + + + /** + * search the full path from the botton of the scene graph - + * startNode, up to the Locale if endNode is null. + * If endNode is not null, the path is found up to, but not + * including, endNode or return null if endNode not hit + * during the search. + */ + static private ArrayList retrievePath(NodeRetained startNode, + NodeRetained endNode, + HashKey key) { + + ArrayList path = new ArrayList(5); + NodeRetained nodeR = startNode; + + if (nodeR.inSharedGroup) { + // getlastNodeId() will destroy this key + key = new HashKey(key); + } + + do { + if (nodeR == endNode) { // we found it ! + return path; + } + + if (nodeR.source.getCapability(Node.ENABLE_PICK_REPORTING)) { + path.add(nodeR); + } + + if (nodeR instanceof SharedGroupRetained) { + // retrieve the last node ID + String nodeId = key.getLastNodeId(); + Vector parents = ((SharedGroupRetained) nodeR).parents; + int sz = parents.size(); + NodeRetained prevNodeR = nodeR; + for(int i=0; i< sz; i++) { + NodeRetained linkR = (NodeRetained) parents.elementAt(i); + if (linkR.nodeId.equals(nodeId)) { + nodeR = linkR; + // Need to add Link to the path report + path.add(nodeR); + // since !(endNode instanceof Link), we + // can skip the check (nodeR == endNode) and + // proceed to parent of link below + break; + } + } + if (nodeR == prevNodeR) { + // branch is already detach + return null; + } + } + nodeR = nodeR.parent; + } while (nodeR != null); // reach Locale + + if (endNode == null) { + // user call pickxxx(Locale locale, PickShape shape) + return path; + } + + // user call pickxxx(BranchGroup endNode, PickShape shape) + // if locale is reached and endNode not hit, this is not + // the path user want to select + return null; + } + + /** + * copy p1, (follow by) p2 into a new array, p2 can be null + * The path is then reverse before return. + */ + static private Node[] mergePath(ArrayList p1, ArrayList p2) { + int s = p1.size(); + int len; + int i; + int l; + if (p2 == null) { + len = s; + } else { + len = s + p2.size(); + } + + Node nodes[] = new Node[len]; + l = len-1; + for (i=0; i < s; i++) { + nodes[l-i] = (Node) ((NodeRetained) p1.get(i)).source; + } + for (int j=0; i< len; i++, j++) { + nodes[l-i] = (Node) ((NodeRetained) p2.get(j)).source; + } + return nodes; + } + + /** + * Select the closest geomAtoms from shape + * geomAtoms.length must be >= 1 + */ + static private GeometryAtom selectClosest(GeometryAtom geomAtoms[], + PickShape shape) { + Point4d pickPos = new Point4d(); + GeometryAtom closestAtom = geomAtoms[0]; + shape.intersect(closestAtom.source.vwcBounds, pickPos); + double distance = pickPos.w; + + for (int i=1; i < geomAtoms.length; i++) { + shape.intersect(geomAtoms[i].source.vwcBounds, pickPos); + if (pickPos.w < distance) { + distance = pickPos.w; + closestAtom = geomAtoms[i]; + } + } + return closestAtom; + } + + /** + * Sort the GeometryAtoms distance from shape in ascending order + * geomAtoms.length must be >= 1 + */ + static private void sortGeomAtoms(GeometryAtom geomAtoms[], + PickShape shape) { + + final double distance[] = new double[geomAtoms.length]; + Point4d pickPos = new Point4d(); + + for (int i=0; i < geomAtoms.length; i++) { + shape.intersect(geomAtoms[i].source.vwcBounds, pickPos); + distance[i] = pickPos.w; + } + + class Sort { + + GeometryAtom atoms[]; + + Sort(GeometryAtom[] atoms) { + this.atoms = atoms; + } + + void sorting() { + if (atoms.length < 7) { + insertSort(); + } else { + quicksort(0, atoms.length-1); + } + } + + // Insertion sort on smallest arrays + final void insertSort() { + for (int i=0; i<atoms.length; i++) { + for (int j=i; j>0 && + (distance[j-1] > distance[j]); j--) { + double t = distance[j]; + distance[j] = distance[j-1]; + distance[j-1] = t; + GeometryAtom p = atoms[j]; + atoms[j] = atoms[j-1]; + atoms[j-1] = p; + } + } + } + + final void quicksort( int l, int r ) { + int i = l; + int j = r; + double k = distance[(l+r) / 2]; + + do { + while (distance[i]<k) i++; + while (k<distance[j]) j--; + if (i<=j) { + double tmp = distance[i]; + distance[i] =distance[j]; + distance[j] = tmp; + + GeometryAtom p=atoms[i]; + atoms[i]=atoms[j]; + atoms[j]=p; + i++; + j--; + } + } while (i<=j); + + if (l<j) quicksort(l,j); + if (l<r) quicksort(i,r); + } + } + + (new Sort(geomAtoms)).sorting(); + } + +} diff --git a/src/classes/share/javax/media/j3d/PointArray.java b/src/classes/share/javax/media/j3d/PointArray.java new file mode 100644 index 0000000..84366ac --- /dev/null +++ b/src/classes/share/javax/media/j3d/PointArray.java @@ -0,0 +1,150 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The PointArray object draws the array of vertices as individual points. + */ + +public class PointArray extends GeometryArray { + + // non-public, no parameter constructor + PointArray() {} + + /** + * Constructs an empty PointArray object with the specified + * number of vertices, and vertex format. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @exception IllegalArgumentException if vertexCount is less than 1 + */ + public PointArray(int vertexCount, int vertexFormat) { + super(vertexCount,vertexFormat); + + if (vertexCount < 1 ) + throw new IllegalArgumentException(J3dI18N.getString("PointArray0")); + } + + /** + * Constructs an empty PointArray object with the specified + * number of vertices, and vertex format, number of texture coordinate + * sets, and texture coordinate mapping array. + * + * @param vertexCount the number of vertex elements in this array<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used. + * + * @exception IllegalArgumentException if vertexCount is less than 1 + * + * @since Java 3D 1.2 + */ + public PointArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap); + + if (vertexCount < 1 ) + throw new IllegalArgumentException(J3dI18N.getString("PointArray0")); + } + + /** + * Creates the retained mode PointArrayRetained object that this + * PointArray object will point to. + */ + void createRetained() { + this.retained = new PointArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + PointArrayRetained rt = (PointArrayRetained) retained; + int texSetCount = rt.getTexCoordSetCount(); + PointArray p; + if (texSetCount == 0) { + p = new PointArray(rt.getVertexCount(), + rt.getVertexFormat()); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + p = new PointArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap); + } + p.duplicateNodeComponent(this); + return p; + } +} diff --git a/src/classes/share/javax/media/j3d/PointArrayRetained.java b/src/classes/share/javax/media/j3d/PointArrayRetained.java new file mode 100644 index 0000000..dda27ac --- /dev/null +++ b/src/classes/share/javax/media/j3d/PointArrayRetained.java @@ -0,0 +1,247 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The PointArray object draws the array of vertices as individual points. + */ + +class PointArrayRetained extends GeometryArrayRetained { + + PointArrayRetained() { + this.geoType = GEO_TYPE_POINT_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + Point3d pnt = new Point3d(); + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnt); + if (intersectPntAndRay(pnt, pickRay.origin, + pickRay.direction, sdist)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + Vector3d dir = + new Vector3d(pickSegment.end.x - pickSegment.start.x, + pickSegment.end.y - pickSegment.start.y, + pickSegment.end.z - pickSegment.start.z); + while (i < validVertexCount) { + getVertexData(i++, pnt); + if (intersectPntAndRay(pnt, pickSegment.start, + dir, sdist) && + (sdist[0] <= 1.0)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + + } + } + break; + case PickShape.PICKBOUNDINGBOX: + case PickShape.PICKBOUNDINGSPHERE: + case PickShape.PICKBOUNDINGPOLYTOPE: + Bounds bounds = ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(i++, pnt); + if (bounds.intersect(pnt)) { + if (dist == null) { + return true; + } + sdist[0] = pickShape.distance(pnt); + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + } + } + + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnt); + + if (intersectCylinder(pnt, pickCylinder, sdist)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnt); + + if (intersectCone(pnt, pickCone, sdist)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = pnt.x; + y = pnt.y; + z = pnt.z; + } + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("PointArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + + } + + boolean intersect(Point3d[] pnts) { + Point3d point = new Point3d(); + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + switch (pnts.length) { + case 3: // Triangle + while (i < validVertexCount) { + getVertexData(i++, point); + if (intersectTriPnt(pnts[0], pnts[1], pnts[2], point)) { + return true; + } + } + break; + case 4: // Quad + while (i < validVertexCount) { + getVertexData(i++, point); + if (intersectTriPnt(pnts[0], pnts[1], pnts[2], point) || + intersectTriPnt(pnts[0], pnts[2], pnts[3], point)) { + return true; + } + } + break; + case 2: // Line + double dist[] = new double[1]; + Vector3d dir = new Vector3d(); + + while (i < validVertexCount) { + getVertexData(i++, point); + dir.x = pnts[1].x - pnts[0].x; + dir.y = pnts[1].y - pnts[0].y; + dir.z = pnts[1].z - pnts[0].z; + if (intersectPntAndRay(point, pnts[0], dir, dist) && + (dist[0] <= 1.0)) { + return true; + } + } + break; + case 1: // Point + while (i < validVertexCount) { + getVertexData(i++, point); + if ((pnts[0].x == point.x) && + (pnts[0].y == point.y) && + (pnts[0].z == point.z)) { + return true; + } + } + break; + } + return false; + } + + + boolean intersect(Transform3D thisToOtherVworld, + GeometryRetained geom) { + + Point3d[] pnt = new Point3d[1]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnt[0] = new Point3d(); + + while (i < validVertexCount) { + getVertexData(i++, pnt[0]); + thisToOtherVworld.transform(pnt[0]); + if (geom.intersect(pnt)) { + return true; + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + Point3d pnt = new Point3d(); + + while (i < validVertexCount) { + getVertexData(i++, pnt); + if (targetBound.intersect(pnt)) { + return true; + } + } + return false; + } + + int getClassType() { + return POINT_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/PointAttributes.java b/src/classes/share/javax/media/j3d/PointAttributes.java new file mode 100644 index 0000000..d42433e --- /dev/null +++ b/src/classes/share/javax/media/j3d/PointAttributes.java @@ -0,0 +1,209 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The PointAttributes object defines all attributes that apply to + * point primitives. The point attributes that can be defined are:<p> + * <ul> + * <li>Size - the size of the point, in pixels. The default is a point + * size of one pixel.</li><p> + * <li>Antialiasing - for points greater than one-pixel in size, + * antialiasing smooths the outline of the point when it is rendered.</li> + * <p></ul> + * If antialiasing is disabled (the default), fractional point sizes + * are rounded to integer sizes, and a screen-aligned square region + * of pixels is drawn.<p> + * <p> + * If antialiasing is enabled, the points are considered transparent + * for rendering purposes. They are rendered with all the other transparent + * objects and adhere to the other transparency settings such as the + * View transparency sorting policy and the View depth buffer freeze + * transparent enable. + * </p> + * + * @see Appearance + * @see View + */ +public class PointAttributes extends NodeComponent { + + /** + * Specifies that this PointAttributes object allows reading its + * point size information. + */ + public static final int + ALLOW_SIZE_READ = CapabilityBits.POINT_ATTRIBUTES_ALLOW_SIZE_READ; + + /** + * Specifies that this PointAttributes object allows writing its + * point size information. + */ + public static final int + ALLOW_SIZE_WRITE = CapabilityBits.POINT_ATTRIBUTES_ALLOW_SIZE_WRITE; + + /** + * Specifies that this PointAttributes object allows reading its + * point antialiasing flag. + */ + public static final int + ALLOW_ANTIALIASING_READ = CapabilityBits.POINT_ATTRIBUTES_ALLOW_ANTIALIASING_READ; + + /** + * Specifies that this PointAttributes object allows writing its + * point antialiasing flag. + */ + public static final int + ALLOW_ANTIALIASING_WRITE = CapabilityBits.POINT_ATTRIBUTES_ALLOW_ANTIALIASING_WRITE; + + /** + * Constructs a PointAttributes object with default parameters. + * The default values are as follows: + * <ul> + * point size : 1<br> + * point antialiasing : false<br> + * </ul> + */ + public PointAttributes(){ + } + + /** + * Constructs a PointAttributes object with specified values. + * @param pointSize the size of points, in pixels + * @param pointAntialiasing flag to set point antialising ON or OFF + */ + public PointAttributes(float pointSize, boolean pointAntialiasing){ + + ((PointAttributesRetained)this.retained).initPointSize(pointSize); + ((PointAttributesRetained)this.retained).initPointAntialiasingEnable(pointAntialiasing); + } + + /** + * Sets the point size for this appearance component object. + * @param pointSize the size, in pixels, of point primitives + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPointSize(float pointSize) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SIZE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointAttributes0")); + + if (isLive()) + ((PointAttributesRetained)this.retained).setPointSize(pointSize); + else + ((PointAttributesRetained)this.retained).initPointSize(pointSize); + + } + + /** + * Gets the point size for this appearance component object. + * @return the size, in pixels, of point primitives + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getPointSize() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PointAttributes1")); + return ((PointAttributesRetained)this.retained).getPointSize(); + } + + /** + * Enables or disables point antialiasing + * for this appearance component object. + * <p> + * If antialiasing is enabled, the points are considered transparent + * for rendering purposes. They are rendered with all the other + * transparent objects and adhere to the other transparency settings + * such as the View transparency sorting policy and the View depth + * buffer freeze transparent enable. + * </p> + * @param state true or false to enable or disable point antialiasing + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @see View + */ + public void setPointAntialiasingEnable(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANTIALIASING_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointAttributes2")); + if (isLive()) + ((PointAttributesRetained)this.retained).setPointAntialiasingEnable(state); + else + ((PointAttributesRetained)this.retained).initPointAntialiasingEnable(state); + + } + + /** + * Retrieves the state of the point antialiasing flag. + * @return true if point antialiasing is enabled, + * false if point antialiasing is disabled + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getPointAntialiasingEnable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ANTIALIASING_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PointAttributes3")); + return ((PointAttributesRetained)this.retained).getPointAntialiasingEnable(); + } + + /** + * Creates a retained mode PointAttributesRetained object that this + * PointAttributes component object will point to. + */ + void createRetained() { + this.retained = new PointAttributesRetained(); + this.retained.setSource(this); + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + PointAttributes pa = new PointAttributes(); + pa.duplicateNodeComponent(this); + return pa; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + PointAttributesRetained attr = (PointAttributesRetained) + originalNodeComponent.retained; + PointAttributesRetained rt = (PointAttributesRetained) retained; + + rt.initPointSize(attr.getPointSize()); + rt.initPointAntialiasingEnable(attr.getPointAntialiasingEnable()); + } + +} diff --git a/src/classes/share/javax/media/j3d/PointAttributesRetained.java b/src/classes/share/javax/media/j3d/PointAttributesRetained.java new file mode 100644 index 0000000..bee0396 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PointAttributesRetained.java @@ -0,0 +1,208 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * The PointAttributesRetained object defines all rendering state that can be set + * as a component object of a Shape3D node. + */ +class PointAttributesRetained extends NodeComponentRetained { + // A list of pre-defined bits to indicate which component + // in this LineAttributesRetained object changed. + static final int POINT_SIZE_CHANGED = 0x01; + static final int POINT_AA_CHANGED = 0x02; + + // Size, in pixels, of point primitives + float pointSize = 1.0f; + + // Point antialiasing switch + boolean pointAntialiasing = false; + + + /** + * Sets the point size for this appearance component object. + * @param pointSize the size, in pixels, of point primitives + */ + final void initPointSize(float pointSize) { + this.pointSize = pointSize; + } + + /** + * Sets the point size for this appearance component object and sends a + * message notifying the interested structures of the change. + * @param pointSize the size, in pixels, of point primitives + */ + final void setPointSize(float pointSize) { + initPointSize(pointSize); + sendMessage(POINT_SIZE_CHANGED, new Float(pointSize)); + } + + /** + * Gets the point size for this appearance component object. + * @return the size, in pixels, of point primitives + */ + final float getPointSize() { + return pointSize; + } + + /** + * Enables or disables point antialiasing + * for this appearance component object. + * @param state true or false to enable or disable point antialiasing + */ + final void initPointAntialiasingEnable(boolean state) { + pointAntialiasing = state; + } + + /** + * Enables or disables point antialiasing + * for this appearance component object and sends a + * message notifying the interested structures of the change. + * @param state true or false to enable or disable point antialiasing + */ + final void setPointAntialiasingEnable(boolean state) { + initPointAntialiasingEnable(pointAntialiasing); + sendMessage(POINT_AA_CHANGED, + (state ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Retrieves the state of the point antialiasing flag. + * @return true if point antialiasing is enabled, + * false if point antialiasing is disabled + */ + final boolean getPointAntialiasingEnable() { + return pointAntialiasing; + } + + /** + * Creates and initializes a mirror object, point the mirror object + * to the retained object if the object is not editable + */ + synchronized void createMirrorObject() { + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror = this; + } else { + PointAttributesRetained mirrorPa + = new PointAttributesRetained(); + mirrorPa.set(this); + mirrorPa.source = source; + mirror = mirrorPa; + } + } else { + ((PointAttributesRetained) mirror).set(this); + } + } + + /** + * Update the native context + */ + native void updateNative(long ctx, float pointSize, boolean pointAntialiasing); + + /** + * Update the native context + */ + void updateNative(long ctx) { + updateNative(ctx, pointSize, pointAntialiasing); + } + + + /** + * Initializes a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + ((PointAttributesRetained)mirror).set(this); + } + + + /** + * Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + + PointAttributesRetained mirrorPa = (PointAttributesRetained) mirror; + + if ((component & POINT_SIZE_CHANGED) != 0) { + mirrorPa.pointSize = ((Float)value).floatValue(); + } + else if ((component & POINT_AA_CHANGED) != 0) { + mirrorPa.pointAntialiasing = ((Boolean)value).booleanValue(); + } + } + + boolean equivalent(PointAttributesRetained pr) { + return ((pr != null) && + (pr.pointSize == pointSize) && + (pr.pointAntialiasing == pointAntialiasing)); + } + + + protected void set(PointAttributesRetained pr) { + super.set(pr); + pointSize = pr.pointSize; + pointAntialiasing = pr.pointAntialiasing; + } + + + final void sendMessage(int attrMask, Object attr) { + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.POINTATTRIBUTES_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.POINTATTRIBUTES_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + + } + void handleFrequencyChange(int bit) { + if (bit == PointAttributes.ALLOW_SIZE_WRITE || + bit == PointAttributes.ALLOW_ANTIALIASING_WRITE) { + setFrequencyChangeMask(bit, 0x1); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/PointLight.java b/src/classes/share/javax/media/j3d/PointLight.java new file mode 100644 index 0000000..916dbda --- /dev/null +++ b/src/classes/share/javax/media/j3d/PointLight.java @@ -0,0 +1,292 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Point3f; +import javax.vecmath.Color3f; + + +/** + * The PointLight object specifies an attenuated light source at a + * fixed point in space that radiates light equally in all directions + * away from the light source. PointLight has the same attributes as + * a Light node, with the addition of location and attenuation + * parameters. + * <p> + * A point light contributes to diffuse and specular reflections, + * which in turn depend on the orientation and position of a + * surface. A point light does not contribute to ambient reflections. + * <p> + * A PointLight is attenuated by multiplying the contribution of the + * light by an attenuation factor. The attenuation factor causes the + * the PointLight's brightness to decrease as distance from the light + * source increases. + * A PointLight's attenuation factor contains three values: + * <P><UL> + * <LI>Constant attenuation</LI> + * <LI>Linear attenuation</LI> + * <LI>Quadratic attenuation</LI></UL> + * <p> + * A PointLight is attenuated by the reciprocal of the sum of: + * <p> + * <ul> + * The constant attenuation factor<br> + * The Linear attenuation factor times the distance between the light + * and the vertex being illuminated<br> + * The quadratic attenuation factor times the square of the distance + * between the light and the vertex + * </ul> + * <p> + * By default, the constant attenuation value is 1 and the other + * two values are 0, resulting in no attenuation. + */ + +public class PointLight extends Light { + /** + * Specifies that this PointLight node allows reading its position + * information. + */ + public static final int + ALLOW_POSITION_READ = CapabilityBits.POINT_LIGHT_ALLOW_POSITION_READ; + + /** + * Specifies that this PointLight node allows writing its position + * information. + */ + public static final int + ALLOW_POSITION_WRITE = CapabilityBits.POINT_LIGHT_ALLOW_POSITION_WRITE; + + /** + * Specifies that this PointLight node allows reading its attenuation + * information. + */ + public static final int + ALLOW_ATTENUATION_READ = CapabilityBits.POINT_LIGHT_ALLOW_ATTENUATION_READ; + + /** + * Specifies that this PointLight node allows writing its attenuation + * information. + */ + public static final int + ALLOW_ATTENUATION_WRITE = CapabilityBits.POINT_LIGHT_ALLOW_ATTENUATION_WRITE; + + /** + * Constructs a PointLight node with default parameters. + * The default values are as follows: + * <ul> + * position : (0,0,0)<br> + * attenuation : (1,0,0)<br> + * </ul> + */ + public PointLight() { + } + + /** + * Constructs and initializes a point light. + * @param color the color of the light source + * @param position the position of the light in three-space + * @param attenuation the attenutation (constant, linear, quadratic) of the light + */ + public PointLight(Color3f color, + Point3f position, + Point3f attenuation) { + super(color); + ((PointLightRetained)this.retained).initPosition(position); + ((PointLightRetained)this.retained).initAttenuation(attenuation); + } + + /** + * Constructs and initializes a point light. + * @param lightOn flag indicating whether this light is on or off + * @param color the color of the light source + * @param position the position of the light in three-space + * @param attenuation the attenuation (constant, linear, quadratic) of the light + */ + public PointLight(boolean lightOn, + Color3f color, + Point3f position, + Point3f attenuation) { + super(lightOn, color); + ((PointLightRetained)this.retained).initPosition(position); + ((PointLightRetained)this.retained).initAttenuation(attenuation); + } + + /** + * Creates the retained mode PointLightRetained object that this + * PointLight component object will point to. + */ + void createRetained() { + this.retained = new PointLightRetained(); + this.retained.setSource(this); + } + + + /** + * Set light position. + * @param position the new position + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPosition(Point3f position) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointLight0")); + + if (isLive()) + ((PointLightRetained)this.retained).setPosition(position); + else + ((PointLightRetained)this.retained).initPosition(position); + } + + /** + * Set light position. + * @param x the new X position + * @param y the new Y position + * @param z the new Z position + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPosition(float x, float y, float z) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointLight1")); + + if (isLive()) + ((PointLightRetained)this.retained).setPosition(x,y,z); + else + ((PointLightRetained)this.retained).initPosition(x,y,z); + } + + /** + * Gets this Light's current position and places it in the parameter specified. + * @param position the vector that will receive this node's position + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getPosition(Point3f position) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PointLight2")); + + ((PointLightRetained)this.retained).getPosition(position); + } + + /** + * Sets this Light's current attenuation values and places it in the parameter specified. + * @param attenuation the vector that will receive the attenuation values + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAttenuation(Point3f attenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ATTENUATION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointLight3")); + + if (isLive()) + ((PointLightRetained)this.retained).setAttenuation(attenuation); + else + ((PointLightRetained)this.retained).initAttenuation(attenuation); + } + + /** + * Sets this Light's current attenuation values and places it in the parameter specified. + * @param constant the light's constant attenuation + * @param linear the light's linear attenuation + * @param quadratic the light's quadratic attenuation + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAttenuation(float constant, float linear, float quadratic) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ATTENUATION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointLight3")); + + if (isLive()) + ((PointLightRetained)this.retained).setAttenuation(constant, linear, quadratic); + else + ((PointLightRetained)this.retained).initAttenuation(constant, linear, quadratic); + } + + /** + * Gets this Light's current attenuation values and places it in the parameter specified. + * @param attenuation the vector that will receive the attenuation values + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getAttenuation(Point3f attenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ATTENUATION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PointLight5")); + + ((PointLightRetained)this.retained).getAttenuation(attenuation); + } + + + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + PointLight p = new PointLight(); + p.duplicateNode(this, forceDuplicate); + return p; + } + + + /** + * Copies all PointLight information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + PointLightRetained attr = (PointLightRetained) originalNode.retained; + PointLightRetained rt = (PointLightRetained) retained; + + Point3f p = new Point3f(); + attr.getPosition(p); + rt.initPosition(p); + + attr.getAttenuation(p); + rt.initAttenuation(p); + } +} diff --git a/src/classes/share/javax/media/j3d/PointLightRetained.java b/src/classes/share/javax/media/j3d/PointLightRetained.java new file mode 100644 index 0000000..8b0750a --- /dev/null +++ b/src/classes/share/javax/media/j3d/PointLightRetained.java @@ -0,0 +1,322 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * A Retained PointLight source. + */ +class PointLightRetained extends LightRetained { + static final int POSITION_CHANGED = LAST_DEFINED_BIT << 1; + static final int ATTENUATION_CHANGED = LAST_DEFINED_BIT << 2; + static final int LAST_POINTLIGHT_DEFINED_BIT = ATTENUATION_CHANGED; + + /** + * The attenuation vector consisting of + * constant, linear, and quadratic coefficients. + */ + Point3f attenuation = new Point3f(1.0f, 0.0f, 0.0f); + + // The position at which this light source exists. + Point3f position = new Point3f(); + + // The transformed position of this light + Point3f xformPosition = new Point3f(); + + // local to vworld scale for attenuation + double localToVworldScale; + + // scaled linearAttenuation from lc to ec + float linearAttenuationInEc; + + // scaled quadraticAttenuation from lc to ec + float quadraticAttenuationInEc; + + PointLightRetained() { + this.nodeType = NodeRetained.POINTLIGHT; + lightType = 3; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + /** + * Initializes this light's position from the vector provided. + * @param position the new position + */ + void initPosition(Point3f position) { + this.position.set(position); + + if (staticTransform != null) { + staticTransform.transform.transform(this.position, this.position); + } + } + + /** + * Sets this light's position from the vector provided. + * @param position the new position + */ + void setPosition(Point3f position) { + initPosition(position); + sendMessage(POSITION_CHANGED, new Point3f(position)); + } + + + /** + * Initializes this light's position from the three values provided. + * @param x the new x position + * @param y the new y position + * @param z the new z position + */ + void initPosition(float x, float y, float z) { + this.position.x = x; + this.position.y = y; + this.position.z = z; + + if (staticTransform != null) { + staticTransform.transform.transform(this.position, this.position); + } + } + + + /** + * Sets this light's position from the three values provided. + * @param x the new x position + * @param y the new y position + * @param z the new z position + */ + void setPosition(float x, float y, float z) { + setPosition(new Point3f(x, y, z)); + } + + + /** + * Retrieves this light's position and places it in the + * vector provided. + * @param position the variable to receive the position vector + */ + void getPosition(Point3f position) { + position.set(this.position); + + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + invTransform.transform(position, position); + } + } + + + /** + * Initializes the point light's attenuation constants. + * @param attenuation a vector consisting of constant, linear, and quadratic coefficients + */ + void initAttenuation(Point3f attenuation) { + this.attenuation.set(attenuation); + } + + /** + * Sets the point light's attenuation constants. + * @param attenuation a vector consisting of constant, linear, and quadratic coefficients + */ + void setAttenuation(Point3f attenuation) { + initAttenuation(attenuation); + sendMessage(ATTENUATION_CHANGED, new Point3f(attenuation)); + } + + /** + * Sets the point light's attenuation. + * @param constant the point light's constant attenuation + * @param linear the linear attenuation of the light + * @param quadratic the quadratic attenuation of the light + */ + void initAttenuation(float constant, + float linear, + float quadratic) { + this.attenuation.x = constant; + this.attenuation.y = linear; + this.attenuation.z = quadratic; + } + + /** + * Sets the point light's attenuation. + * @param constant the point light's constant attenuation + * @param linear the linear attenuation of the light + * @param quadratic the quadratic attenuation of the light + */ + void setAttenuation(float constant, + float linear, + float quadratic) { + setAttenuation(new Point3f(constant, linear, quadratic)); + } + + /** + * Retrieves the light's attenuation and places the value in the parameter + * specified. + * @param attenuation the variable that will contain the attenuation + */ + void getAttenuation(Point3f attenuation) { + attenuation.set(this.attenuation); + } + + /** + * This update function, and its native counterpart, + * updates a point light. This includes its color, attenuation, + * and its transformed position. + */ + native void updateLight(long ctx, int lightSlot, float red, float green, + float blue, float ax, float ay, float az, + float px, float py, float pz); + + void update(long ctx, int lightSlot, double scale) { + validateAttenuationInEc(scale); + updateLight(ctx, lightSlot, color.x, color.y, color.z, + attenuation.x, linearAttenuationInEc, + quadraticAttenuationInEc, + xformPosition.x, xformPosition.y, + xformPosition.z); + + } + + void setLive(SetLiveState s) { + super.setLive(s); + J3dMessage createMessage = super.initMessage(9); + Object[] objs = (Object[])createMessage.args[4]; + objs[7] = new Point3f(position); + objs[8] = new Point3f(attenuation); + + VirtualUniverse.mc.processMessage(createMessage); + } + + // This is called only from SpotLightRetained, so as + // to not create a message for initialization. for spotlight + // the initialization of the message is done by SpotLightRetained + void doSetLive(SetLiveState s) { + super.setLive(s); + } + + J3dMessage initMessage(int num) { + J3dMessage createMessage = super.initMessage(num); + Object[] objs = (Object[])createMessage.args[4]; + objs[7] = new Point3f(position); + objs[8] = new Point3f(attenuation); + return createMessage; + } + + + // Note : if you add any more fields here , you need to update + // updateLight() in RenderingEnvironmentStructure + void updateMirrorObject(Object[] objs) { + + int component = ((Integer)objs[1]).intValue(); + Transform3D mlLastLocalToVworld; + int i; + int numLgts = ((Integer)objs[2]).intValue(); + LightRetained[] mLgts = (LightRetained[]) objs[3]; + + + if ((component & POSITION_CHANGED) != 0) { + for (i = 0; i < numLgts; i++) { + if (mLgts[i] instanceof PointLightRetained) { + PointLightRetained ml = (PointLightRetained)mLgts[i]; + mlLastLocalToVworld = ml.getLastLocalToVworld(); + ml.position = (Point3f) objs[4]; + mlLastLocalToVworld.transform(ml.position, + ml.xformPosition); + ml.localToVworldScale = + mlLastLocalToVworld.getDistanceScale(); + } + } + } + else if ((component & ATTENUATION_CHANGED) != 0) { + for (i = 0; i < numLgts; i++) { + if (mLgts[i] instanceof PointLightRetained) { + PointLightRetained ml = (PointLightRetained)mLgts[i]; + ml.attenuation.set((Point3f)objs[4]); + } + } + } + else if ((component & INIT_MIRROR) != 0) { + for (i = 0; i < numLgts; i++) { + if (mLgts[i] instanceof PointLightRetained) { + PointLightRetained ml = (PointLightRetained)mirrorLights[i]; + ml.position = (Point3f)((Object[]) objs[4])[7]; + ml.attenuation.set((Point3f)((Object[]) objs[4])[8]); + mlLastLocalToVworld = ml.getLastLocalToVworld(); + mlLastLocalToVworld.transform(ml.position, + ml.xformPosition); + ml.localToVworldScale = + mlLastLocalToVworld.getDistanceScale(); + } + } + } + + // call the parent's mirror object update routine + super.updateMirrorObject(objs); + } + + void validateAttenuationInEc(double vworldToCoexistenceScale) { + double localToEcScale = localToVworldScale * vworldToCoexistenceScale; + + linearAttenuationInEc = (float)(attenuation.y / localToEcScale); + quadraticAttenuationInEc = + (float)(attenuation.z / (localToEcScale * localToEcScale)); + } + + + + // Clones only the retained side, internal use only + protected Object clone() { + PointLightRetained pr = + (PointLightRetained)super.clone(); + + pr.attenuation = new Point3f(attenuation); + pr.position = new Point3f(position); + pr.xformPosition = new Point3f(); + return pr; + } + + + // Called on the mirror object + void updateTransformChange() { + super.updateTransformChange(); + + Transform3D lastLocalToVworld = getLastLocalToVworld(); + + lastLocalToVworld.transform(position, xformPosition); + localToVworldScale = lastLocalToVworld.getDistanceScale(); + + validateAttenuationInEc(0.0861328125); + } + + void sendMessage(int attrMask, Object attr) { + + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.universe = universe; + createMessage.type = J3dMessage.LIGHT_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + if (inSharedGroup) + createMessage.args[2] = new Integer(numMirrorLights); + else + createMessage.args[2] = new Integer(1); + createMessage.args[3] = mirrorLights.clone(); + createMessage.args[4] = attr; + VirtualUniverse.mc.processMessage(createMessage); + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + xform.transform.transform(position, position); + } +} diff --git a/src/classes/share/javax/media/j3d/PointSound.java b/src/classes/share/javax/media/j3d/PointSound.java new file mode 100644 index 0000000..59a098d --- /dev/null +++ b/src/classes/share/javax/media/j3d/PointSound.java @@ -0,0 +1,528 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Point2f; +import javax.vecmath.Point3f; + + +/** + * The PointSound node (a sub-class of the Sound node) defines a spatially + * located sound source whose waves radiate uniformly in all directions from + * a given location in space. It has the same attributes as a Sound object + * with the addition of a location and the specification of distance-based + * gain attenuation for listener positions between an array of distances. + *<P> + * A sound's amplitude is attenuated based on the distance between the listener + * and the sound source position. A piecewise linear curve (defined in terms of + * pairs of distance and gain scale factor) specifies the gain scale factor slope. + * + * The PointSound's location and attenuation distances are defined in the local + * coordinate system of the node. + *<P> + * Distance Gain Attenuation + * <UL> + * Associated with distances from the listener to the sound source via an + * array of (distance, gain-scale-factor) pairs. The gain scale factor + * applied to the sound source is the linear interpolated gain value between + * the distance value range that includes the current distance from + * the listener to the sound source. If the distance from the listener to + * the sound source is less than the first distance in the array, the first + * gain scale factor is applied to the sound source. This creates a + * spherical region around the listener within which all sound gain is + * uniformly scaled by the first gain in the array. If the distance from + * the listener to the sound source is greater than the last distance in + * the array, the last gain scale factor is applied to the sound source. + *<P> + * Distance elements in this array of Point2f is a monotonically-increasing + * set of floating point numbers measured from the location of the sound + * source. Gain scale factors elements in this list of pairs can be any + * positive floating point numbers. While for most applications this list + * of gain scale factors will usually be monotonically-decreasing, they + * do not have to be. + * If this + * is not set, no distance gain attenuation is performed (equivalent to + * using a distance gain of 1.0 for all distances). + *<P> + * getDistanceGainLength method returns the length of the distance gain + * attenuation arrays. Arrays passed into getDistanceGain methods should all + * be at least this size. + *<P> + * There are two methods for getDistanceGain, one returning an array of + * points, the other returning separate arrays for each attenuation + * component.</UL> + */ + +public class PointSound extends Sound { + // Constants + // + // These flags, when enabled using the setCapability method, allow an + // application to invoke methods that respectively read and write the position + // and the distance gain array. These capability flags are enforced only when + // the node is part of a live or compiled scene graph + + /** + * Specifies that this node allows access to its object's position + * information. + */ + public static final int + ALLOW_POSITION_READ = CapabilityBits.POINT_SOUND_ALLOW_POSITION_READ; + + /** + * Specifies that this node allows writing to its object's position + * information. + */ + public static final int + ALLOW_POSITION_WRITE = CapabilityBits.POINT_SOUND_ALLOW_POSITION_WRITE; + + /** + * Specifies that this node allows access to its object's distance + * gain attenuation information. + */ + public static final int + ALLOW_DISTANCE_GAIN_READ = CapabilityBits.POINT_SOUND_ALLOW_DISTANCE_GAIN_READ; + + /** + * Specifies that this node allows writing to its object's distance + * gain attenuation information. + */ + public static final int + ALLOW_DISTANCE_GAIN_WRITE = CapabilityBits.POINT_SOUND_ALLOW_DISTANCE_GAIN_WRITE; + + /** + * Constructs and initializes a new PointSound node using default + * parameters. The following default values are used: + * <ul> + * position vector: (0.0, 0.0, 0.0)<br> + * Back attenuation: null<br> + * distance gain attenuation: null (no attenuation performed)<br> + * </ul> + */ + public PointSound() { + // Uses default values defined for Sound and PointSound nodes + super(); + } + + /** + * Constructs a PointSound node object using only the provided parameter + * values for sound data, sample gain, and position. The remaining fields + * are set to the above default values. This form uses a point as input for + * its position. + * @param soundData sound data associated with this sound source node + * @param initialGain amplitude scale factor applied to sound source + * @param position 3D location of source + */ + public PointSound(MediaContainer soundData, + float initialGain, + Point3f position) { + super(soundData, initialGain); + ((PointSoundRetained)this.retained).setPosition(position); + } + + /** + * Constructs a PointSound node object using only the provided parameter + * values for sound data, sample gain, and position. The remaining fields + * are set to to the above default values. This form uses individual float + * parameters for the elements of the position point. + * @param soundData sound data associated with this sound source node + * @param initialGain amplitude scale factor applied to sound source data + * @param posX x coordinate of location of source + * @param posY y coordinate of location of source + * @param posZ z coordinate of location of source + */ + public PointSound(MediaContainer soundData, + float initialGain, + float posX, float posY, float posZ ) { + super(soundData, initialGain); + ((PointSoundRetained)this.retained).setPosition(posX,posY,posZ); + } + + // The next four constructors fill all this classes fields with the provided + // arguments values. + // See the header for the setDistanceGain method for details on how the + // those arrays are interpreted. + + /** + * Construct a PointSound object accepting Point3f as input for the position + * and accepting an array of Point2f for the distance attenuation values + * where each pair in the array contains a distance and a gain scale factor. + * @param soundData sound data associated with this sound source node + * @param initialGain amplitude scale factor applied to sound source + * @param loopCount number of times loop is looped + * @param release flag denoting playing sound data to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param position 3D location of source + * @param distanceGain array of (distance,gain) pairs controling attenuation + */ + public PointSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + Point3f position, + Point2f[] distanceGain) { + + super(soundData, initialGain, loopCount, release, continuous, + enable, region, priority ); + ((PointSoundRetained)this.retained).setPosition(position); + ((PointSoundRetained)this.retained).setDistanceGain(distanceGain); + } + + /** + * Construct a PointSound object accepting individual float parameters for + * the elements of the position point, and accepting an array of Point2f for + * the distance attenuation values where each pair in the array contains a + * distance and a gain scale factor. + * @param soundData sound data associated with this sound source node + * @param initialGain amplitude scale factor applied to sound source + * @param loopCount number of times loop is looped + * @param release flag denoting playing sound to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param posX x coordinate of location of source + * @param posY y coordinate of location of source + * @param posZ z coordinate of location of source + * @param distanceGain array of (distance,gain) pairs controling attenuation + */ + public PointSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + float posX, float posY, float posZ, + Point2f[] distanceGain) { + + super(soundData, initialGain, loopCount, release, + continuous, enable, region, priority ); + ((PointSoundRetained)this.retained).setPosition(posX,posY,posZ); + ((PointSoundRetained)this.retained).setDistanceGain(distanceGain); + } + + /** + * Construct a PointSound object accepting points as input for the position. + * and accepting separate arrays for the distance and gain scale factors + * components of distance attenuation. + * @param soundData sound data associated with this sound source node + * @param initialGain amplitude scale factor applied to sound source + * @param loopCount number of times loop is looped + * @param release flag denoting playing sound data to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param position 3D location of source + * @param attenuationDistance array of distance values used for attenuation + * @param attenuationGain array of gain scale factors used for attenuation + */ + public PointSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + Point3f position, + float[] attenuationDistance, + float[] attenuationGain) { + + super(soundData, initialGain, loopCount, release, continuous, + enable, region, priority ); + ((PointSoundRetained)this.retained).setPosition(position); + ((PointSoundRetained)this.retained).setDistanceGain( + attenuationDistance, attenuationGain); + } + + /** + * Construct a PointSound object accepting individual float parameters for + * the elements of the position points, and accepting separate arrays for + * the distance and gain scale factors components of distance attenuation. + * @param soundData sound data associated with this sound source node + * @param initialGain amplitude scale factor applied to sound source + * @param loopCount number of times loop is looped + * @param release flag denoting playing sound to end + * @param continuous denotes that sound silently plays when disabled + * @param enable sound switched on/off + * @param region scheduling bounds + * @param priority playback ranking value + * @param posX x coordinate of location of source + * @param posY y coordinate of location of source + * @param posZ z coordinate of location of source + * @param attenuationDistance array of distance values used for attenuation + * @param attenuationGain array of gain scale factors used for attenuation + */ + public PointSound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + float posX, float posY, float posZ, + float[] attenuationDistance, + float[] attenuationGain) { + + super(soundData, initialGain, loopCount, release, + continuous, enable, region, priority ); + ((PointSoundRetained)this.retained).setPosition(posX,posY,posZ); + ((PointSoundRetained)this.retained).setDistanceGain( + attenuationDistance, attenuationGain); + } + + /** + * Creates the retained mode PointSoundRetained object that this + * PointSound object will point to. + */ + void createRetained() { + this.retained = new PointSoundRetained(); + this.retained.setSource(this); + } + + /** + * Sets this sound's location from the vector provided. + * @param position the new location + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPosition(Point3f position) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointSound0")); + + ((PointSoundRetained)this.retained).setPosition(position); + } + + /** + * Sets this sound's position from the three values provided. + * @param x the new x position + * @param y the new y position + * @param z the new z position + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPosition(float x, float y, float z) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointSound0")); + + ((PointSoundRetained)this.retained).setPosition(x,y,z); + } + + /** + * Retrieves this sound's direction and places it in the + * vector provided. + * @param position the variable to receive the direction vector + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getPosition(Point3f position) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PointSound2")); + + ((PointSoundRetained)this.retained).getPosition(position); + } + + /** + * Sets this sound's distance gain attenuation - where gain scale factor + * is applied to sound based on distance listener is from sound source. + * This form of setDistanceGain takes these pairs of values as an array of + * Point2f. + * @param attenuation defined by pairs of (distance,gain-scale-factor) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDistanceGain(Point2f[] attenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointSound3")); + + ((PointSoundRetained)this.retained).setDistanceGain(attenuation); + } + + /** + * Sets this sound's distance gain attenuation as an array of Point2fs. + * This form of setDistanceGain accepts two separate arrays for these values. + * The distance and gainScale arrays should be of the same length. If the + * gainScale array length is greater than the distance array length, the + * gainScale array elements beyond the length of the distance array are + * ignored. If the gainScale array is shorter than the distance array, the + * last gainScale array value is repeated to fill an array of length equal + * to distance array. + * @param distance array of monotonically-increasing floats + * @param gain array of non-negative scale factors + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDistanceGain(float[] distance, float[] gain) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PointSound3")); + + ((PointSoundRetained)this.retained).setDistanceGain(distance, gain); + } + + /** + * Get the length of this node's distance gain attenuation arrays. + * @return distance gain attenuation array length + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getDistanceGainLength() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PointSound4")); + + return (((PointSoundRetained)this.retained).getDistanceGainLength()); + } + + /** + * Gets this sound's distance attenuation. The distance attenuation + * pairs are copied into the specified array. + * The array must be large enough to hold all of the points. + * The individual array elements must be allocated by the caller. + * @param attenuation arrays containing distance attenuation pairs + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDistanceGain(Point2f[] attenuation) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PointSound4")); + + ((PointSoundRetained)this.retained).getDistanceGain(attenuation); + } + + /** + * Gets this sound's distance gain attenuation values in separate arrays. + * The arrays must be large enough to hold all of the values. + * @param distance array of float distance from sound source + * @param gain array of non-negative scale factors associated with + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDistanceGain(float[] distance, float[] gain) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DISTANCE_GAIN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PointSound4")); + + ((PointSoundRetained)this.retained).getDistanceGain(distance,gain); + } + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + PointSound p = new PointSound(); + p.duplicateNode(this, forceDuplicate); + return p; + } + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @exception ClassCastException if originalNode is not an instance of + * <code>PointSound</code> + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + checkDuplicateNode(originalNode, forceDuplicate); + } + + + /** + * Copies all PointSound information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + PointSoundRetained orgRetained = (PointSoundRetained)originalNode.retained; + PointSoundRetained thisRetained = (PointSoundRetained)this.retained; + + Point3f p = new Point3f(); + orgRetained.getPosition(p); + thisRetained.setPosition(p); + + int len = orgRetained.getDistanceGainLength(); + float distance[] = new float[len]; + float gain[] = new float[len]; + orgRetained.getDistanceGain(distance, gain); + thisRetained.setDistanceGain(distance, gain); + } + +} diff --git a/src/classes/share/javax/media/j3d/PointSoundRetained.java b/src/classes/share/javax/media/j3d/PointSoundRetained.java new file mode 100644 index 0000000..d0006c0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PointSoundRetained.java @@ -0,0 +1,289 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.lang.Math; +import java.net.URL; +import javax.vecmath.Point3f; +import javax.vecmath.Point3d; +import javax.vecmath.Point2f; + +/** + * The PointSoundRetained node (a sub-class of the SoundRetained node) defines + * a spatially-located sound source whose waves radiate uniformly in all + * directions from a given location in space. + */ + +class PointSoundRetained extends SoundRetained { + /** + * Origin of Sound source in Listener's space. + */ + Point3f position = new Point3f(0.0f, 0.0f, 0.0f); + + /** + * The transformed position of this sound + */ + Point3f xformPosition = new Point3f(); + Transform3D trans = new Transform3D(); + + // Pairs of distances and gain scale factors that define piecewise linear + // gain attenuation between each pair. + float[] attenuationDistance; + float[] attenuationGain; + + PointSoundRetained() { + this.nodeType = NodeRetained.POINTSOUND; + } + + /** + * Sets this sound's location from the vector provided. + * @param position the new location + */ + void setPosition(Point3f position) { + if (staticTransform != null) { + staticTransform.transform.transform(position, this.position); + } else { + this.position.set(position); + } + + getLastLocalToVworld().transform(position, xformPosition); + + dispatchAttribChange(POSITION_DIRTY_BIT, (new Point3f(this.position))); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Sets this sound's position from the three values provided. + * @param x the new x position + * @param y the new y position + * @param z the new z position + */ + void setPosition(float x, float y, float z) { + position.x = x; + position.y = y; + position.z = z; + if (staticTransform != null) { + staticTransform.transform.transform(this.position); + } + + getLastLocalToVworld().transform(position, xformPosition); + + dispatchAttribChange(POSITION_DIRTY_BIT, (new Point3f(this.position))); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves this sound's location and places it in the vector provided. + * @param position the variable to receive the location vector + */ + void getPosition(Point3f position) { + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + invTransform.transform(this.position, position); + } else { + position.set(this.position); + } + } + + void getXformPosition(Point3f position) { + position.set(this.xformPosition); + } + + /** + * Sets this sound's distance gain attenuation - where gain scale factor + * is applied to sound based on distance listener is from sound source. + * @param distance attenuation pairs of (distance,gain-scale-factor) + */ + void setDistanceGain(Point2f[] attenuation) { + // if attenuation array null set both attenuation components to null + if (attenuation == null) { + this.attenuationDistance = null; + this.attenuationGain = null; + // QUESTION: is this needed so that dispatch***() doesn't + // fail with null? + return; + } + + int attenuationLength = attenuation.length; + this.attenuationDistance = new float[attenuationLength]; + this.attenuationGain = new float[attenuationLength]; + for (int i = 0; i < attenuationLength; i++) { + this.attenuationDistance[i] = attenuation[i].x; + this.attenuationGain[i] = attenuation[i].y; + } + dispatchAttribChange(DISTANCE_GAIN_DIRTY_BIT, attenuation); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Sets this sound's distance gain given separate arrays. + * applied to sound based on distance listener is from sound source. + * @param distance array of monotonically-increasing floats. + * @param gain array of amplitude scale factors associated with distances. + */ + void setDistanceGain(float[] distance, float[] gain) { + // if distance or gain arrays are null then treat both as null + if (distance == null) { + this.attenuationDistance = null; + this.attenuationGain = null; + // QUESTION: is this needed so that dispatch***() doesn't + // fail with null? + return; + } + + int gainLength = gain.length; + int distanceLength = distance.length; + this.attenuationDistance = new float[distanceLength]; + this.attenuationGain = new float[distanceLength]; + // Copy the distance array into nodes field + System.arraycopy(distance, 0, this.attenuationDistance, 0, distanceLength); + // Copy the gain array an array of same length as the distance array + if (distanceLength <= gainLength) { + System.arraycopy(gain, 0, this.attenuationGain, 0, distanceLength); + } + else { + System.arraycopy(gain, 0, this.attenuationGain, 0, gainLength); + // Extend gain array to length of distance array + // replicate last gain values. + for (int i=gainLength; i< distanceLength; i++) { + this.attenuationGain[i] = gain[gainLength - 1]; + } + } + Point2f [] attenuation = new Point2f[distanceLength]; + for (int i=0; i<distanceLength; i++) { + attenuation[i] = new Point2f(this.attenuationDistance[i], + this.attenuationGain[i]); + } + dispatchAttribChange(DISTANCE_GAIN_DIRTY_BIT, attenuation); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Gets this sound's distance attenuation array length + * @return distance gain attenuation array length + */ + int getDistanceGainLength() { + if (attenuationDistance == null) + return 0; + else + return this.attenuationDistance.length; + } + + /** + * Retieves sound's distance attenuation + * Put the contents of the two separate distance and gain arrays into + * an array of Point2f. + * @param attenuation containing distance attenuation pairs + */ + void getDistanceGain(Point2f[] attenuation) { + // write into arrays passed in, don't do a new + if (attenuation == null) + return; + if (this.attenuationDistance == null || + this.attenuationGain == null) + return; + int attenuationLength = attenuation.length; + // attenuationDistance and Gain array lengths should be the same + int distanceLength = this.attenuationDistance.length; + if (distanceLength > attenuationLength) + distanceLength = attenuationLength; + for (int i=0; i< distanceLength; i++) { + attenuation[i].x = attenuationDistance[i]; + attenuation[i].y = attenuationGain[i]; + } + } + + /** + * Retieves this sound's attenuation distance and gain arrays, returned in + * separate arrays. + * @param distance array of monotonically-increasing floats. + * @param gain array of amplitude scale factors associated with distances. + */ + void getDistanceGain(float[] distance, float[] gain) { + // write into arrays passed in, don't do a new + if (distance == null || gain == null) + return; + if (this.attenuationDistance == null || this.attenuationGain == null) + return; + // These two array length should be the same + int attenuationLength = this.attenuationDistance.length; + int distanceLength = distance.length; + if (distanceLength > attenuationLength) + distanceLength = attenuationLength; + System.arraycopy(this.attenuationDistance, 0, distance, 0, distanceLength); + attenuationLength = this.attenuationDistance.length; + int gainLength = gain.length; + if (gainLength > attenuationLength) + gainLength = attenuationLength; + System.arraycopy(this.attenuationGain, 0, gain, 0, gainLength); + } + + /** + * This updates the positional fields of point sound. + * + * Distance gain attenuation field not maintained in mirror object. + */ + void updateMirrorObject(Object[] objs) { + if (debugFlag) + debugPrint("PointSoundRetained:updateMirrorObj()"); + int component = ((Integer)objs[1]).intValue(); + int numSnds = ((Integer)objs[2]).intValue(); + SoundRetained[] mSnds = (SoundRetained[]) objs[3]; + if (component == -1) { + // update every field + initMirrorObject(((PointSoundRetained)objs[2])); + return; + } + if ((component & POSITION_DIRTY_BIT) != 0) { + for (int i = 0; i < numSnds; i++) { + PointSoundRetained point = (PointSoundRetained)mSnds[i]; + point.position = (Point3f)objs[4]; + point.getLastLocalToVworld().transform(point.position, + point.xformPosition); + } + } + + // call the parent's mirror object update routine + super.updateMirrorObject(objs); + } + + synchronized void initMirrorObject(PointSoundRetained ms) { + super.initMirrorObject(ms); + ms.position.set(this.position); + ms.xformPosition.set(this.xformPosition); + } + + // Called on the mirror object + void updateTransformChange() { + super.updateTransformChange(); + getLastLocalToVworld().transform(position, xformPosition); + // set flag looked at by Scheduler to denote Transform change + // this flag will force resneding transformed position to AudioDevice + if (debugFlag) + debugPrint("PointSoundRetained xformPosition is (" + xformPosition.x + + ", " + xformPosition.y + ", "+ xformPosition.z + ")"); + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + xform.transform.transform(position, position); + } +} diff --git a/src/classes/share/javax/media/j3d/PolygonAttributes.java b/src/classes/share/javax/media/j3d/PolygonAttributes.java new file mode 100644 index 0000000..96ffddb --- /dev/null +++ b/src/classes/share/javax/media/j3d/PolygonAttributes.java @@ -0,0 +1,464 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The PolygonAttributes object defines attributes for rendering polygon + * primitives. + * Polygon primitives include triangles, triangle strips, triangle fans, + * and quads. + * The polygon attributes that can be defined are:</li> + * <p><ul> + * <li>Rasterization mode - defines how the polygon is drawn: as points, + * outlines, or filled.<p> + * <ul> + * <li>POLYGON_POINT - the polygon is rendered as points + * drawn at the vertices.</li><p> + * <li>POLYGON_LINE - the polygon is rendered as lines + * drawn between consecutive vertices.</li><p> + * <li>POLYGON_FILL - the polygon is rendered by filling the interior + * between the vertices. The default mode.</li> + * <p></ul> + * <li>Face culling - defines which polygons are culled (discarded) + * before they are converted to screen coordinates.<p> + * <ul> + * <li>CULL_NONE - disables face culling.</li> + * <li>CULL_BACK - culls all back-facing polygons. The default.</li> + * <li>CULL_FRONT - culls all front-facing polygons.</li> + * <p></ul> + * <li>Back-face normal flip - specifies whether vertex normals of + * back-facing polygons are flipped (negated) prior to lighting. The + * setting is either true, meaning to flip back-facing normals, or + * false. The default is false.</li> + * <p> + * <li>Offset - the depth values of all pixels generated by polygon + * rasterization can be offset by a value that is computed for that + * polygon. Two values are used to specify the offset:</li><p> + * <ul> + * <li>Offset bias - the constant polygon offset that is added to + * the final device coordinate Z value of polygon primitives.</li> + * <p> + * <li>Offset factor - the factor to be multiplied by the + * slope of the polygon and then added to the final, device coordinate + * Z value of the polygon primitives.</li><p> + * </ul> + * These values can be either positive or negative. The default + * for both of these values is 0.0.<p> + * </ul> + * + * @see Appearance + */ +public class PolygonAttributes extends NodeComponent { + + /** + * Specifies that this PolygonAttributes object allows reading its + * cull face information. + */ + public static final int + ALLOW_CULL_FACE_READ = CapabilityBits.POLYGON_ATTRIBUTES_ALLOW_CULL_FACE_READ; + + /** + * Specifies that this PolygonAttributes object allows writing its + * cull face information. + */ + public static final int + ALLOW_CULL_FACE_WRITE = CapabilityBits.POLYGON_ATTRIBUTES_ALLOW_CULL_FACE_WRITE; + + /** + * Specifies that this PolygonAttributes object allows reading its + * back face normal flip flag. + */ + public static final int + ALLOW_NORMAL_FLIP_READ = CapabilityBits.POLYGON_ATTRIBUTES_ALLOW_NORMAL_FLIP_READ; + + /** + * Specifies that this PolygonAttributes object allows writing its + * back face normal flip flag. + */ + public static final int + ALLOW_NORMAL_FLIP_WRITE = CapabilityBits.POLYGON_ATTRIBUTES_ALLOW_NORMAL_FLIP_WRITE; + + /** + * Specifies that this PolygonAttributes object allows reading its + * polygon mode information. + */ + public static final int + ALLOW_MODE_READ = CapabilityBits.POLYGON_ATTRIBUTES_ALLOW_MODE_READ; + + /** + * Specifies that this PolygonAttributes object allows writing its + * polygon mode information. + */ + public static final int + ALLOW_MODE_WRITE = CapabilityBits.POLYGON_ATTRIBUTES_ALLOW_MODE_WRITE; + + /** + * Specifies that this PolygonAttributes object allows reading its + * polygon offset information. + */ + public static final int + ALLOW_OFFSET_READ = CapabilityBits.POLYGON_ATTRIBUTES_ALLOW_OFFSET_READ; + + /** + * Specifies that this PolygonAttributes object allows writing its + * polygon offset information. + */ + public static final int + ALLOW_OFFSET_WRITE = CapabilityBits.POLYGON_ATTRIBUTES_ALLOW_OFFSET_WRITE; + + // Polygon rasterization modes + /** + * Render polygonal primitives as points drawn at the vertices + * of the polygon. + */ + public static final int POLYGON_POINT = 0; + /** + * Render polygonal primitives as lines drawn between consecutive + * vertices of the polygon. + */ + public static final int POLYGON_LINE = 1; + /** + * Render polygonal primitives by filling the interior of the polygon. + */ + public static final int POLYGON_FILL = 2; + + /** + * Don't perform any face culling. + */ + public static final int CULL_NONE = 0; + /** + * Cull all back-facing polygons. This is the default mode. + */ + public static final int CULL_BACK = 1; + /** + * Cull all front-facing polygons. + */ + public static final int CULL_FRONT = 2; + + /** + * Constructs a PolygonAttributes object with default parameters. + * The default values are as follows: + * <ul> + * cull face : CULL_BACK<br> + * back face normal flip : false<br> + * polygon mode : POLYGON_FILL<br> + * polygon offset : 0.0<br> + * polygon offset factor : 0.0<br> + * </ul> + */ + public PolygonAttributes() { + // Just use defaults for all attributes + } + + /** + * Constructs a PolygonAttributes object with specified values. + * @param polygonMode polygon rasterization mode; one of POLYGON_POINT, + * POLYGON_LINE, or POLYGON_FILL + * @param cullFace polygon culling mode; one of CULL_NONE, + * CULL_BACK, or CULL_FRONT + * @param polygonOffset constant polygon offset + */ + public PolygonAttributes(int polygonMode, + int cullFace, + float polygonOffset) { + this(polygonMode, cullFace, polygonOffset, false, 0.0f); + } + + /** + * Constructs PolygonAttributes object with specified values. + * @param polygonMode polygon rasterization mode; one of POLYGON_POINT, + * POLYGON_LINE, or POLYGON_FILL + * @param cullFace polygon culling mode; one of CULL_NONE, + * CULL_BACK, or CULL_FRONT + * @param polygonOffset constant polygon offset + * @param backFaceNormalFlip back face normal flip flag; true or false + */ + public PolygonAttributes(int polygonMode, + int cullFace, + float polygonOffset, + boolean backFaceNormalFlip) { + this(polygonMode, cullFace, polygonOffset, backFaceNormalFlip, 0.0f); + } + + /** + * Constructs PolygonAttributes object with specified values. + * @param polygonMode polygon rasterization mode; one of POLYGON_POINT, + * POLYGON_LINE, or POLYGON_FILL + * @param cullFace polygon culling mode; one of CULL_NONE, + * CULL_BACK, or CULL_FRONT + * @param polygonOffset constant polygon offset + * @param backFaceNormalFlip back face normal flip flag; true or false + * @param polygonOffsetFactor polygon offset factor for slope-based polygon + * offset + * + * @since Java 3D 1.2 + */ + public PolygonAttributes(int polygonMode, + int cullFace, + float polygonOffset, + boolean backFaceNormalFlip, + float polygonOffsetFactor) { + + if (polygonMode < POLYGON_POINT || polygonMode > POLYGON_FILL) + throw new IllegalArgumentException(J3dI18N.getString("PolygonAttributes0")); + + if (cullFace < CULL_NONE || cullFace > CULL_FRONT) + throw new IllegalArgumentException(J3dI18N.getString("PolygonAttributes12")); + + ((PolygonAttributesRetained)this.retained).initPolygonMode(polygonMode); + ((PolygonAttributesRetained)this.retained).initCullFace(cullFace); + ((PolygonAttributesRetained)this.retained).initPolygonOffset(polygonOffset); + ((PolygonAttributesRetained)this.retained).initBackFaceNormalFlip(backFaceNormalFlip); + ((PolygonAttributesRetained)this.retained).initPolygonOffsetFactor(polygonOffsetFactor); + } + + /** + * Sets the face culling for this + * appearance component object. + * @param cullFace the face to be culled, one of: + * CULL_NONE, CULL_FRONT, or CULL_BACK + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setCullFace(int cullFace) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CULL_FACE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes2")); + + if (cullFace < CULL_NONE || cullFace > CULL_FRONT) + throw new IllegalArgumentException(J3dI18N.getString("PolygonAttributes3")); + if (isLive()) + ((PolygonAttributesRetained)this.retained).setCullFace(cullFace); + else + ((PolygonAttributesRetained)this.retained).initCullFace(cullFace); + + } + + /** + * Gets the face culling for this + * appearance component object. + * @return the face to be culled + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getCullFace() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CULL_FACE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes4")); + + return ((PolygonAttributesRetained)this.retained).getCullFace(); + } + + /** + * Sets the back face normal flip flag to the specified value. + * This flag indicates whether vertex normals of back facing polygons + * should be flipped (negated) prior to lighting. When this flag + * is set to true and back face culling is disabled, polygons are + * rendered as if the polygon had two sides with opposing normals. + * This feature is disabled by default. + * @param backFaceNormalFlip the back face normal flip flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setBackFaceNormalFlip(boolean backFaceNormalFlip) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_FLIP_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes5")); + if (isLive()) + ((PolygonAttributesRetained)this.retained).setBackFaceNormalFlip(backFaceNormalFlip); + else + ((PolygonAttributesRetained)this.retained).initBackFaceNormalFlip(backFaceNormalFlip); + + } + + /** + * Gets the back face normal flip flag. + * @return the back face normal flip flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getBackFaceNormalFlip() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_NORMAL_FLIP_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes6")); + + return ((PolygonAttributesRetained)this.retained).getBackFaceNormalFlip(); + } + + /** + * Sets the polygon rasterization mode for this + * appearance component object. + * @param polygonMode the polygon rasterization mode to be used; one of + * POLYGON_FILL, POLYGON_LINE, or POLYGON_POINT + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPolygonMode(int polygonMode) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_MODE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes7")); + + if (polygonMode < POLYGON_POINT || polygonMode > POLYGON_FILL) + throw new IllegalArgumentException(J3dI18N.getString("PolygonAttributes8")); + if (isLive()) + ((PolygonAttributesRetained)this.retained).setPolygonMode(polygonMode); + else + ((PolygonAttributesRetained)this.retained).initPolygonMode(polygonMode); + + } + + /** + * Gets the polygon rasterization mode for this + * appearance component object. + * @return the polygon rasterization mode + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getPolygonMode() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes9")); + + return ((PolygonAttributesRetained)this.retained).getPolygonMode(); + } + + /** + * Sets the constant polygon offset to the specified value. + * This screen space + * offset is added to the final, device coordinate Z value of polygon + * primitives. + * @param polygonOffset the constant polygon offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPolygonOffset(float polygonOffset) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes10")); + + if (isLive()) + ((PolygonAttributesRetained)this.retained).setPolygonOffset(polygonOffset); + else + ((PolygonAttributesRetained)this.retained).initPolygonOffset(polygonOffset); + + } + + /** + * Gets the constant polygon offset. + * @return the constant polygon offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getPolygonOffset() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes11")); + + return ((PolygonAttributesRetained)this.retained).getPolygonOffset(); + } + + /** + * Sets the polygon offset factor to the specified value. + * This factor is multiplied by the slope of the polygon, and + * then added to the final, device coordinate Z value of polygon + * primitives. + * @param polygonOffsetFactor the polygon offset factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void setPolygonOffsetFactor(float polygonOffsetFactor) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes10")); + + if (isLive()) + ((PolygonAttributesRetained)this.retained). + setPolygonOffsetFactor(polygonOffsetFactor); + else + ((PolygonAttributesRetained)this.retained). + initPolygonOffsetFactor(polygonOffsetFactor); + } + + /** + * Gets the polygon offset factor. + * @return the polygon offset factor. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public float getPolygonOffsetFactor() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("PolygonAttributes11")); + + return ((PolygonAttributesRetained)this.retained).getPolygonOffsetFactor(); + } + + /** + * Creates a retained mode PolygonAttributesRetained object that this + * PolygonAttributes component object will point to. + */ + void createRetained() { + this.retained = new PolygonAttributesRetained(); + this.retained.setSource(this); + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + PolygonAttributes pga = new PolygonAttributes(); + pga.duplicateNodeComponent(this); + return pga; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + PolygonAttributesRetained attr = (PolygonAttributesRetained) + originalNodeComponent.retained; + + PolygonAttributesRetained rt = (PolygonAttributesRetained) retained; + + rt.initCullFace(attr.getCullFace()); + rt.initBackFaceNormalFlip(attr.getBackFaceNormalFlip()); + rt.initPolygonMode(attr.getPolygonMode()); + rt.initPolygonOffset(attr.getPolygonOffset()); + rt.initPolygonOffsetFactor(attr.getPolygonOffsetFactor()); + } +} diff --git a/src/classes/share/javax/media/j3d/PolygonAttributesRetained.java b/src/classes/share/javax/media/j3d/PolygonAttributesRetained.java new file mode 100644 index 0000000..14792a1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/PolygonAttributesRetained.java @@ -0,0 +1,351 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * The PolygonAttributes object defines all rendering state that can be set + * as a component object of a Shape3D node. + */ +class PolygonAttributesRetained extends NodeComponentRetained { + // A list of pre-defined bits to indicate which component + // in this LineAttributesRetained object changed. + static final int POLYGON_MODE_CHANGED = 0x01; + static final int POLYGON_CULL_CHANGED = 0x02; + static final int POLYGON_OFFSET_CHANGED = 0x04; + static final int POLYGON_BACKFACENORMALFLIP_CHANGED = 0x08; + static final int POLYGON_OFFSETFACTOR_CHANGED = 0x10; + + // Polygon rasterization mode (point, line, fill) + int polygonMode = PolygonAttributes.POLYGON_FILL; + + // Face culling mode + int cullFace = PolygonAttributes.CULL_BACK; + + // back face normal flip flag + boolean backFaceNormalFlip = false; + + // constant polygon offset + float polygonOffset; + + // polygon offset factor + float polygonOffsetFactor; + + /** + * Sets the face culling for this + * appearance component object, + * @param cullFace the face to be culled, one of: + * CULL_NONE, CULL_FRONT, or CULL_BACK + */ + final void initCullFace(int cullFace) { + this.cullFace = cullFace; + } + + /** + * Sets the face culling for this + * appearance component object and sends a message notifying + * the interested structures of the change. + * @param cullFace the face to be culled, one of: + * CULL_NONE, CULL_FRONT, or CULL_BACK + */ + final void setCullFace(int cullFace) { + initCullFace(cullFace); + sendMessage(POLYGON_CULL_CHANGED, new Integer(cullFace)); + } + + /** + * Gets the face culling for this + * appearance component object. + * @return the face to be culled + */ + final int getCullFace() { + return cullFace; + } + + /** + * Sets the back face normal flip flag to the specified value + * This flag indicates whether vertex normals of back facing polygons + * should be flipped (negated) prior to lighting. When this flag + * is set to true and back face culling is disabled, polygons are + * rendered as if the polygon had two sides with opposing normals. + * This feature is disabled by default + * @param backFaceNormalFlip the back face normal flip flag + */ + final void initBackFaceNormalFlip(boolean backFaceNormalFlip) { + this.backFaceNormalFlip = backFaceNormalFlip; + } + + /** + * Sets the back face normal flip flag to the specified value + * and sends a message notifying + * the interested structures of the change. + * This flag indicates whether vertex normals of back facing polygons + * should be flipped (negated) prior to lighting. When this flag + * is set to true and back face culling is disabled, polygons are + * rendered as if the polygon had two sides with opposing normals. + * This feature is disabled by default + * @param backFaceNormalFlip the back face normal flip flag + */ + final void setBackFaceNormalFlip(boolean backFaceNormalFlip) { + initBackFaceNormalFlip(backFaceNormalFlip); + sendMessage(POLYGON_BACKFACENORMALFLIP_CHANGED, + (backFaceNormalFlip ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Gets the back face normal flip flag. + * @return the back face normal flip flag + */ + final boolean getBackFaceNormalFlip() { + return backFaceNormalFlip; + } + + /** + * Sets the polygon rasterization mode for this + * appearance component object. + * @param polygonMode the polygon rasterization mode to be used; one of + * POLYGON_FILL, POLYGON_LINE, or POLYGON_POINT + */ + final void initPolygonMode(int polygonMode) { + this.polygonMode = polygonMode; + } + + /** + * Sets the polygon rasterization mode for this + * appearance component object and sends a message notifying + * the interested structures of the change. + * @param polygonMode the polygon rasterization mode to be used; one of + * POLYGON_FILL, POLYGON_LINE, or POLYGON_POINT + */ + final void setPolygonMode(int polygonMode) { + initPolygonMode(polygonMode); + sendMessage(POLYGON_MODE_CHANGED, new Integer(polygonMode)); + } + + /** + * Gets the polygon rasterization mode for this + * appearance component object. + * @return polygonMode the polygon rasterization mode + */ + final int getPolygonMode() { + return polygonMode; + } + + /** + * Sets the polygon offset to the specified value and sends a + * message notifying the interested structures of the change. + * This screen space offset is added to the final, device + * coordinate Z value of polygon primitives. + * @param polygonOffset the polygon offset + */ + final void initPolygonOffset(float polygonOffset) { + this.polygonOffset = polygonOffset; + } + + /** + * Sets the polygon offset to the specified value and sends a + * message notifying the interested structures of the change. + * This screen space offset is added to the final, device + * coordinate Z value of polygon primitives. + * @param polygonOffset the polygon offset + */ + final void setPolygonOffset(float polygonOffset) { + initPolygonOffset(polygonOffset); + sendMessage(POLYGON_OFFSET_CHANGED, new Float(polygonOffset)); + } + + + /** + * Gets the polygon offset. + * @return polygonOffset the polygon offset + */ + final float getPolygonOffset() { + return polygonOffset; + } + + + /** + * Sets the polygon offset factor to the specified value and sends a + * message notifying the interested structures of the change. + * This factor is multiplied by the slope of the polygon, and + * then added to the final, device coordinate Z value of polygon + * primitives. + * @param polygonOffsetFactor the polygon offset factor + */ + final void initPolygonOffsetFactor(float polygonOffsetFactor) { + this.polygonOffsetFactor = polygonOffsetFactor; + } + + /** + * Sets the polygon offset factor to the specified value and sends a + * message notifying the interested structures of the change. + * This factor is multiplied by the slope of the polygon, and + * then added to the final, device coordinate Z value of polygon + * primitives. + * @param polygonOffsetFactor the polygon offset + */ + final void setPolygonOffsetFactor(float polygonOffsetFactor) { + initPolygonOffsetFactor(polygonOffsetFactor); + sendMessage(POLYGON_OFFSETFACTOR_CHANGED, + new Float(polygonOffsetFactor)); + } + + + /** + * Gets the polygon offset factor. + * @return polygonOffset the polygon offset factor + */ + final float getPolygonOffsetFactor() { + return polygonOffsetFactor; + } + + /** + * Creates and initializes a mirror object, point the mirror object + * to the retained object if the object is not editable + */ + synchronized void createMirrorObject() { + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror = this; + } else { + PolygonAttributesRetained mirrorPa = new PolygonAttributesRetained(); + mirrorPa.set(this); + mirrorPa.source = source; + mirror = mirrorPa; + } + } else { + ((PolygonAttributesRetained) mirror).set(this); + } + } + + + /** + * Updates the native context + */ + native void updateNative(long ctx, + int polygonMode, int cullFace, + boolean backFaceNormalFlip, + float polygonOffset, + float polygonOffsetFactor); + + /** + * Updates the native context + */ + void updateNative(long ctx) { + updateNative(ctx, + polygonMode, cullFace, backFaceNormalFlip, + polygonOffset, polygonOffsetFactor); + } + + /** + * Initializes a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + ((PolygonAttributesRetained) mirror).set(this); + } + + /** + * Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + + PolygonAttributesRetained mirrorPa = (PolygonAttributesRetained) mirror; + + if ((component & POLYGON_MODE_CHANGED) != 0) { + mirrorPa.polygonMode = ((Integer)value).intValue(); + } + else if ((component & POLYGON_CULL_CHANGED) != 0) { + mirrorPa.cullFace = ((Integer)value).intValue(); + } + else if ((component & POLYGON_BACKFACENORMALFLIP_CHANGED) != 0) { + mirrorPa.backFaceNormalFlip = ((Boolean)value).booleanValue(); + } + else if ((component & POLYGON_OFFSET_CHANGED) != 0) { + mirrorPa.polygonOffset = ((Float)value).floatValue(); + } + else if ((component & POLYGON_OFFSETFACTOR_CHANGED) != 0) { + mirrorPa.polygonOffsetFactor = ((Float) value).floatValue(); + } + } + + + boolean equivalent(PolygonAttributesRetained pr) { + return ((pr != null) && + (pr.cullFace == cullFace) && + (pr.backFaceNormalFlip == backFaceNormalFlip) && + (pr.polygonOffset == polygonOffset) && + (pr.polygonMode == polygonMode) && + (pr.polygonOffsetFactor == polygonOffsetFactor)); + } + + protected void set(PolygonAttributesRetained pr) { + super.set(pr); + cullFace = pr.cullFace; + backFaceNormalFlip = pr.backFaceNormalFlip; + polygonMode = pr.polygonMode; + polygonOffset = pr.polygonOffset; + polygonOffsetFactor = pr.polygonOffsetFactor; + } + + final void sendMessage(int attrMask, Object attr) { + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.POLYGONATTRIBUTES_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.POLYGONATTRIBUTES_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + + + } + void handleFrequencyChange(int bit) { + if (bit == PolygonAttributes.ALLOW_CULL_FACE_WRITE || + bit == PolygonAttributes.ALLOW_NORMAL_FLIP_WRITE|| + bit == PolygonAttributes.ALLOW_MODE_WRITE || + bit == PolygonAttributes.ALLOW_OFFSET_WRITE) { + setFrequencyChangeMask(bit, 0x1); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/PositionInterpolator.java b/src/classes/share/javax/media/j3d/PositionInterpolator.java new file mode 100644 index 0000000..627453d --- /dev/null +++ b/src/classes/share/javax/media/j3d/PositionInterpolator.java @@ -0,0 +1,204 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Vector3d; + + +/** + * Position interpolator behavior. This class defines a behavior + * that modifies the translational component of its target + * TransformGroup by linearly interpolating between a pair of + * specified positions (using the value generated by the + * specified Alpha object). The interpolated position is used + * to generate a translation transform along the local X-axis + * of this interpolator. + */ + +public class PositionInterpolator extends TransformInterpolator { + + private Transform3D translation = new Transform3D(); + private Vector3d transv = new Vector3d(); + + float startPosition; + float endPosition; + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, default constructor used by cloneNode + PositionInterpolator() { + } + + /** + * Constructs a trivial position interpolator with a specified target, + * an axisOfTranslation set to Identity, a startPosition of 0.0f, and + * an endPosition of 1.0f. + * @param alpha The alpha object for this Interpolator + * @param target The target for this position Interpolator + */ + public PositionInterpolator(Alpha alpha, TransformGroup target) { + super(alpha, target); + + this.startPosition = 0.0f; + this.endPosition = 1.0f; + } + + + /** + * Constructs a new position interpolator that varies the target + * TransformGroup's translational component (startPosition and endPosition). + * @param alpha the alpha object for this interpolator + * @param target the transformgroup node effected by this positionInterpolator + * @param axisOfTransform the transform that defines the local coordinate + * system in which this interpolator operates. The translation is + * done along the X-axis of this local coordinate system. + * @param startPosition the starting position + * @param endPosition the ending position + */ + public PositionInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + float startPosition, + float endPosition) { + + super(alpha, target, axisOfTransform ); + + this.startPosition = startPosition; + this.endPosition = endPosition; + } + + /** + * This method sets the startPosition for this interpolator. + * @param position The new start position + */ + public void setStartPosition(float position) { + this.startPosition = position; + } + + /** + * This method retrieves this interpolator's startPosition. + * @return the interpolator's start position value + */ + public float getStartPosition() { + return this.startPosition; + } + + /** + * This method sets the endPosition for this interpolator. + * @param position The new end position + */ + public void setEndPosition(float position) { + this.endPosition = position; + } + + /** + * This method retrieves this interpolator's endPosition. + * @return the interpolator's end position vslue + */ + public float getEndPosition() { + return this.endPosition; + } + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.setTransformAxis(Transform3D)</code> + */ + public void setAxisOfTranslation(Transform3D axisOfTranslation) { + setTransformAxis(axisOfTranslation); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.getTransformAxis()</code> + */ + public Transform3D getAxisOfTranslation() { + return getTransformAxis(); + } + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + + double val = (1.0-alphaValue)*startPosition + alphaValue*endPosition; + + // construct a Transform3D from: axis * translation * axisInverse + transv.set(val, 0.0, 0.0); + translation.setTranslation(transv); + + transform.mul(axis, translation); + transform.mul(transform, axisInverse); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + PositionInterpolator pi = new PositionInterpolator(); + pi.duplicateNode(this, forceDuplicate); + return pi; + } + + /** + * Copies all PositionInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + PositionInterpolator pi = (PositionInterpolator) originalNode; + + setStartPosition(pi.getStartPosition()); + setEndPosition(pi.getEndPosition()); + } +} diff --git a/src/classes/share/javax/media/j3d/PositionPathInterpolator.java b/src/classes/share/javax/media/j3d/PositionPathInterpolator.java new file mode 100644 index 0000000..544d68b --- /dev/null +++ b/src/classes/share/javax/media/j3d/PositionPathInterpolator.java @@ -0,0 +1,263 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; + + +/** + * PositionPathInterpolator behavior. This class defines a behavior + * that modifies the translational component of its target TransformGroup + * by linearly interpolating among a series of predefined knot/position + * pairs (using the value generated by the specified Alpha object). The + * interpolated position is used to generate a translation transform + * in the local coordinate system of this interpolator. The first knot + * must have a value of 0.0. The last knot must have a value of 1.0. An + * intermediate knot with index k must have a value strictly greater + * than any knot with index less than k. + */ + +public class PositionPathInterpolator extends PathInterpolator { + private Transform3D position = new Transform3D(); + private Vector3f pos = new Vector3f(); + + // Array of positions at each knot + private Point3f positions[]; + private float prevInterpolationValue = Float.NaN; + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, default constructor used by cloneNode + PositionPathInterpolator() { + } + + + /** + * Constructs a new PositionPathInterpolator that varies the transform + * of the target TransformGroup. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node affected by this translator + * @param axisOfTransform the transform that defines the local + * coordinate system in which this interpolator operates + * @param knots an array of knot values that specify interpolation points. + * @param positions an array of position values at the knots. + * @exception IllegalArgumentException if the lengths of the + * knots and positions arrays are not the same. + */ + public PositionPathInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + float[] knots, + Point3f[] positions) { + + super(alpha, target, axisOfTransform, knots); + + if (knots.length != positions.length) + throw new IllegalArgumentException(J3dI18N.getString("PositionPathInterpolator0")); + setPathArrays(positions); + } + + + /** + * Sets the position at the specified index for this + * interpolator. + * @param index the index of the position to be changed + * @param position the new position at the index + */ + public void setPosition(int index, Point3f position) { + this.positions[index].set(position); + } + + + /** + * Retrieves the position value at the specified index. + * @param index the index of the value requested + * @param position the variable to receive the position value at + * the specified index + */ + public void getPosition(int index, Point3f position) { + position.set(this.positions[index]); + } + + + /** + * Replaces the existing arrays of knot values + * and position values with the specified arrays. + * The arrays of knots and positions are copied + * into this interpolator object. + * @param knots a new array of knot values that specify + * interpolation points + * @param positions a new array of position values at the knots + * @exception IllegalArgumentException if the lengths of the + * knots and positions arrays are not the same. + * + * @since Java 3D 1.2 + */ + public void setPathArrays(float[] knots, + Point3f[] positions) { + + if (knots.length != positions.length) + throw new IllegalArgumentException(J3dI18N.getString("PositionPathInterpolator0")); + + setKnots(knots); + setPathArrays(positions); + } + + + // Set the specific arrays for this path interpolator + private void setPathArrays(Point3f[] positions) { + + this.positions = new Point3f[positions.length]; + for(int i = 0; i < positions.length; i++) { + this.positions[i] = new Point3f(); + this.positions[i].set(positions[i]); + } + } + + + /** + * Copies the array of position values from this interpolator + * into the specified array. + * The array must be large enough to hold all of the positions. + * The individual array elements must be allocated by the caller. + * @param positions array that will receive the positions + * + * @since Java 3D 1.2 + */ + public void getPositions(Point3f[] positions) { + for (int i = 0; i < this.positions.length; i++) { + positions[i].set(this.positions[i]); + } + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.setTransformAxis(Transform3D)</code> + */ + public void setAxisOfTranslation(Transform3D axisOfTranslation) { + setTransformAxis(axisOfTranslation); + } + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.getTransformAxis()</code> + */ + public Transform3D getAxisOfTranslation() { + return getTransformAxis(); + } + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + + computePathInterpolation(alphaValue); + + if (currentKnotIndex == 0 && + currentInterpolationValue == 0f) { + pos.x = positions[0].x; + pos.y = positions[0].y; + pos.z = positions[0].z; + } else { + pos.x = positions[currentKnotIndex].x + + (positions[currentKnotIndex+1].x - + positions[currentKnotIndex].x) * currentInterpolationValue; + pos.y = positions[currentKnotIndex].y + + (positions[currentKnotIndex+1].y - + positions[currentKnotIndex].y) * currentInterpolationValue; + pos.z = positions[currentKnotIndex].z + + (positions[currentKnotIndex+1].z - + positions[currentKnotIndex].z) * currentInterpolationValue; + } + position.setIdentity(); + position.setTranslation(pos); + + // construct a Transform3D from: axis * position * axisInverse + transform.mul(axis, position); + transform.mul(transform, axisInverse); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + PositionPathInterpolator ppi = new PositionPathInterpolator(); + ppi.duplicateNode(this, forceDuplicate); + return ppi; + } + + + /** + * Copies all PositionPathInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + PositionPathInterpolator pi = + (PositionPathInterpolator) originalNode; + + int len = pi.getArrayLengths(); + + // No API available to set the size of positions + positions = new Point3f[len]; + + Point3f dupPoint = new Point3f(); + for (int i = 0; i < len; i++) { + positions[i] = new Point3f(); + pi.getPosition(i, dupPoint); + setPosition(i, dupPoint); + } + } +} diff --git a/src/classes/share/javax/media/j3d/QuadArray.java b/src/classes/share/javax/media/j3d/QuadArray.java new file mode 100644 index 0000000..caa8780 --- /dev/null +++ b/src/classes/share/javax/media/j3d/QuadArray.java @@ -0,0 +1,156 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The QuadArray object draws the array of vertices as individual + * quadrilaterals. Each group + * of four vertices defines a quadrilateral to be drawn. + */ + +public class QuadArray extends GeometryArray { + + // non-public, no parameter constructor + QuadArray() {} + + /** + * Constructs an empty QuadArray object with the specified + * number of vertices, and vertex format. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @exception IllegalArgumentException if vertexCount is less than 4 + * or vertexCount is <i>not</i> a multiple of 4 + */ + public QuadArray(int vertexCount, int vertexFormat) { + super(vertexCount,vertexFormat); + + if (vertexCount < 4 || ((vertexCount%4) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("QuadArray0")); + } + + /** + * Constructs an empty QuadArray object with the specified + * number of vertices, and vertex format, number of texture coordinate + * sets, and texture coordinate mapping array. + * + * @param vertexCount the number of vertex elements in this array<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3 or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used. + * + * @exception IllegalArgumentException if vertexCount is less than 4 + * or vertexCount is <i>not</i> a multiple of 4 + * + * @since Java 3D 1.2 + */ + public QuadArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap); + + if (vertexCount < 4 || ((vertexCount%4) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("QuadArray0")); + } + + /** + * Creates the retained mode QuadArrayRetained object that this + * QuadArray object will point to. + */ + void createRetained() { + this.retained = new QuadArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + QuadArrayRetained rt = (QuadArrayRetained) retained; + int texSetCount = rt.getTexCoordSetCount(); + QuadArray q; + if (texSetCount == 0) { + q = new QuadArray(rt.getVertexCount(), + rt.getVertexFormat()); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + q = new QuadArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap); + } + q.duplicateNodeComponent(this); + return q; + } + + +} diff --git a/src/classes/share/javax/media/j3d/QuadArrayRetained.java b/src/classes/share/javax/media/j3d/QuadArrayRetained.java new file mode 100644 index 0000000..eb0d76f --- /dev/null +++ b/src/classes/share/javax/media/j3d/QuadArrayRetained.java @@ -0,0 +1,460 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The QuadArray object draws the array of vertices as individual + * quadrilaterals. Each group + * of four vertices defines a quadrilateral to be drawn. + */ + +class QuadArrayRetained extends GeometryArrayRetained { + + QuadArrayRetained() { + this.geoType = GEO_TYPE_QUAD_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[4]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + pnts[3] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + getVertexData(i++, pnts[3]); + if (intersectRay(pnts, pickRay, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + getVertexData(i++, pnts[3]); + if (intersectSegment(pnts, pickSegment.start, + pickSegment.end, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + getVertexData(i++, pnts[3]); + + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + getVertexData(i++, pnts[3]); + + if (intersectBoundingSphere(pnts, bsphere, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + getVertexData(i++, pnts[3]); + + if (intersectBoundingPolytope(pnts, bpolytope, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + getVertexData(i++, pnts[3]); + + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + getVertexData(i++, pnts[3]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("QuadArrayRetained0")); + default: + throw new RuntimeException("PickShape not supported for intersection "); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + + } + + // intersect pnts[] with every quad in this object + boolean intersect(Point3d[] pnts) { + Point3d[] points = new Point3d[4]; + double dist[] = new double[1]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + points[3] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle + while (i < validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + getVertexData(i++, points[3]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[2], points[3], + pnts[0], pnts[1], pnts[2])) { + return true; + } + } + break; + case 4: // Quad + + while (i < validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + getVertexData(i++, points[3]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[2], pnts[3]) || + intersectTriTri(points[0], points[2], points[3], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[2], points[3], + pnts[0], pnts[2], pnts[3])) { + return true; + } + } + break; + case 2: // Line + while (i < validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + getVertexData(i++, points[3]); + if (intersectSegment(points, pnts[0], pnts[1], dist, + null)) { + return true; + } + } + break; + case 1: // Point + while (i < validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + getVertexData(i++, points[3]); + if (intersectTriPnt(points[0], points[1], points[2], + pnts[0]) || + intersectTriPnt(points[0], points[2], points[3], + pnts[0])) { + return true; + } + } + break; + } + return false; + } + + + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) { + + Point3d[] points = new Point3d[4]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + points[3] = new Point3d(); + + while (i < validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + getVertexData(i++, points[3]); + thisToOtherVworld.transform(points[0]); + thisToOtherVworld.transform(points[1]); + thisToOtherVworld.transform(points[2]); + thisToOtherVworld.transform(points[3]); + if (geom.intersect(points)) { + return true; + } + } // for each quad + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + Point3d[] points = new Point3d[4]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + points[3] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + getVertexData(i++, points[3]); + if (intersectBoundingBox(points, box, null, null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + getVertexData(i++, points[3]); + if (intersectBoundingSphere(points, bsphere, null, + null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + getVertexData(i++, points[3]); + if (intersectBoundingPolytope(points, bpolytope, null, null)) { + return true; + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + // From Graphics Gems IV (pg5) and Graphics Gems II, Pg170 + // The centroid is the area-weighted sum of the centroids of + // disjoint triangles that make up the polygon. + void computeCentroid() { + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + Point3d pnt0 = getPoint3d(); + Point3d pnt1 = getPoint3d(); + Point3d pnt2 = getPoint3d(); + Point3d pnt3 = getPoint3d(); + Vector3d vec = getVector3d(); + Vector3d normal = getVector3d(); + Vector3d tmpvec = getVector3d(); + + double area; + double totalarea = 0; + + centroid.x = 0; + centroid.y = 0; + centroid.z = 0; + + while (i < validVertexCount) { + getVertexData(i++, pnt0); + getVertexData(i++, pnt1); + getVertexData(i++, pnt2); + getVertexData(i++, pnt3); + + // Determine the normal + tmpvec.sub(pnt0, pnt1); + vec.sub(pnt1, pnt2); + + // Do the cross product + normal.cross(tmpvec, vec); + normal.normalize(); + // If a degenerate triangle, don't include + if (Double.isNaN(normal.x+normal.y+normal.z)) + continue; + tmpvec.set(0,0,0); + // compute the area of each triangle + getCrossValue(pnt0, pnt1, tmpvec); + getCrossValue(pnt1, pnt2, tmpvec); + getCrossValue(pnt2, pnt0, tmpvec); + area = normal.dot(tmpvec); + totalarea += area; + centroid.x += (pnt0.x+pnt1.x+pnt2.x) * area; + centroid.y += (pnt0.y+pnt1.y+pnt2.y) * area; + centroid.z += (pnt0.z+pnt1.z+pnt2.z) * area; + + // compute the area of each triangle + tmpvec.set(0,0,0); + getCrossValue(pnt0, pnt2, tmpvec); + getCrossValue(pnt2, pnt3, tmpvec); + getCrossValue(pnt3, pnt0, tmpvec); + area = normal.dot(tmpvec); + totalarea += area; + centroid.x += (pnt3.x+pnt0.x+pnt2.x) * area; + centroid.y += (pnt3.y+pnt0.y+pnt2.y) * area; + centroid.z += (pnt3.z+pnt0.z+pnt2.z) * area; + } + if (totalarea != 0.0) { + area = 1.0/(3.0 * totalarea); + centroid.x *= area; + centroid.y *= area; + centroid.z *= area; + } + freeVector3d(tmpvec); + freeVector3d(vec); + freeVector3d(normal); + freePoint3d(pnt0); + freePoint3d(pnt1); + freePoint3d(pnt2); + freePoint3d(pnt3); + } + + int getClassType() { + return QUAD_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/Raster.java b/src/classes/share/javax/media/j3d/Raster.java new file mode 100644 index 0000000..8057b50 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Raster.java @@ -0,0 +1,749 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.awt.Point; +import java.awt.Dimension; + + +/** + * The Raster object extends Geometry to allow drawing a raster image + * that is attached to a 3D location in the virtual world. + * It contains a 3D point that is defined in the local object + * coordinate system of the Shape3D node that references the Raster. + * It also contains a type specifier, a clipping mode, a reference to + * a ImageComponent2D object and/or a DepthComponent object, an + * integer x,y source offset and a size (width, height) to allow + * reading or writing a portion of the referenced image, and an + * integer x,y destination offset to position the raster relative to + * the transformed 3D point. + * In addition to being used as a type of geometry for drawing, + * a Raster may be used to readback pixel data (color and/or z-buffer) + * from the frame buffer in immediate mode. + * <p> + * The geometric extent of a Raster object is a single 3D point, specified + * by the raster position. This means that geometry-based picking or + * collision with a Raster object will only intersect the object at + * this single point; the 2D raster image is neither pickable + * nor collidable. + */ + +public class Raster extends Geometry { + /** + * Specifies a Raster object with color data. + * In this mode, the image reference must point to + * a valid ImageComponent object. + * + * @see #setType + */ + public static final int RASTER_COLOR = 0x1; + + /** + * Specifies a Raster object with depth (z-buffer) data. + * In this mode, the depthImage reference must point to + * a valid DepthComponent object. + * + * @see #setType + */ + public static final int RASTER_DEPTH = 0x2; + + /** + * Specifies a Raster object with both color and depth (z-buffer) data. + * In this mode, the image reference must point to + * a valid ImageComponent object, and the depthImage reference + * must point to a valid DepthComponent object. + * + * @see #setType + */ + public static final int RASTER_COLOR_DEPTH = RASTER_COLOR | RASTER_DEPTH; + + + /** + * Specifies that this raster object is not drawn + * if the raster position is outside the viewing volume. + * In this mode, the raster is not drawn when the transformed + * raster position is clipped out, even if part of the raster would + * have been visible. This is the default mode. + * + * @see #setClipMode + * + * @since Java 3D 1.3 + */ + public static final int CLIP_POSITION = 0; + + /** + * Specifies that the raster object is clipped as an image after + * the raster position has been transformed. In this mode, part + * of the raster may be drawn even when the transformed raster + * position is clipped out. + * + * @see #setClipMode + * + * @since Java 3D 1.3 + */ + public static final int CLIP_IMAGE = 1; + + + /** + * Specifies that this Raster allows reading the position. + */ + public static final int + ALLOW_POSITION_READ = CapabilityBits.RASTER_ALLOW_POSITION_READ; + + /** + * Specifies that this Raster allows writing the position. + */ + public static final int + ALLOW_POSITION_WRITE = CapabilityBits.RASTER_ALLOW_POSITION_WRITE; + + /** + * Specifies that this Raster allows reading the source or + * destination offset. + */ + public static final int + ALLOW_OFFSET_READ = CapabilityBits.RASTER_ALLOW_OFFSET_READ; + + /** + * Specifies that this Raster allows writing the source or + * destination offset. + */ + public static final int + ALLOW_OFFSET_WRITE = CapabilityBits.RASTER_ALLOW_OFFSET_WRITE; + + /** + * Specifies that this Raster allows reading the image. + */ + public static final int + ALLOW_IMAGE_READ = CapabilityBits.RASTER_ALLOW_IMAGE_READ; + + /** + * Specifies that this Raster allows writing the image. + */ + public static final int + ALLOW_IMAGE_WRITE = CapabilityBits.RASTER_ALLOW_IMAGE_WRITE; + + /** + * Specifies that this Raster allows reading the depth component. + */ + public static final int + ALLOW_DEPTH_COMPONENT_READ = CapabilityBits.RASTER_ALLOW_DEPTH_COMPONENT_READ; + + /** + * Specifies that this Raster allows writing the depth component. + */ + public static final int + ALLOW_DEPTH_COMPONENT_WRITE = CapabilityBits.RASTER_ALLOW_DEPTH_COMPONENT_WRITE; + + /** + * Specifies that this Raster allows reading the size. + */ + public static final int + ALLOW_SIZE_READ = CapabilityBits.RASTER_ALLOW_SIZE_READ; + + /** + * Specifies that this Raster allows writing the size. + */ + public static final int + ALLOW_SIZE_WRITE = CapabilityBits.RASTER_ALLOW_SIZE_WRITE; + + /** + * Specifies that this Raster allows reading the type. + */ + public static final int + ALLOW_TYPE_READ = CapabilityBits.RASTER_ALLOW_TYPE_READ; + + /** + * Specifies that this Raster allows reading the clip mode. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_CLIP_MODE_READ = CapabilityBits.RASTER_ALLOW_CLIP_MODE_READ; + + /** + * Specifies that this Raster allows writing the clip mode. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_CLIP_MODE_WRITE = CapabilityBits.RASTER_ALLOW_CLIP_MODE_WRITE; + + + /** + * Constructs a Raster object with default parameters. + * The default values are as follows: + * <ul> + * type : RASTER_COLOR<br> + * clipMode : CLIP_POSITION<br> + * position : (0,0,0)<br> + * srcOffset : (0,0)<br> + * size : (0,0)<br> + * dstOffset : (0,0)<br> + * image : null<br> + * depth component : null<br> + * </ul> + */ + public Raster() { + } + + /** + * Constructs a new Raster object with the specified values. + * @param pos the position in object coordinates of the upper-left + * corner of the raster + * @param type the type of raster object, one of: RASTER_COLOR, + * RASTER_DEPTH, or RASTER_COLOR_DEPTH + * @param xSrcOffset the x offset within the source array of pixels + * at which to start copying + * @param ySrcOffset the y offset within the source array of pixels + * at which to start copying + * @param width the number of columns of pixels to copy + * @param height the number of rows of pixels to copy + * @param image the ImageComponent2D object containing the + * color data + * @param depthComponent the DepthComponent object containing the depth + * (z-buffer) data + */ + public Raster(Point3f pos, + int type, + int xSrcOffset, + int ySrcOffset, + int width, + int height, + ImageComponent2D image, + DepthComponent depthComponent) { + + ((RasterRetained)this.retained).setPosition(pos); + ((RasterRetained)this.retained).setType(type); + ((RasterRetained)this.retained).setSrcOffset(xSrcOffset, ySrcOffset); + ((RasterRetained)this.retained).setSize(width, height); + ((RasterRetained)this.retained).setImage(image); + ((RasterRetained)this.retained).setDepthComponent(depthComponent); + } + + /** + * Constructs a new Raster object with the specified values. + * @param pos the position in object coordinates of the upper-left + * corner of the raster + * @param type the type of raster object, one of: RASTER_COLOR, + * RASTER_DEPTH, or RASTER_COLOR_DEPTH + * @param srcOffset the offset within the source array of pixels + * at which to start copying + * @param size the width and height of the image to be copied + * @param image the ImageComponent2D object containing the + * color data + * @param depthComponent the DepthComponent object containing the depth + * (z-buffer) data + */ + public Raster(Point3f pos, + int type, + Point srcOffset, + Dimension size, + ImageComponent2D image, + DepthComponent depthComponent) { + + ((RasterRetained)this.retained).setPosition(pos); + ((RasterRetained)this.retained).setType(type); + ((RasterRetained)this.retained).setSrcOffset(srcOffset.x, srcOffset.y); + ((RasterRetained)this.retained).setSize(size.width, size.height); + ((RasterRetained)this.retained).setImage(image); + ((RasterRetained)this.retained).setDepthComponent(depthComponent); + } + + /** + * Constructs a new Raster object with the specified values. + * @param pos the position in object coordinates of the upper-left + * corner of the raster + * @param type the type of raster object, one of: RASTER_COLOR, + * RASTER_DEPTH, or RASTER_COLOR_DEPTH + * @param clipMode the clipping mode of the raster object, one of: + * CLIP_POSITION or CLIP_IMAGE + * @param srcOffset the offset within the source array of pixels + * at which to start copying + * @param size the width and height of the image to be copied + * @param dstOffset the destination pixel offset of the upper-left + * corner of the rendered image relative to the transformed position + * @param image the ImageComponent2D object containing the + * color data + * @param depthComponent the DepthComponent object containing the depth + * (z-buffer) data + * + * @since Java 3D 1.3 + */ + public Raster(Point3f pos, + int type, + int clipMode, + Point srcOffset, + Dimension size, + Point dstOffset, + ImageComponent2D image, + DepthComponent depthComponent) { + + ((RasterRetained)this.retained).setPosition(pos); + ((RasterRetained)this.retained).setType(type); + ((RasterRetained)this.retained).setClipMode(clipMode); + ((RasterRetained)this.retained).setSrcOffset(srcOffset.x, srcOffset.y); + ((RasterRetained)this.retained).setSize(size.width, size.height); + ((RasterRetained)this.retained).setDstOffset(dstOffset.x, dstOffset.y); + ((RasterRetained)this.retained).setImage(image); + ((RasterRetained)this.retained).setDepthComponent(depthComponent); + } + + /** + * Creates the retained mode Raster object that this + * Raster object will point to. + */ + void createRetained() { + retained = new RasterRetained(); + retained.setSource(this); + } + + /** + * Sets the position in object coordinates of this raster. This + * position is transformed into device coordinates and is used as + * the upper-left corner of the raster. + * @param pos the new position of this raster + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPosition(Point3f pos) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster0")); + ((RasterRetained)this.retained).setPosition(pos); + } + + /** + * Retrieves the current position in object coordinates of this raster. + * @param pos the vector that will receive the current position + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getPosition(Point3f pos) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster1")); + + ((RasterRetained)this.retained).getPosition(pos); + } + + /** + * Sets the type of this raster object to one of: RASTER_COLOR, + * RASTER_DEPTH, or RASTER_COLOR_DEPTH. + * @param type the new type of this raster + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + public void setType(int type) { + checkForLiveOrCompiled(); + ((RasterRetained)this.retained).setType(type); + } + + + /** + * Retrieves the current type of this raster object, one of: RASTER_COLOR, + * RASTER_DEPTH, or RASTER_COLOR_DEPTH. + * @return type the type of this raster + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getType() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TYPE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster2")); + return (((RasterRetained)this.retained).getType()); + } + + + /** + * Sets the clipping mode of this raster object. + * @param clipMode the new clipping mode of this raster, + * one of: CLIP_POSITION or CLIP_IMAGE. The default mode + * is CLIP_POSITION. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setClipMode(int clipMode) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CLIP_MODE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster10")); + + ((RasterRetained)this.retained).setClipMode(clipMode); + } + + + /** + * Retrieves the current clipping mode of this raster object. + * @return clipMode the clipping mode of this raster, + * one of: CLIP_POSITION or CLIP_IMAGE. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getClipMode() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CLIP_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster11")); + + return (((RasterRetained)this.retained).getClipMode()); + } + + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>setSrcOffset(int,int)</code> + */ + public void setOffset(int xSrcOffset, int ySrcOffset) { + setSrcOffset(xSrcOffset, ySrcOffset); + } + + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>setSrcOffset(java.awt.Point)</code> + */ + public void setOffset(Point srcOffset) { + setSrcOffset(srcOffset); + } + + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>getSrcOffset(java.awt.Point)</code> + */ + public void getOffset(Point srcOffset) { + getSrcOffset(srcOffset); + } + + + /** + * Sets the offset within the source array of pixels + * at which to start copying. + * @param xSrcOffset the x offset within the source array of pixels + * at which to start copying + * @param ySrcOffset the y offset within the source array of pixels + * at which to start copying + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setSrcOffset(int xSrcOffset, int ySrcOffset) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster7")); + + ((RasterRetained)this.retained).setSrcOffset(xSrcOffset, ySrcOffset); + } + + + /** + * Sets the offset within the source array of pixels + * at which to start copying. + * @param srcOffset the new source pixel offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setSrcOffset(Point srcOffset) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster7")); + + ((RasterRetained)this.retained).setSrcOffset(srcOffset.x, srcOffset.y); + } + + + /** + * Retrieves the current source pixel offset. + * @param srcOffset the object that will receive the source offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getSrcOffset(Point srcOffset) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster8")); + + ((RasterRetained)this.retained).getSrcOffset(srcOffset); + } + + + /** + * Sets the number of pixels to be copied from the pixel array. + * @param width the number of columns in the array of pixels to copy + * @param height the number of rows in the array of pixels to copy + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setSize(int width, int height) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SIZE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster9")); + + ((RasterRetained)this.retained).setSize(width, height); + } + + /** + * Sets the size of the array of pixels to be copied. + * @param size the new size + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setSize(Dimension size) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SIZE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster9")); + + ((RasterRetained)this.retained).setSize(size.width, size.height); + } + + + /** + * Retrieves the current raster size. + * @param size the object that will receive the size + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getSize(Dimension size) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster1")); + + ((RasterRetained)this.retained).getSize(size); + } + + + /** + * Sets the destination pixel offset of the upper-left corner of + * the rendered image relative to the transformed position. This + * pixel offset is added to the transformed raster position prior + * to rendering the image. + * + * @param xDstOffset the x coordinate of the new offset + * @param yDstOffset the y coordinate of the new offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setDstOffset(int xDstOffset, int yDstOffset) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster7")); + + ((RasterRetained)this.retained).setDstOffset(xDstOffset, yDstOffset); + } + + + /** + * Sets the destination pixel offset of the upper-left corner of + * the rendered image relative to the transformed position. This + * pixel offset is added to the transformed raster position prior + * to rendering the image. + * + * @param dstOffset the new destination pixel offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setDstOffset(Point dstOffset) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster7")); + + ((RasterRetained)this.retained).setDstOffset(dstOffset.x, dstOffset.y); + } + + + /** + * Retrieves the current destination pixel offset. + * @param dstOffset the object that will receive the destination offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getDstOffset(Point dstOffset) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_OFFSET_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster8")); + + ((RasterRetained)this.retained).getDstOffset(dstOffset); + } + + + /** + * Sets the pixel array used to copy pixels to/from a Canvas3D. + * This is used when the type is RASTER_COLOR or RASTER_COLOR_DEPTH. + * @param image the ImageComponent2D object containing the + * color data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setImage(ImageComponent2D image) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_IMAGE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster3")); + ((RasterRetained)this.retained).setImage(image); + } + + /** + * Retrieves the current pixel array object. + * @return image the ImageComponent2D object containing the + * color data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public ImageComponent2D getImage() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster4")); + return (((RasterRetained)this.retained).getImage()); + } + + /** + * Sets the depth image used to copy pixels to/from a Canvas3D. + * This is used when the type is RASTER_DEPTH or RASTER_COLOR_DEPTH. + * @param depthComponent the DepthComponent object containing the + * depth (z-buffer) data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDepthComponent(DepthComponent depthComponent) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DEPTH_COMPONENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster5")); + ((RasterRetained)this.retained).setDepthComponent(depthComponent); + } + + /** + * Retrieves the current depth image object. + * @return depthImage DepthComponent containing the + * depth (z-buffer) data + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public DepthComponent getDepthComponent() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DEPTH_COMPONENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Raster6")); + return (((RasterRetained)this.retained).getDepthComponent()); + } + + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + Raster r = new Raster(); + r.duplicateNodeComponent(this); + return r; + } + + + /** + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @deprecated replaced with duplicateNodeComponent( + * NodeComponent originalNodeComponent, boolean forceDuplicate) + */ + public void duplicateNodeComponent(NodeComponent originalNodeComponent) { + checkDuplicateNodeComponent(originalNodeComponent); + } + + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + RasterRetained raster = (RasterRetained) originalNodeComponent.retained; + RasterRetained rt = (RasterRetained) retained; + + Point3f p = new Point3f(); + raster.getPosition(p); + rt.setPosition(p); + rt.setType(raster.getType()); + rt.setClipMode(raster.getClipMode()); + Point offset = new Point(); + raster.getSrcOffset(offset); + rt.setSrcOffset(offset.x, offset.y); + raster.getDstOffset(offset); + rt.setDstOffset(offset.x, offset.y); + Dimension dim = new Dimension(); + raster.getSize(dim); + rt.setSize(dim.width, dim.height); + rt.setImage((ImageComponent2D) getNodeComponent( + raster.getImage(), + forceDuplicate, + originalNodeComponent.nodeHashtable)); + rt.setDepthComponent((DepthComponent) getNodeComponent( + raster.getDepthComponent(), + forceDuplicate, + originalNodeComponent.nodeHashtable)); + } + + + /** + * This function is called from getNodeComponent() to see if any of + * the sub-NodeComponents duplicateOnCloneTree flag is true. + * If it is the case, current NodeComponent needs to + * duplicate also even though current duplicateOnCloneTree flag is false. + * This should be overwrite by NodeComponent which contains sub-NodeComponent. + */ + boolean duplicateChild() { + if (getDuplicateOnCloneTree()) + return true; + RasterRetained rt = (RasterRetained) retained; + + NodeComponent nc = rt.getImage(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getDepthComponent(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + return false; + } + +} diff --git a/src/classes/share/javax/media/j3d/RasterRetained.java b/src/classes/share/javax/media/j3d/RasterRetained.java new file mode 100644 index 0000000..e80d706 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RasterRetained.java @@ -0,0 +1,719 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.awt.Point; +import java.awt.Dimension; +import java.util.ArrayList; + +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; + +/** + * A Retained Raster. + */ + +class RasterRetained extends GeometryRetained { + + /** + * Raster type + */ + int type = Raster.RASTER_COLOR; + + int clipMode = Raster.CLIP_POSITION; + Point3f position = new Point3f(); + int xSrcOffset = 0; + int ySrcOffset = 0; + + // Used internally in CLIP_IMAGE mode + private int xOffset = 0; + private int yOffset = 0; + + int width = 0; + int height = 0; + int xDstOffset = 0; + int yDstOffset = 0; + ImageComponent2DRetained image = null; + DepthComponentRetained depthComponent = null; + float lastAlpha = 1.0f; + + private Point3d adjPos; // Position of the Raster after adjusting for dstOffset + private Point2d winCoord; // Position of Raster in window coordinates + private Transform3D vwip; // Vworld to Image plate transform + // false when computeWinCoord() get null RenderMolecule. + // In this case rendering is skip. + private boolean validVwip; + + + RasterRetained() { + this.geoType = GEO_TYPE_RASTER; + + vwip = new Transform3D(); + adjPos = new Point3d(); + winCoord = new Point2d(); + } + + /** + * Set the Raster position + * @param position new raster position + */ + final void setPosition(Point3f pos) { + geomLock.getLock(); + position.x = pos.x; + position.y = pos.y; + position.z = pos.z; + geomLock.unLock(); + sendChangedMessage(J3dThread.UPDATE_GEOMETRY, null, null); + } + + /** + * Retrieves the Raster's position + * @param position the variable to receive the position vector + */ + final void getPosition(Point3f pos) { + pos.x = position.x; + pos.y = position.y; + pos.z = position.z; + } + + /** + * Sets the type of this raster object to one of: RASTER_COLOR, + * RASTER_DEPTH, or RASTER_COLOR_DEPTH. + * @param type the new type of this raster + */ + final void setType(int type) { + geomLock.getLock(); + this.type = type; + geomLock.unLock(); + } + + + /** + * Retrieves the current type of this raster object, one of: RASTER_COLOR, + * RASTER_DEPTH, or RASTER_COLOR_DEPTH. + * @return type the type of this raster + */ + final int getType() { + return type; + } + + /** + * Sets the clipping mode of this raster object. + * @param clipMode the new clipping mode of this raster, + * one of: CLIP_POSITION or CLIP_IMAGE. The default mode + * is CLIP_POSITION. + */ + final void setClipMode(int clipMode) { + + geomLock.getLock(); + this.clipMode = clipMode; + geomLock.unLock(); + computeBoundingBox(); + if(source.isLive()) { + //update the Shape3Ds that refer to this Raster + int un = userLists.size(); + ArrayList shapeList; + Shape3DRetained ms, shape; + int sn; + for(int i = 0; i < un; i++) { + shapeList = (ArrayList)userLists.get(i); + sn = shapeList.size(); + for(int j = 0; j < sn; j++) { + ms = (Shape3DRetained)shapeList.get(j); + shape = (Shape3DRetained)ms.sourceNode; + shape.setBoundsAutoCompute(false); + shape.setBounds(geoBounds); + } + } + } + + } + + + /** + * Retrieves the current clipping mode of this raster object. + * @return clipMode the clipping mode of this raster, + * one of: CLIP_POSITION or CLIP_IMAGE. + */ + final int getClipMode() { + return clipMode; + } + + /** + * Sets the offset within the source array of pixels at which + * to start copying. + * @param xSrcOffset the x offset within the source array of pixels + * at which to start copying + * @param ySrcOffset the y offset within the source array of pixels + * at which to start copying + */ + final void setSrcOffset(int xSrcOffset, int ySrcOffset) { + geomLock.getLock(); + this.xSrcOffset = xSrcOffset; + this.ySrcOffset = ySrcOffset; + geomLock.unLock(); + } + + /** + * Retrieves the current source pixel offset. + * @param srcOffset the object that will receive the source offset + */ + final void getSrcOffset(Point srcOffset) { + srcOffset.setLocation(xSrcOffset, ySrcOffset); + } + + /** + * Sets the number of pixels to be copied from the pixel array. + * @param width the number of columns in the array of pixels to copy + * @param height the number of rows in the array of pixels to copy + */ + final void setSize(int width, int height) { + geomLock.getLock(); + this.width = width; + this.height = height; + geomLock.unLock(); + } + + + /** + * Sets the size of the array of pixels to be copied. + * @param size the new size + */ + final void getSize(Dimension size) { + size.setSize(width, height); + } + + + /** + * Sets the destination pixel offset of the upper-left + * corner of the rendered image relative to the transformed position. + * @param xDstOffset the x coordinate of the new offset + * @param yDstOffset the y coordinate of the new offset + */ + final void setDstOffset(int xDstOffset, int yDstOffset) { + geomLock.getLock(); + this.xDstOffset = xDstOffset; + this.yDstOffset = yDstOffset; + geomLock.unLock(); + } + + /** + * Retrieves the current destination pixel offset. + * @param dstOffset the object that will receive the destination offset + */ + final void getDstOffset(Point dstOffset) { + dstOffset.setLocation(xDstOffset, yDstOffset); + } + + + /** + * Sets the pixel array used to copy pixels to/from a Canvas3D. + * This is used when the type is RASTER_COLOR or RASTER_COLOR_DEPTH. + * @param image the ImageComponent2D object containing the + * color data + */ + final void setImage(ImageComponent2D image) { + ImageComponent2DRetained oldImage = this.image; + + if (this.source.isLive()) { + + if (this.image != null) { + this.image.clearLive(refCount); + } + if (image != null) { + ((ImageComponent2DRetained)image.retained).setLive(inBackgroundGroup, refCount); + } + } + + geomLock.getLock(); + if (image != null) { + ImageComponent2DRetained rimage = + (ImageComponent2DRetained)image.retained; + rimage.setRasterRef(); + this.image = rimage; + } else { + this.image = null; + } + + + + // Set the lastAlpha to 1.0f + lastAlpha = 1.0f; + geomLock.unLock(); + sendChangedMessage((J3dThread.UPDATE_RENDER|J3dThread.UPDATE_RENDERING_ATTRIBUTES), + oldImage, this.image); + } + + /** + * Retrieves the current pixel array object. + * @return image the ImageComponent2D object containing the + * color data + */ + final ImageComponent2D getImage() { + return (image == null ? null : (ImageComponent2D)image.source); + } + + /** + * Sets the depth image used to copy pixels to/from a Canvas3D. + * This is used when the type is RASTER_DEPTH or RASTER_COLOR_DEPTH. + * @param depthImage the DepthComponent object containing the + * depth (z-buffer) data + */ + final void setDepthComponent(DepthComponent depthComponent) { + if (this.source.isLive()) { + if (this.depthComponent != null) { + this.depthComponent.clearLive(refCount); + } + if (depthComponent != null) { + ((DepthComponentRetained)depthComponent.retained).setLive(inBackgroundGroup, refCount); + } + } + geomLock.getLock(); + if (depthComponent == null) { + this.depthComponent = null; + } else { + this.depthComponent = + (DepthComponentRetained)depthComponent.retained; + } + geomLock.unLock(); + } + + /** + * Retrieves the current depth image object. + * @return depthImage DepthComponent containing the + * depth (z-buffer) data + */ + final DepthComponent getDepthComponent() { + return (depthComponent == null ? null : + (DepthComponent)depthComponent.source); + } + + void setLive(boolean inBackgroundGroup, int refCount) { + super.doSetLive(inBackgroundGroup, refCount); + if (image != null) { + image.setLive(inBackgroundGroup, refCount); + } + if (depthComponent != null) { + depthComponent.setLive(inBackgroundGroup, refCount); + } + isEditable = source.getCapability(Raster.ALLOW_OFFSET_WRITE) || + source.getCapability(Raster.ALLOW_POSITION_WRITE) || + ((type & Raster.RASTER_COLOR) != 0 && + source.getCapability(Raster.ALLOW_IMAGE_WRITE)) || + ((type & Raster.RASTER_DEPTH) != 0 && + source.getCapability( + Raster.ALLOW_DEPTH_COMPONENT_WRITE)) || + source.getCapability( Raster.ALLOW_SIZE_WRITE); + + super.markAsLive(); + } + + void clearLive(int refCount) { + super.clearLive(refCount); + if (image != null) + image.clearLive(refCount); + if (depthComponent != null) + depthComponent.clearLive(refCount); + } + /* + // Simply pass along to the NodeComponents + void compile(CompileState compState) { + setCompiled(); + + if (image != null) + image.compile(compState); + + if (depthComponent != null) + depthComponent.compile(compState); + } + */ + + void computeBoundingBox() { + if(clipMode == Raster.CLIP_IMAGE) { + // Disable view frustum culling by setting the raster's bounds to + // infinity. + Point3d minBounds = new Point3d(Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY, + Double.NEGATIVE_INFINITY); + Point3d maxBounds = new Point3d(Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY, + Double.POSITIVE_INFINITY); + geoBounds.setUpper(maxBounds); + geoBounds.setLower(minBounds); + } else { + Point3d center = new Point3d(); + center.x = position.x; + center.y = position.y; + center.z = position.z; + geoBounds.setUpper(center); + geoBounds.setLower(center); + } + } + + void update() { + computeBoundingBox(); + } + + private void sendChangedMessage(int threads, Object arg1, Object arg2) { + + synchronized(liveStateLock) { + if (source.isLive()) { + synchronized (universeList) { + int numMessages = universeList.size(); + J3dMessage[] m = new J3dMessage[numMessages]; + for (int i=0; i<numMessages; i++) { + m[i] = VirtualUniverse.mc.getMessage(); + m[i].type = J3dMessage.GEOMETRY_CHANGED; + m[i].threads = threads; + m[i].args[0] = Shape3DRetained. + getGeomAtomsArray((ArrayList)userLists.get(i)); + m[i].args[1] = this; + Object[] obj = new Object[2]; + obj[0] = arg1; + obj[1] = arg2; + m[i].args[2] = obj; + m[i].args[3] = new Integer(changedFrequent); + m[i].universe = (VirtualUniverse)universeList.get(i); + } + VirtualUniverse.mc.processMessage(m); + } + } + } + } + + + /** + * Native method that does the rendering + */ + native void execute(long ctx, GeometryRetained geo, + boolean updateAlpha, float alpha, + int type, int width, int height, + int xSrcOffset, int ySrcOffset, + float x, float y, float z, byte[] image); + /* + native void executeTiled(long ctx, GeometryRetained geo, + int format, int width, int height, + int xSrcOffset, int ySrcOffset, + int deltaw, int deltah, + float x, float y, float z, byte[] image); + */ + + void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha, + boolean multiScreen, int screen, + boolean ignoreVertexColors, int pass) { + + // Compute the offset position of the raster + // This has to be done at render time because we need access + // to the Canvas3D info + + // Check if adjusted position needs to be computed + + validVwip = true; + adjPos.set((double)position.x, (double)position.y, (double)position.z); + + if(xDstOffset != 0 || yDstOffset != 0) { + getOffsetPos(cv, ra, adjPos); + } + + xOffset = xSrcOffset; + yOffset = ySrcOffset; + // Check if the image needs to be clipped + + if (clipMode == Raster.CLIP_IMAGE) + clipImage(cv, ra, adjPos); + + if (!validVwip) { + return; + } + if ((image != null) && !image.imageYdownCacheDirty) { + // If its a null image do nothing .. + if (image != null && image.imageYdown[0] != null) { + // Handle alpha, if necessary + // Note, raster always makes a copy, so we can update alpha + // in the image + if (updateAlpha) { + // Update Alpha value per screen + // If the image is by reference, force a copy, since + // we need to copy the alpha values + image.updateAlpha(cv, screen, alpha); + execute(cv.ctx, this, updateAlpha, alpha, + type, width, height, xOffset, yOffset, + (float)adjPos.x, (float)adjPos.y , (float)adjPos.z, + image.imageYdown[screen]); + } + else { + execute(cv.ctx, this, updateAlpha, alpha, + type, width, height, xOffset, yOffset, + (float)adjPos.x, (float)adjPos.y , (float)adjPos.z, + image.imageYdown[0]); + } + } + } + /* + else { + // Should never come here ... + if ((type & Raster.RASTER_COLOR) != 0){ + // Send down the tiles + int tilew = image.tilew; + int tileh = image.tileh; + int w = width, h = height; + int curw, curh; + int xo = xOffset, yo = yOffset; + float xpos = position.x, ypos = position.y; + // First find the tile {x.y} to start from + int tileX = 0, tileY = 0; + while (xo > tilew) { + tileX++; + xo -= tilew; + } + + while (yo > tileh) { + tileY++; + yo -= tileh; + } + int initTileY = image.minTileY+tileY; + int initTileX = image.minTileX+tileX; + int m,n; + int deltaw, deltah = 0; + curh = tileh - yo; + for (m = initTileY; m < image.minTileY+image.numYTiles; m++) { + curw = tilew - xo; + deltaw = 0; + w = width; + for (n = initTileX; n < image.minTileX+image.numXTiles; n++) { + java.awt.image.Raster ras; + ras = image.bImage[0].getTile(n,m); + byte[] tmpImage = ((DataBufferByte)ras.getDataBuffer()).getData(); + if (w <curw) { + curw = w; + } + executeTiled(cv.ctx, this, image.storedYdownFormat, + curw, + curh, + xo, yo, + deltaw, deltah, + (float)adjPos.x, (float)adjPos.y , (float)adjPos.z, + tmpImage); + + xo = 0; + w -= curw; + if (w == 0) + break; + deltaw += curw; + curw = tilew; + } + yo = 0; + h -= curh; + if (h == 0) + break; + deltah += curh; + curh = tileh; + if (h < curh) + curh = h; + xo = xOffset; + } + } + if ((type & Raster.RASTER_DEPTH) != 0) { + execute(cv.ctx, this, updateAlpha, alpha, + Raster.RASTER_DEPTH, width, height, + xOffset, yOffset, + (float)adjPos.x, (float)adjPos.y , (float)adjPos.z, + image.imageYdown[screen]); + } + } + */ + } + + /** + * Computes the position of the origin of this Raster in object coordinates + * The origin is the top-left corner offset by the destination offset + * The offset position is returned in objCoord + * + * @param objCoord - Position of the Raster in object coordinates + * @return nothing. The offset position is returned in objCoord + */ + private void getOffsetPos(Canvas3D canvas, RenderAtom ra, Point3d objCoord) { + computeWinCoord(canvas, ra, winCoord, objCoord); + + // Add the destination offset to the Raster position in window coordinates + winCoord.x -= xDstOffset; + winCoord.y -= yDstOffset; + + // Now back transform this offset pt. from window to object coordinates + computeObjCoord(canvas, winCoord, objCoord); + // pt. is now in object space again + } + + /** + * Clips the image against the window. This method simulates + * clipping the image by determining the subimage that will be + * drawn and adjusting the xOffset and yOffset accordingly. Only + * clipping against the left and top edges needs to be handled, + * clipping against the right and bottom edges will be handled by + * the underlying graphics library automatically. + */ + private void clipImage(Canvas3D canvas, RenderAtom ra, Point3d objCoord) { + // check if window coordinates have already been calculated by + // getOffsetPos(). + + if(xDstOffset == 0 && yDstOffset == 0) { + double x = objCoord.x; + double y = objCoord.y; + double z = objCoord.z; + computeWinCoord(canvas, ra, winCoord, objCoord); + + if ((winCoord.x > 0) && (winCoord.y > 0)) { + objCoord.x = x; + objCoord.y = y; + objCoord.z = z; + return; // no need to clip + } + } else { + if ((winCoord.x > 0) && (winCoord.y > 0)) { + return; + } + } + + + // Check if the Raster point will be culled + // Note that w use 1 instead of 0, because when hardware + // tranform the coordinate back to winCoord it may get + // a small negative value due to numerically inaccurancy. + // This clip the Raster away and cause flickering + // (see bug 4732965) + if(winCoord.x < 1) { + // Negate the window position and use this as the offset + xOffset = (int)-winCoord.x+1; + winCoord.x = 1; + } + + if(winCoord.y < 1) { + // Negate the window position and use this as the offset + yOffset = (int)-winCoord.y+1; + winCoord.y = 1; + } + + //check if user-specified subimage is smaller than the clipped image + if (xOffset < xSrcOffset) + xOffset = xSrcOffset; + if(yOffset < ySrcOffset) + yOffset = ySrcOffset; + // back transform to object coords + if(xDstOffset == 0 && yDstOffset == 0) + // Image plate to local Xform needs to be computed + computeObjCoord(canvas, winCoord, objCoord); + else { + // vwip should contain the Imageplate to Local transform + // (it was computed by computeObjCoord). + // We can simply use the previously computed value here + canvas.getPixelLocationInImagePlate(winCoord.x, winCoord.y, + objCoord.z, objCoord); + vwip.transform(objCoord); + } + + } + + private void computeObjCoord(Canvas3D canvas, Point2d winCoord, Point3d objCoord) { + // Back transform this pt. from window to object coordinates + // Assumes this method is ALWAYS called after computeWinCoord has been + // called. computeWinCoord calculates the Vworld to Image Plate Xform. + // This method simply uses it without recomputing it. + + canvas.getPixelLocationInImagePlate(winCoord.x, winCoord.y, objCoord.z, + objCoord); + // Get image plate to object coord transform + // inv(P x M) + vwip.invert(); + vwip.transform(objCoord); + } + + private void computeWinCoord(Canvas3D canvas, RenderAtom ra, + Point2d winCoord, Point3d objCoord) { + // Get local to Vworld transform + RenderMolecule rm = ra.renderMolecule; + + if (rm == null) { + // removeRenderAtom() may set ra.renderMolecule to null + // in RenderBin before this renderer thread run. + validVwip = false; + return; + } + // MT safe issue: We can't reference ra.renderMolecule below since + // RenderBin thread may set it to null anytime. Use rm instead. + + Transform3D lvw = rm.localToVworld[rm.localToVworldIndex[ + NodeRetained.LAST_LOCAL_TO_VWORLD]]; + + // Get Vworld to image plate Xform + canvas.getLastVworldToImagePlate(vwip); + + // v' = vwip x lvw x v + // where v' = transformed vertex, + // lvw = local to Vworld Xform + // vwip = Vworld to Image plate Xform + // v = vertex + + // Compute composite local to image plate Xform + vwip.mul(lvw); + + // Transform the Raster's position from object to world coordinates + vwip.transform(objCoord); + + // Get the window coordinates of this point + canvas.getPixelLocationFromImagePlate(objCoord, winCoord); + } + + int getClassType() { + return RASTER_TYPE; + } + + // notifies the Raster mirror object that the image data in a referenced + // ImageComponent object is changed. + // Currently we are not making use of this information. + + void notifyImageComponentImageChanged(ImageComponentRetained image, + ImageComponentUpdateInfo value) { + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + return false; + } + boolean intersect(Bounds targetBound) { + return false; + } + + boolean intersect(Point3d[] pnts) { + return false; + } + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained + geom) { + return false; + } + boolean intersect(Transform3D thisLocalToVworld, + Transform3D otherLocalToVworld, + GeometryRetained geom) { + return false; + } + + boolean intersect(Transform3D thisLocalToVworld, Bounds targetBound) { + return false; + } + void handleFrequencyChange(int bit) { + if (bit == Raster.ALLOW_IMAGE_WRITE) + setFrequencyChangeMask(bit, 0x1); + + } + +} diff --git a/src/classes/share/javax/media/j3d/RenderAtom.java b/src/classes/share/javax/media/j3d/RenderAtom.java new file mode 100644 index 0000000..ac78e5c --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderAtom.java @@ -0,0 +1,366 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.*; + +/** + * A RenderAtom is a wrapper for a GeometryAtom in a given RenderBin. + */ + +class RenderAtom extends Object implements ObjectUpdate { + /** + * The geometry atom of this render atom + */ + GeometryAtom geometryAtom = null; + + /** + * The RenderMolecule for this RenderAtom + */ + RenderMolecule renderMolecule = null; + + + /** + * The lights that influence this RenderAtom + */ + LightRetained[] lights = null; + + /** + * The fog that influences this RenderAtom + */ + FogRetained fog = null; + + /** + * The model clip that influences this RenderAtom + */ + ModelClipRetained modelClip = null; + + /** + * The appearance that influences this RenderAtom + */ + AppearanceRetained app = null; + + // + // Convert all boolean to a bitmask, saves memory since + // there are many RenderAtoms per view + // + + /** + * Indicates whether or not this object is in + * the render bin. + */ + static int IN_RENDERBIN = 0x1; + + // True if the above localeVwcBounds is a local copy rather + // than a reference to the bounds in shape + static int HAS_SEPARATE_LOCALE_VWC_BOUNDS = 0x2; + + // true if one of the geometries not going to a display list, hence + // need to maintain a local localeVwcBounds in order to avoid + // conflict with the vwcBounds in shape which is CURRENT + // while the checking of localeVwcBounds in rendering time + // should be LAST. In order words, localeVwcBounds contain + // the last vwcBounds, whereas, shape.vwcBounds contain the + // current vwcBounds + // + static int NEED_SEPARATE_LOCALE_VWC_BOUNDS = 0x4; + + static int ON_UPDATELIST = 0x8; + static int ON_LOCALE_VWC_BOUNDS_UPDATELIST = 0x10; + // true if comes from Oriented Shape3D + static int IS_ORIENTED = 0x20; + // true if in dirty oriented render atom list + static int IN_DIRTY_ORIENTED_RAs = 0x40; + + // true if in dirty depth sort position list + static int IN_SORTED_POS_DIRTY_TRANSP_LIST = 0x80; + + // A bitmask for all the bit specified above in this renderAtom + int dirtyMask = 0; + + /** + * Environment set that this renderAtom belongs to, used + * to compare the new env set with the old one when the + * scoping/bounds of a light/fog changes + */ + EnvironmentSet envSet; + + /** + * Used for non-text3d + */ + BoundingBox localeVwcBounds = null; + + + /** + * The last time this atom was reported visible + */ + long lastVisibleTime = -1; + + /** + * Next and Previous references for the list of RenderAtoms + * groupType is a mask set to true if this renderAtom is part of the displaylist array + * of the renderMolecule + * One per geometry in the geometryArr in the geometryAtom, since + * each geometry in the geometryAtom can end up in a different + * atomList(primary, secondary, seperatedlist) of the renderMoceule + */ + RenderAtomListInfo[] rListInfo; + + /** + * Used in depthSorted transparency, once per rInfo + */ + TransparentRenderingInfo[] parentTInfo = null; + + /** + * Used when depth sorted transparecy is turned on + * one dlist per rinfo + */ + int[] dlistIds = null; + + // One per geometry in the geometryArr in the geometryAtom + static int TEXT3D = 0x1; + static int DLIST = 0x2; + static int CG = 0x4; + static int OTHER = 0x8; + static int SEPARATE_DLIST_PER_GEO = 0x10; + static int VARRAY = 0x20; + static int SEPARATE_DLIST_PER_RINFO = 0x40; + static int PRIMARY = TEXT3D | DLIST | CG | OTHER|SEPARATE_DLIST_PER_RINFO; + + // Rendermolecule to which its currently being added + RenderMolecule added = null; + + // Rendermolecule from which its currently being removed + RenderMolecule removed = null; + + // non-null, if part of the add list(for the next frame) in the renderMolecule + RenderAtom nextAdd = null; + RenderAtom prevAdd = null; + + // non-null, if part of the remove list(for the next frame) in the renderMolecule + RenderAtom nextRemove = null; + RenderAtom prevRemove = null; + + RenderAtom() { + } + + /** + * This sets the inRenderBin flag + */ + synchronized void setRenderBin(boolean value) { + if (value == false) { + app = null; + dirtyMask &= ~IN_RENDERBIN; + dirtyMask &= ~ON_LOCALE_VWC_BOUNDS_UPDATELIST; + dirtyMask &= ~ON_UPDATELIST; + } + else { + dirtyMask |= IN_RENDERBIN; + } + + } + + /** + * This returns whether or not this atom goes into the opaque + * light bin + */ + boolean isOpaque() { + AppearanceRetained app = geometryAtom.source.appearance; + + if (app == null) { + return true; + } + + TransparencyAttributesRetained ta = app.transparencyAttributes; + + if (!VirtualUniverse.mc.isD3D()) { + // D3D doesn't support line/point antialiasing + switch (geometryAtom.geoType) { + case GeometryRetained.GEO_TYPE_POINT_SET: + case GeometryRetained.GEO_TYPE_INDEXED_POINT_SET: + if ((app.pointAttributes != null) && + app.pointAttributes.pointAntialiasing) { + return false; + } + break; + case GeometryRetained.GEO_TYPE_LINE_SET: + case GeometryRetained.GEO_TYPE_LINE_STRIP_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_STRIP_SET: + if ((app.lineAttributes != null) && + app.lineAttributes.lineAntialiasing) { + return false; + } + break; + case GeometryRetained.GEO_TYPE_RASTER: + case GeometryRetained.GEO_TYPE_COMPRESSED: + break; + default: + if (app.polygonAttributes != null) { + if ((app.polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_POINT) && + (app.pointAttributes != null) && + app.pointAttributes.pointAntialiasing) { + return false; + } else if ((app.polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_LINE) && + (app.lineAttributes != null) && + app.lineAttributes.lineAntialiasing) { + return false; + } + } + break; + } + + return ((ta == null) || + (ta.transparencyMode == + TransparencyAttributes.NONE) || + (ta.transparencyMode == + TransparencyAttributes.SCREEN_DOOR)); + } else { + return ((ta == null) || + (ta.transparencyMode == + TransparencyAttributes.NONE)); + } + } + + boolean inRenderBin() { + return ((dirtyMask & IN_RENDERBIN) != 0); + } + + boolean hasSeparateLocaleVwcBounds() { + return ((dirtyMask & HAS_SEPARATE_LOCALE_VWC_BOUNDS) != 0); + } + + boolean needSeparateLocaleVwcBounds() { + return ((dirtyMask & NEED_SEPARATE_LOCALE_VWC_BOUNDS) != 0); + } + + boolean onUpdateList() { + return ((dirtyMask & ON_UPDATELIST) != 0); + } + + boolean onLocaleVwcBoundsUpdateList() { + return ((dirtyMask & ON_LOCALE_VWC_BOUNDS_UPDATELIST) != 0); + } + + boolean isOriented() { + return ((dirtyMask & IS_ORIENTED) != 0); + } + + boolean inDepthSortList() { + return ((dirtyMask & IN_SORTED_POS_DIRTY_TRANSP_LIST) != 0); + } + + + boolean inDirtyOrientedRAs() { + return ((dirtyMask & IN_DIRTY_ORIENTED_RAs) != 0); + } + + public void updateObject() { + if (inRenderBin()) { + int lastLVWIndex = + renderMolecule.localToVworldIndex[NodeRetained.LAST_LOCAL_TO_VWORLD]; + + for (int i = 0; i < rListInfo.length; i++) { + if (rListInfo[i].geometry() == null) + continue; + + if (geometryAtom.source.inBackgroundGroup) { + if (rListInfo[i].infLocalToVworld == null) + rListInfo[i].infLocalToVworld = new Transform3D(); + + // to preserve the character transformation for Text3D atoms + renderMolecule.localToVworld[lastLVWIndex].getRotation( + rListInfo[i].infLocalToVworld); + rListInfo[i].infLocalToVworld.mul( + geometryAtom.lastLocalTransformArray[i]); + } else { + rListInfo[i].localToVworld.mul( + renderMolecule.localeLocalToVworld[lastLVWIndex], + geometryAtom.lastLocalTransformArray[i]); + } + } + } + dirtyMask &= ~ON_UPDATELIST; + } + + void updateOrientedTransform() { + int lastLVWIndex = + renderMolecule.localToVworldIndex[NodeRetained.LAST_LOCAL_TO_VWORLD]; + Transform3D orientedTransform = + ((OrientedShape3DRetained)geometryAtom.source). + getOrientedTransform(renderMolecule.renderBin.view.viewIndex); + for (int i = 0; i < rListInfo.length; i++) { + + if (geometryAtom.geoType == GeometryRetained.GEO_TYPE_TEXT3D && + geometryAtom.lastLocalTransformArray[i] != null) { + if (geometryAtom.source.inBackgroundGroup) { + if (rListInfo[i].infLocalToVworld == null) + rListInfo[i].infLocalToVworld = new Transform3D(); + + rListInfo[i].infLocalToVworld.mul( + renderMolecule.infLocalToVworld[lastLVWIndex], + orientedTransform); + rListInfo[i].infLocalToVworld.mul( + geometryAtom.lastLocalTransformArray[i]); + } else { + rListInfo[i].localToVworld.mul( + renderMolecule.localeLocalToVworld[lastLVWIndex], + orientedTransform); + rListInfo[i].localToVworld.mul( + geometryAtom.lastLocalTransformArray[i]); + } + } else { + if (geometryAtom.source.inBackgroundGroup) { + if (rListInfo[i].infLocalToVworld == null) + rListInfo[i].infLocalToVworld = new Transform3D(); + + rListInfo[i].infLocalToVworld.mul( + renderMolecule.infLocalToVworld[lastLVWIndex], + orientedTransform); + } else { + rListInfo[i].localToVworld.mul( + renderMolecule.localeLocalToVworld[lastLVWIndex], + orientedTransform); + } + } + } + } + + // updateLocaleVwcBounds is called from RenderBin.updateObject() + // to update the local copy of the localeVwcBounds + + void updateLocaleVwcBounds() { + + // it is possible that inRenderBin could be false because + // the renderAtom could have been removed from RenderBin + // in the same frame, and removeRenderAtoms does happen + // before updateLocaleVwcBounds + if (inRenderBin()) { + // Check if the locale of this is different from the + // locale on which the view is,then compute the translated + // localeVwcBounds + if (renderMolecule.renderBin.locale != geometryAtom.source.locale) { + + geometryAtom.source.locale. + hiRes.difference(renderMolecule.renderBin.locale.hiRes, + renderMolecule.renderBin.localeTranslation); + localeVwcBounds.translate(geometryAtom.source.vwcBounds, + renderMolecule.renderBin.localeTranslation); + } else { + localeVwcBounds.set(geometryAtom.source.vwcBounds); + } + dirtyMask &= ~ON_LOCALE_VWC_BOUNDS_UPDATELIST; + } + } +} diff --git a/src/classes/share/javax/media/j3d/RenderAtomListInfo.java b/src/classes/share/javax/media/j3d/RenderAtomListInfo.java new file mode 100644 index 0000000..0509419 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderAtomListInfo.java @@ -0,0 +1,43 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +/** + * Information per geometry in the renderAtom, there are several + * of these per RenderAtom, one per geometry in GeometryAtom + */ +class RenderAtomListInfo extends Object { + // RenderAtom that its a part of + + RenderAtom renderAtom = null; + + // Specific geometry index in the GeometryAtom geometryArr list that + // corresponds to this RenderAtomListInfo + int index; + + // Prev and next pointer + RenderAtomListInfo next = null; + RenderAtomListInfo prev = null; + + // Which bucket in the renderMolecule that it falls info + int groupType = 0; + + // Used only for Text3D + // background geometry rendering + Transform3D infLocalToVworld = null; + Transform3D localToVworld = null; + + + GeometryRetained geometry() { + return renderAtom.geometryAtom.geometryArray[index]; + } +} diff --git a/src/classes/share/javax/media/j3d/RenderBin.java b/src/classes/share/javax/media/j3d/RenderBin.java new file mode 100644 index 0000000..089f887 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderBin.java @@ -0,0 +1,6891 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.*; + +/** + * The RenderBin is a structure that optimizes rendering by doing efficient + * state sorting of objects to be rendered. + */ + +class RenderBin extends J3dStructure implements ObjectUpdate { + + /** + * The list of RenderAtoms + */ + ArrayList renderAtoms = new ArrayList(5); + + /** + * A couple ArrayLists used during light Processing + */ + ArrayList lightMessageList = new ArrayList(5); + + ArrayList bhTreesArrList = new ArrayList(5); + + // Messges retrieved when a message is sent to RenderingEnv Structure + J3dMessage[] m; + + /** + * List of renderMolecules that are soleUser + * have to do a 2 pass, first update values + * then sort based on equivalent material + */ + ArrayList rmUpdateList = new ArrayList(); + ArrayList aBinUpdateList = new ArrayList(); + + /** + * List of TextureBin that are soleUser that + * needs to have its components updated @updateObject time + */ + ArrayList tbUpdateList = new ArrayList(); + + /** + * List of Bins that are soleUser that have new renderAtom + * added into, which requires a pre-update screening to + * check if any of its node component changes could have been + * missed because the changes happen when all the render atoms + * are temporarily removed from the bin. + */ + ArrayList updateCheckList = new ArrayList(); + + /** + * The number of lights supported by the underlying context. + */ + int maxLights; + + /** + * The opaque objects + */ + LightBin opaqueBin = null; + + /** + * OpaqueBins to be added for the next frame + */ + LightBin addOpaqueBin = null; + + // This is a list of textureBins to be rendered, if the transpSortPolicy + // is NONE, otherwise, if the transpSortPolicy is geometry, then + // this is the list of renderAtoms to be rendered + ArrayList allTransparentObjects = new ArrayList(5); + + TransparentRenderingInfo transparentInfo; + + /** + * List of RenderAtoms whose postion have changed - only used for + * depth sorted transparency + */ + ArrayList positionDirtyList = new ArrayList(5); + + + /** + * A set of freelists for RenderBin type objects + */ + ArrayList lightBinFreelist = new ArrayList(5); + ArrayList envSetFreelist = new ArrayList(5); + ArrayList attrBinFreelist = new ArrayList(5); + ArrayList textureBinFreelist = new ArrayList(5); + ArrayList renderMoleculeFreelist = new ArrayList(5); + ArrayList transparentInfoFreeList = new ArrayList(5); + + /** + * Used when ColoringAttributes is null + */ + Color3f white = new Color3f(1.0f, 1.0f, 1.0f); + + /** + * Used when Background is null + */ + Color3f black = new Color3f(0.0f, 0.0f, 0.0f); + + /** + * The backgound color data. + */ + BackgroundRetained background = new BackgroundRetained(); + + /** + * The view platform transforms. + */ + // used for rendering - lights and fog modelling + Transform3D vworldToVpc = new Transform3D(); + + // used for updating vpSchedSphere + Transform3D vpcToVworld = new Transform3D(); + + /** + * Two bounding spheres to track the scheduling region of + * the view platform. + */ + BoundingSphere vpSchedSphereInVworld = new BoundingSphere(); + + /** + * To cache the view frustum bounding box. + */ + BoundingBox viewFrustumBBox = new BoundingBox(); + BoundingBox canvasFrustumBBox = new BoundingBox(); + + /** + * To ensure that vpcToVworld is valid (not null) for the first pass + */ + boolean afterFirst = false; + + /** + * back clip distance in vworld + */ + double backClipDistanceInVworld; + + boolean backClipActive = false; + + /** + * These variables control when compaction occurs + */ + int frameCount = 0; + int frameCountCutoff = 150; + int notVisibleCount = 75; + long removeCutoffTime = -1; + + /** + * variables to process transform messages + */ + boolean transformMsg = false; + UpdateTargets targets = null; + ArrayList blUsers = null; + + /** + * The View for this render bin + */ + View view = null; + + ArrayList toBeAddedTextureResourceFreeList = new ArrayList(5); + ArrayList displayListResourceFreeList = new ArrayList(5); + boolean resourceToFree = false; + + // a list of top level OrderedGroups + ArrayList orderedBins = new ArrayList(5); + + // List of changed elements in the environment that needs to + // be reloaded + ArrayList changedLts = new ArrayList(5); + ArrayList changedFogs = new ArrayList(5); + ArrayList changedModelClips = new ArrayList(5); + + // Flag to indicate whether the canvas should be marked + static int REEVALUATE_LIGHTS = 0x1; + static int REEVALUATE_FOG = 0x2; + static int REEVALUATE_MCLIP = 0x4; + static int REEVALUATE_ALL_ENV = REEVALUATE_LIGHTS | REEVALUATE_FOG | REEVALUATE_MCLIP; + int envDirty = 0; + + + boolean reEvaluateBg = true; + + boolean reEvaluateClip = true; + + boolean reEvaluateSortMode = false; + + + // list of renderMolecule + // RenderBin will not reused in two different universe, so it is + // safe to pass null in last parameters in new IndexedUnorderSet() + IndexedUnorderSet renderMoleculeList = + new IndexedUnorderSet(RenderMolecule.class, + RenderMolecule.RENDER_MOLECULE_LIST, null); + + // List of renderAtoms that have a shared dlist (due to geo.refCount > 1) + UnorderList sharedDList = new UnorderList(5, RenderAtomListInfo.class); + + ArrayList dirtyRenderMoleculeList = new ArrayList(5); + + + /** + * ArrayList of objects to be updated + */ + ArrayList objUpdateList = new ArrayList(5); + + ArrayList raLocaleVwcBoundsUpdateList = new ArrayList(5); + + /** + * remove the bins first before adding them to new ones + */ + IndexedUnorderSet removeRenderAtomInRMList = + new IndexedUnorderSet(RenderMolecule.class, + RenderMolecule.REMOVE_RENDER_ATOM_IN_RM_LIST, null); + + + /** + * list of affect OrderedGroups with childIndexOrder changed. + */ + ArrayList ogCIOList = new ArrayList(5); + + /** + * list of ordered bins from which orderedCollection are added/removed + */ + ArrayList obList = new ArrayList(5); + + /** + * Ordered Bin processing + */ + ArrayList orderedBinsList = new ArrayList(5); + ArrayList toBeAddedBinList = new ArrayList(5); + + /** + * arraylist of geometry that should be locked to ensure + * that the same snapshot of the geometry is rendered + * across all canvases + */ + ArrayList lockGeometryList = new ArrayList(5); + + + /** + * arraylist of dlist that will be rebuilt + */ + ArrayList dlistLockList = new ArrayList(5); + + // Background node that contains geometry + BackgroundRetained geometryBackground = null; + + // background geometry processing + LightBin bgOpaqueBin = null; + LightBin bgAddOpaqueBin = null; + ArrayList bgOrderedBins = new ArrayList(5); + TransparentRenderingInfo bgTransparentInfo; + + + // vworldToVpc for background geometry + Transform3D infVworldToVpc = new Transform3D(); + + // true if vpcToVworld has been modified + boolean vpcToVworldDirty = true; + + // current active background + BackgroundRetained currentActiveBackground = new BackgroundRetained(); + + // Flag to indicate that alternate app is dirty + boolean altAppearanceDirty = true; + + + // List of node components that need special processing, due to + // extensions + ArrayList nodeComponentList = new ArrayList(5); + + + // List of node components ***for this frame*** that need special + // processing due to extension + ArrayList newNodeComponentList = new ArrayList(5); + ArrayList removeNodeComponentList = new ArrayList(5); + ArrayList dirtyNodeComponentList = new ArrayList(5); + + ArrayList textureBinList = new ArrayList(5); + + /** + * arraylist of refernce geometry that should be locked when transparency + * is on, so that we can make a mirror copy of the colors safely + */ + ArrayList dirtyReferenceGeomList = new ArrayList(5); + + + /** + * used by geometry execute routines to determine if the + * alpha values can be zapped + */ + boolean multiScreen = false; + + // list of all Oriented RenderAtoms + ArrayList orientedRAs = new ArrayList(5); + + // list of Oriented RenderAtoms whose orientedTransforms require update + ArrayList dirtyOrientedRAs = new ArrayList(5); + + // Cached copy of dirty oriented RAs to be updated in MasterControl + ArrayList cachedDirtyOrientedRAs = null; + + // list of offScreen message that + ArrayList offScreenMessage = new ArrayList(5); + + // Vector used for locale translation + Vector3d localeTranslation = new Vector3d(); + + // Separate dlists that were added/removed in this snapshot + UnorderList addDlist = new UnorderList(5, RenderAtomListInfo.class); + UnorderList removeDlist = new UnorderList(5, RenderAtomListInfo.class); + + // Separate dlists per rinfo that were added/removed in this snapshot + ArrayList addDlistPerRinfo = new ArrayList(5); + ArrayList removeDlistPerRinfo = new ArrayList(5); + + Locale locale = null; + + // Set to true if locale changes as part of UPDATE_VIEW message + boolean localeChanged = false; + + + // Cached copy to be used by all RenderMolecules + DisplayListRenderMethod dlistRenderMethod = null; + + // Need to query BHTree again with visibility policy change + boolean reactivateView = false; + + /** + * A flag indicates that the cached visible GeometryAtoms for this RenderBin might + * be invalid. + */ + private boolean visGAIsDirty = false; + + /** + * A flag indicates that a visibility query to the GeometryStructure is needed. + */ + private boolean visQuery = false; + + // Temporary dirtylist + ArrayList dirtyList = new ArrayList(5); + + // Transaprency sort mode + int transpSortMode = View.TRANSPARENCY_SORT_NONE; + int cachedTranspSortMode = View.TRANSPARENCY_SORT_NONE; + + + // Temporary dirtylist + ArrayList dirtyDepthSortRenderAtom = new ArrayList(5); + int numDirtyTinfo = 0; + + // Eye position in vworld + Point3d eyeInVworld = new Point3d(); + // Number of RenderAtomListInfo in the depthSortedList + int nElements = 0; + + + + /** + * Constructs a new RenderBin + */ + RenderBin(VirtualUniverse u, View v) { + super(u, J3dThread.UPDATE_RENDER); + vworldToVpc.setIdentity(); + universe = u; + view = v; + transpSortMode = v.transparencySortingPolicy; + cachedTranspSortMode = v.transparencySortingPolicy; + maxLights = VirtualUniverse.mc.maxLights; + ViewPlatform vp = view.getViewPlatform(); + if (vp != null) { + locale = ((ViewPlatformRetained) (vp.retained)).locale; + } + dlistRenderMethod = (DisplayListRenderMethod) + VirtualUniverse.mc.getDisplayListRenderMethod(); + } + + /** + * updateObject + */ + public void updateObject() { + int i, j, k; + RenderMolecule rm; + RenderAtomListInfo ra; + LightBin tmp; + float radius; + BackgroundRetained bg; + ObjectUpdate ob; + OrderedBin orderBin; + TextureRetained tex; + Integer texIdObj; + int size; + DetailTextureImage dtex; + + // System.out.println("dirtyRenderMoleculeList.size = "+dirtyRenderMoleculeList.size()); + // System.out.println("reEvaluateBg = "+reEvaluateBg); + // System.out.println("reEvaluateClip = "+reEvaluateClip); + // System.out.println("<========+End All Cached Values===========>"); + // Add the new lightBins that have been created + // System.out.println("objUpdateList.size = "+objUpdateList.size()); + // System.out.println("addOpaqueBin = "+addOpaqueBin); + // System.out.println("opaqueBin = "+opaqueBin); + + // List of renderMolecule from which renderAtoms have been removed + size = removeRenderAtomInRMList.size(); + if (size > 0) { + RenderMolecule[] rmArr = (RenderMolecule[]) + removeRenderAtomInRMList.toArray(false); + for (i=0 ; i<size; i++) { + rmArr[i].updateRemoveRenderAtoms(); + } + } + + // Add any OGs that need to be added to this frame + // List of Ordered Groups that have been removed + + size = obList.size(); + if ( size > 0) { + for (i = 0 ; i < size; i++) { + orderBin = (OrderedBin)obList.get(i); + orderBin.addRemoveOrderedCollection(); + } + } + + size = ogCIOList.size(); + if(size > 0) { + J3dMessage m; + for(i=0; i<size; i++) { + m = (J3dMessage) ogCIOList.get(i); + + switch(m.type) { + case J3dMessage.ORDERED_GROUP_TABLE_CHANGED: + OrderedGroupRetained og = (OrderedGroupRetained)m.args[3]; + if(og != null) { + og.childIndexOrder = ((int[])m.args[4]); + } + break; + + + case J3dMessage.ORDERED_GROUP_INSERTED: + case J3dMessage.ORDERED_GROUP_REMOVED: + if(m.args[3] != null) { + Object[] ogArr = (Object[]) m.args[3]; + Object[] ogTableArr = (Object[]) m.args[4]; + for(j=0; j<ogArr.length; j++) { + if(ogArr[j] != null) { + ((OrderedGroupRetained)ogArr[j]).childIndexOrder = + ((int[])ogTableArr[j]); + } + } + } + + break; + } + m.decRefcount(); + } + } + + + if (addOpaqueBin != null) { + + if (opaqueBin != null) { + tmp = opaqueBin; + while (tmp.next != null) { + tmp = tmp.next; + } + addOpaqueBin.prev = tmp; + tmp.next = addOpaqueBin; + } + else { + opaqueBin = addOpaqueBin; + } + } + + if (bgAddOpaqueBin != null) { + if (bgOpaqueBin != null) { + tmp = bgOpaqueBin; + while (tmp.next != null) { + tmp = tmp.next; + } + bgAddOpaqueBin.prev = tmp; + tmp.next = bgAddOpaqueBin; + } + else { + bgOpaqueBin = bgAddOpaqueBin; + } + } + + size = orderedBinsList.size(); + if (size > 0 ) { + + for (i = 0; i < size; i++) { + ArrayList obs= (ArrayList) orderedBinsList.get(i); + ArrayList list = (ArrayList) toBeAddedBinList.get(i); + + int lSize = list.size(); + for (j = 0; j < lSize; j++) { + obs.add(list.get(j)); + } + } + } + + + size = raLocaleVwcBoundsUpdateList.size(); + if ( size > 0) { + RenderAtom renderAtom; + for (i = 0; i < size; i++) { + renderAtom = (RenderAtom)raLocaleVwcBoundsUpdateList.get(i); + renderAtom.updateLocaleVwcBounds(); + } + } + + if ((size = aBinUpdateList.size()) > 0) { + for (i = 0; i < size; i++) { + AttributeBin abin = (AttributeBin)aBinUpdateList.get(i); + abin.updateNodeComponent(); + + } + } + + + // Update the sole user TextureBins. + if (tbUpdateList.size() > 0) { + TextureBin tb; + size = tbUpdateList.size(); + for (i = 0; i < size; i++) { + tb = (TextureBin) tbUpdateList.get(i); + tb.updateNodeComponent(); + } + + // do another pass to re-sort TextureBin based on the + // texture in the first texture unit state + for (i = 0; i < size; i++) { + tb = (TextureBin) tbUpdateList.get(i); + // Bug Id : 4701430 - Have to be sure tb.attributeBin is + // not equal to null. This is a temporary fix for j3d1.3. + if (((tb.tbFlag & TextureBin.RESORT) != 0) && + (tb.attributeBin != null)) { + + tb.attributeBin.reInsertTextureBin(tb); + tb.tbFlag &= ~TextureBin.RESORT; + } + } + } + + + // Update the soleUser node components first + // This way material equivalence during insertion + // of new RMs is based on the updated ones + if ((size = rmUpdateList.size()) > 0) { + for (i = 0; i < size; i++) { + rm = (RenderMolecule)rmUpdateList.get(i); + + boolean changeLists = rm.updateNodeComponent(); + // If an existing rm went from opaque to transparent or vice-versa + // and has not been removed, then switch the RM + if (changeLists && rm.textureBin != null) { + rm.textureBin.changeLists(rm); + } + } + for (i = 0; i < size; i++) { + rm = (RenderMolecule)rmUpdateList.get(i); + rm.reEvaluateEquivalence(); + } + } + + + + size = objUpdateList.size(); + if ( size > 0) { + for (i = 0; i < size; i++) { + ob = (ObjectUpdate)objUpdateList.get(i); + ob.updateObject(); + } + } + + size = dirtyReferenceGeomList.size(); + if ( size > 0) { + GeometryArrayRetained geo; + Canvas3D canvases[] = view.getCanvases(); + + for (i = 0; i < size; i++) { + geo = (GeometryArrayRetained) dirtyReferenceGeomList.get(i); + // Evaluate the nodeComponentList for all the canvases + geo.geomLock.getLock(); + j = 0; + // Do the setup only once{if necessary} for each geometry + boolean found = false; + while(j < canvases.length && !found) { + if ((canvases[j].extensionsSupported & Canvas3D.SUN_GLOBAL_ALPHA) == 0) { + if ((geo.vertexFormat & GeometryArray.INTERLEAVED) != 0) { + geo.setupMirrorInterleavedColorPointer(true); + found = true; + } + else { + geo.setupMirrorColorPointer((geo.vertexType & GeometryArrayRetained.COLOR_DEFINED),true); + found = true; + } + } + j++; + } + geo.geomLock.unLock(); + + } + + } + + if (reEvaluateBg) { + setBackground(currentActiveBackground); + } + + size = textureBinList.size(); +//System.out.println("textureBinList.size= " + size); + if (size > 0) { + Canvas3D canvasList[][] = view.getCanvasList(false); + Canvas3D cv; + boolean useSharedCtx = false; + TextureRetained texture; + + // do a quick check to see if there is any canvas using + // shared context + for (j = 0; j < canvasList.length && !useSharedCtx; j++) { + cv = canvasList[j][0]; + if (cv.useSharedCtx) { + useSharedCtx = true; + } + } + + for (int m = 0; m <size; m++) { + k = 0; + TextureBin tb = (TextureBin) textureBinList.get(m); + tb.tbFlag |= TextureBin.ON_RENDER_BIN_LIST; + + if (tb.texUnitState == null) + continue; + + for (i = 0; i < tb.texUnitState.length; i++) { + if (tb.texUnitState[i] != null && + tb.texUnitState[i].texture != null) { + + texture = tb.texUnitState[i].texture; + + // for all the textures in this texture bin list that + // need to be reloaded, add the textures to the + // corresponding resource reload list if the + // resource uses shared context, + // so that the texture can be reloaded up front, and + // we don't need to do make context current for + // each texture reload. Make Context current isn't + // cheap. + + if (useSharedCtx) { + synchronized (texture.resourceLock) { + for (j = 0; j < canvasList.length; j++) { + cv = canvasList[j][0]; + if (cv.useSharedCtx && + cv.screen.renderer != null && + ((cv.screen.renderer.rendererBit & + (texture.resourceCreationMask | + texture.resourceInReloadList)) == 0)) { + + cv.screen.renderer.textureReloadList.add( + texture); + + texture.resourceInReloadList |= + cv.screen.renderer.rendererBit; + } + } + } + } + } + } + } + } + + size = newNodeComponentList.size(); + if ( size > 0) { +//System.out.println("newNodeComponentlist.size= " + size); + Canvas3D canvases[] = view.getCanvases(); + for (i = 0; i < size; i++) { + // Evaluate the nodeComponentList for all the canvases + ImageComponentRetained nc = (ImageComponentRetained)newNodeComponentList.get(i); + // Only evalaute extension for by-ref Images + if (nc.isByReference()) { + nc.geomLock.getLock(); + for (j = 0; j <canvases.length; j++) { + // If the context is null, then the extension + // will be evaluated during context creation in + // the renderer + if (canvases[j].ctx != 0) { + nc.evaluateExtensions(canvases[j].extensionsSupported); + } + } + nc.geomLock.unLock(); + } + nodeComponentList.add(nc); + } + } + + size = removeNodeComponentList.size(); + if ( size > 0) { + for (i = 0; i < size; i++) { + nodeComponentList.remove(removeNodeComponentList.get(i)); + } + } + + + // reevaluate dirty node component + size = dirtyNodeComponentList.size(); + if (size > 0) { + Canvas3D canvases[] = view.getCanvases(); + for (i = 0; i < size; i++) { + // Evaluate the nodeComponentList for all the canvases + ImageComponentRetained nc = + (ImageComponentRetained)dirtyNodeComponentList.get(i); + // Only evalaute extension for by-ref Images + if (nc.isByReference()) { + nc.geomLock.getLock(); + for (j = 0; j <canvases.length; j++) { + // If the context is null, then the extension + // will be evaluated during context creation in + // the renderer + if (canvases[j].ctx != 0) { + nc.evaluateExtensions( + canvases[j].extensionsSupported); + } + } + nc.geomLock.unLock(); + } + } + } + + + if (reEvaluateClip) { + double[] retVal = null; + if ((retVal = universe.renderingEnvironmentStructure.backClipDistanceInVworld(vpSchedSphereInVworld, view)) != null) { + backClipDistanceInVworld = retVal[0]; + backClipActive = true; + } + else { + backClipActive = false; + } + view.vDirtyMask |= View.CLIP_DIRTY; + } + + multiScreen = ((view.getScreens()).length > 1); + + // renderBin is ready now, so send the offScreen message + size = offScreenMessage.size(); + if ( size > 0) { + J3dMessage m; + for (i=size-1; i>=0; i--) { + m = (J3dMessage) offScreenMessage.get(i); + m.threads = J3dThread.RENDER_THREAD; + ((Canvas3D)m.args[0]).screen.renderer.rendererStructure.addMessage(m); + + // the above call will increment the reference count again + m.decRefcount(); + } + } + + // called from renderBin when there are dirtyOrientedRAs + // This routin cache the dirtyOrintedRAs to be updated + // by mastercontrol + if (dirtyOrientedRAs.size() > 0) { + // Keep a copy to be handled by mastercontrol + cachedDirtyOrientedRAs = (ArrayList)dirtyOrientedRAs.clone(); + } + boolean sortAll = false; + if (reEvaluateSortMode && transpSortMode != cachedTranspSortMode) { + convertTransparentRenderingStruct(transpSortMode, cachedTranspSortMode); + transpSortMode = cachedTranspSortMode; + if (transpSortMode == View.TRANSPARENCY_SORT_GEOMETRY) { + if (transparentInfo != null){ + sortAll = true; + } + } + } + + if (vpcToVworldDirty) { + vworldToVpc.invert(vpcToVworld); + // Have the send down the lights, so set the canvas + // lightbin to null + Canvas3D canvases[] = view.getCanvases(); + for (i = 0; i < canvases.length; i++) { + canvases[i].lightBin = null; + } + if (canvases.length > 0) { + Transform3D xform; + canvases[0].getCenterEyeInImagePlate(eyeInVworld); + // xform is imagePlateToLocal + xform = canvases[0].canvasViewCache.getImagePlateToVworld(); + xform.transform(eyeInVworld); + } + if (transpSortMode == View.TRANSPARENCY_SORT_GEOMETRY && transparentInfo != null) { + // System.out.println("sortAll 1"); + sortAll = true; + } + } + size = dirtyDepthSortRenderAtom.size(); + + + if (sortAll || size > 0) { + int tsize = allTransparentObjects.size(); + + double zVal; + for (i = 0; i < tsize; i++) { + RenderAtom renderAtom = (RenderAtom)allTransparentObjects.get(i); + for (k = 0; k < renderAtom.rListInfo.length; k++) { + if (renderAtom.rListInfo[k].geometry() == null) + continue; + zVal = renderAtom.geometryAtom.centroid[k].distanceSquared(eyeInVworld); + renderAtom.parentTInfo[k].zVal = zVal; + + } + } + + // Check to see if a majority of the transparent Objects have changed + // If less than 66% of all transparentStructs are dirty + // then, remove and insert, otherwise resort everything + + + if (size > 0 && 1.5f * numDirtyTinfo > nElements) { + // System.out.println("sortAll 3, size = "+size); + sortAll = true; + } + + if (size > 0) { + TransparentRenderingInfo dirtyList = null, rList; + for (i = 0; i < size; i++) { + RenderAtom renderAtom = (RenderAtom)dirtyDepthSortRenderAtom.get(i); + if (!renderAtom.inRenderBin()) + continue; + renderAtom.dirtyMask &= ~RenderAtom.IN_SORTED_POS_DIRTY_TRANSP_LIST; + if (!sortAll) { + dirtyList = collectDirtyTRInfo(dirtyList, renderAtom); + } + } + + if (dirtyList != null) { + // System.out.println("====> sort Some"); + dirtyList = depthSortAll(dirtyList); + // Now merge the newly sorted list with the old one + transparentInfo = mergeDepthSort(transparentInfo, dirtyList); + } + } + // Sort all the transparent renderAtoms + if (sortAll) { + transparentInfo = depthSortAll(transparentInfo); + } + } + + if (addDlist.size() > 0 || removeDlist.size() > 0) { + Canvas3D canvasList[][] = view.getCanvasList(false); + Canvas3D cv; + ArrayList rlist = new ArrayList(5); + + for (i = 0; i < canvasList.length; i++) { + cv = canvasList[i][0]; + if (cv.useSharedCtx) { + // Do this only once per renderer for this view + if (!rlist.contains(cv.screen.renderer)) { + rlist.add(cv.screen.renderer); + updateDlistRendererResource(cv.screen.renderer); + } + } else { + updateDlistCanvasResource(canvasList[i]); + } + } + + } + + + + if (dirtyRenderMoleculeList.size() > 0 || + addDlistPerRinfo.size() > 0 || + removeDlistPerRinfo.size() > 0 || + displayListResourceFreeList.size() > 0 || + toBeAddedTextureResourceFreeList.size() > 0 ) { + + Canvas3D canvasList[][] = view.getCanvasList(false); + Canvas3D cv; + + for (i = 0; i < canvasList.length; i++) { + cv = canvasList[i][0]; + if (cv.useSharedCtx && (cv.screen.renderer != null)) { + updateRendererResource(cv.screen.renderer); + } else { + updateCanvasResource(canvasList[i]); + } + } + + Integer id; + size = displayListResourceFreeList.size(); + for (i = 0; i < size; i++) { + id = (Integer)displayListResourceFreeList.get(i); + VirtualUniverse.mc.freeDisplayListId(id); + } + + // lock list of dlist + // TODO: Instead of copying could we keep 2 arrays + // and just toggle? + size = dirtyRenderMoleculeList.size(); + for (i = 0; i < size; i++) { + rm = (RenderMolecule)dirtyRenderMoleculeList.get(i); + rm.onUpdateList = 0; + ra = rm.primaryRenderAtomList; + while (ra != null) { + dlistLockList.add(ra.geometry()); + ra = ra.next; + } + } + size = addDlistPerRinfo.size(); + for (i = 0; i < size; i++) { + ra = (RenderAtomListInfo)addDlistPerRinfo.get(i); + if (ra.geometry() != null) { + dlistLockList.add(ra.geometry()); + } + } + + } + + clearAllUpdateObjectState(); + /* + if (opaqueBin != null) { + System.out.println(this + "***** Begin Dumping OpaqueBin *****"); + dumpBin(opaqueBin); + System.out.println("***** End Dumping OpaqueBin *****"); + } + */ + + } + + + void updateDlistRendererResource(Renderer rdr) { + int i, value = 0; + int size = 0; + RenderAtomListInfo arr[]; + RenderAtomListInfo ra; + + if (rdr == null) { + return; + } + + if ((size = addDlist.size()) > 0) { + arr = (RenderAtomListInfo []) addDlist.toArray(false); + for (i = 0; i < size; i++) { + ra = arr[i]; + GeometryArrayRetained geo = (GeometryArrayRetained)ra.geometry(); + + // First time thru this renderer or the context that + // it is built for no longer matches the context + // used in the renderer, create a dlist + sharedDList.add(ra); + geo.incrDlistRefCount(rdr.rendererBit); + + if (((geo.resourceCreationMask & rdr.rendererBit) == 0) || + (geo.getDlistTimeStamp(rdr.rendererBit) != + rdr.sharedCtxTimeStamp)) { + geo.resourceCreationMask |= rdr.rendererBit; + dirtyList.add(ra); + } + } + } + + if ((size = removeDlist.size()) > 0) { + arr = (RenderAtomListInfo []) removeDlist.toArray(false); + for (i = 0; i < size; i++) { + ra = arr[i]; + sharedDList.remove(ra); + + GeometryArrayRetained geo = (GeometryArrayRetained)ra.geometry(); + value = geo.decrDlistRefCount(rdr.rendererBit); + // System.out.println("========> geo.refcount = "+geo.refCount); + // add this geometry's dlist to be freed + if (value == 0) { + rdr.displayListResourceFreeList.add(geo.dlistObj); + geo.resourceCreationMask &= ~rdr.rendererBit; + // All Dlist on all renderer have been freed, then return dlistID + if (geo.resourceCreationMask == 0) { + geo.freeDlistId(); + } + } + } + } + if ((size = dirtyList.size()) > 0) { + for (i = 0; i < size; i++) { + ra = (RenderAtomListInfo)dirtyList.get(i); + GeometryArrayRetained geo = (GeometryArrayRetained)ra.geometry(); + if ( (geo.resourceCreationMask & rdr.rendererBit) != 0) { + rdr.dirtyRenderAtomList.add(ra); + } + } + rdr.dirtyDisplayList = true; + dirtyList.clear(); + } + } + + void updateDlistCanvasResource(Canvas3D[] canvases) { + int i, j, value; + Canvas3D cv; + int size = 0; + RenderAtomListInfo arr[]; + RenderAtomListInfo ra; + + // Add the newly added dlist to the sharedList + if ((size = addDlist.size()) > 0) { + arr = (RenderAtomListInfo []) addDlist.toArray(false); + for (i = 0; i <size; i++) { + sharedDList.add(arr[i]); + } + } + + // Remove the newly removed dlist from the sharedList + if ((size = removeDlist.size()) > 0) { + arr = (RenderAtomListInfo []) removeDlist.toArray(false); + for (i = 0; i < size; i++) { + sharedDList.remove(arr[i]); + } + } + + // add to the dirty list per canvas + for (j = 0; j < canvases.length; j++) { + cv = canvases[j]; + + if ((size = addDlist.size()) > 0) { + arr = (RenderAtomListInfo []) addDlist.toArray(false); + for (i = 0; i <size; i++) { + ra = arr[i]; + GeometryArrayRetained geo = (GeometryArrayRetained) ra.geometry(); + + geo.incrDlistRefCount(cv.canvasBit); + if ((cv.ctx != 0) && + ((geo.resourceCreationMask & cv.canvasBit) == 0) || + (geo.getDlistTimeStamp(cv.canvasBit) != + cv.ctxTimeStamp)) { + geo.resourceCreationMask |= cv.canvasBit; + dirtyList.add(ra); + } + } + } + if ((size = removeDlist.size()) > 0) { + arr = (RenderAtomListInfo []) removeDlist.toArray(false); + for (i = 0; i < size; i++) { + GeometryArrayRetained geo = + (GeometryArrayRetained) arr[i].geometry(); + + value = geo.decrDlistRefCount(canvases[j].canvasBit); + // add this geometry's dlist to be freed + if (value == 0) { + if (cv.ctx != 0) { + canvases[j].displayListResourceFreeList.add(geo.dlistObj); + } + geo.resourceCreationMask &= ~canvases[j].canvasBit; + // All Dlist on all canvases have been freed, then return dlistID + if (geo.resourceCreationMask == 0) + geo.freeDlistId(); + } + } + } + if ((size = dirtyList.size()) > 0) { + for (i = 0; i <size; i++) { + ra = (RenderAtomListInfo)dirtyList.get(i); + GeometryArrayRetained geo = (GeometryArrayRetained)ra.geometry(); + if ((geo.resourceCreationMask & cv.canvasBit) != 0) { + cv.dirtyRenderAtomList.add(ra); + } + } + cv.dirtyDisplayList = true; + dirtyList.clear(); + } + } + + + } + + void clearAllUpdateObjectState() { + localeChanged = false; + obList.clear(); + rmUpdateList.clear(); + ogCIOList.clear(); + aBinUpdateList.clear(); + tbUpdateList.clear(); + removeRenderAtomInRMList.clear(); + addOpaqueBin = null; + bgAddOpaqueBin = null; + orderedBinsList.clear(); + toBeAddedBinList.clear(); + objUpdateList.clear(); + raLocaleVwcBoundsUpdateList.clear(); + displayListResourceFreeList.clear(); + toBeAddedTextureResourceFreeList.clear(); + dirtyRenderMoleculeList.clear(); + dirtyReferenceGeomList.clear(); + reEvaluateBg = false; + textureBinList.clear(); + newNodeComponentList.clear(); + removeNodeComponentList.clear(); + dirtyNodeComponentList.clear(); + reEvaluateClip = false; + vpcToVworldDirty = false; + offScreenMessage.clear(); + addDlist.clear(); + removeDlist.clear(); + addDlistPerRinfo.clear(); + removeDlistPerRinfo.clear(); + clearDirtyOrientedRAs(); + reEvaluateSortMode = false; + dirtyDepthSortRenderAtom.clear(); + numDirtyTinfo = 0; + } + + void updateRendererResource(Renderer rdr) { + RenderMolecule rm; + TextureRetained tex; + Integer texIdObj; + DetailTextureImage dtex; + + if (rdr == null) + return; + + // Take care of display lists per Rinfo that should be rebuilt + int size = addDlistPerRinfo.size(); + + if (size > 0) { + for (int j = 0; j < size; j++) { + RenderAtomListInfo rinfo = (RenderAtomListInfo)addDlistPerRinfo.get(j); + if (rinfo.renderAtom.inRenderBin()) { + Object[] obj = new Object[2]; + obj[0] = rinfo; + obj[1] = rinfo.renderAtom.renderMolecule; + rdr.dirtyDlistPerRinfoList.add(obj); + } + } + rdr.dirtyDisplayList = true; + } + + + // Take care of display lists that should be rebuilt + size = dirtyRenderMoleculeList.size(); + if (size > 0) { + for (int j = 0; j < size; j++) { + rm =(RenderMolecule)dirtyRenderMoleculeList.get(j); + rdr.dirtyRenderMoleculeList.add(rm); + } + rdr.dirtyDisplayList = true; + } + + // Take care of texture that should be freed + size = toBeAddedTextureResourceFreeList.size(); + int id; + for (int j=0; j < size; j++) { + tex = (TextureRetained)toBeAddedTextureResourceFreeList.get(j); + id = tex.objectId; + if ((id >= rdr.textureIDResourceTable.size()) || + (id <= 0) || + (rdr.textureIDResourceTable.get(id) != tex)) { + // tex.objectId may change by another Renderer thread, + // need find original texID from searching + // rdr.textureIdResourceTable + id = rdr.textureIDResourceTable.indexOf(tex); + + if (id <= 0) { + continue; + } + } + + // Since multiple renderBins (in the same screen) + // can share a texture object, make sure that + // we are not duplicating what has been added + // by a different renderBin in the same screen + if ((tex.resourceCreationMask & rdr.rendererBit) != 0) { + texIdObj = new Integer(id); + if (!rdr.textureIdResourceFreeList.contains(texIdObj)) { + rdr.textureIdResourceFreeList.add(texIdObj); + tex.resourceCreationMask &= ~rdr.rendererBit; + } + if (tex instanceof Texture2DRetained) { + dtex = ((Texture2DRetained) tex).detailTexture; + if ((dtex != null) && + ((dtex.resourceCreationMask[tex.format] & rdr.rendererBit) != 0)) { + id = dtex.objectIds[tex.format]; + if ((id >= rdr.textureIDResourceTable.size()) || + (rdr.textureIDResourceTable.get(id) != dtex)) { + id = rdr.textureIDResourceTable.indexOf(dtex); + if (id <= 0) { + continue; + } + } + texIdObj = new Integer(id); + if (!rdr.textureIdResourceFreeList.contains(texIdObj)) { + rdr.textureIdResourceFreeList.add(texIdObj); + dtex.resourceCreationMask[tex.format] &= ~rdr.rendererBit; + } + } + } + } + } + + // Take care of display list that should be freed + size = displayListResourceFreeList.size(); + Integer displayListIDObj; + + for (int j=0; j <size; j++) { + displayListIDObj = (Integer) displayListResourceFreeList.get(j); + // It doesn't harm to free the same ID twice, the + // underlying graphics library just ignore the second request + rdr.displayListResourceFreeList.add(displayListIDObj); + } + // Take care of display list that should be freed + size = removeDlistPerRinfo.size(); + for (int j=0; j < size; j++) { + RenderAtomListInfo ra = (RenderAtomListInfo)removeDlistPerRinfo.get(j); + rdr.displayListResourceFreeList.add(new Integer(ra.renderAtom.dlistIds[ra.index])); + ra.groupType = 0; + ra.renderAtom.dlistIds[ra.index] = -1; + } + } + + void updateCanvasResource(Canvas3D[] canvases) { + int i, j; + RenderMolecule rm; + TextureRetained tex; + Integer texIdObj; + DetailTextureImage dtex; + + // update dirtyRenderMoleculeList for each canvas + for (i = 0; i < canvases.length; i++) { + Canvas3D cv = canvases[i]; + + // Take care of display lists per Rinfo that should be rebuilt + int size = addDlistPerRinfo.size(); + if (size > 0) { + for ( j = 0; j < size; j++) { + RenderAtomListInfo rinfo = (RenderAtomListInfo)addDlistPerRinfo.get(j); + if (rinfo.renderAtom.inRenderBin()) { + Object[] obj = new Object[2]; + obj[0] = rinfo; + obj[1] = rinfo.renderAtom.renderMolecule; + cv.dirtyDlistPerRinfoList.add(obj); + } + } + cv.dirtyDisplayList = true; + } + // Take care of display lists that should be rebuilt + size = dirtyRenderMoleculeList.size(); + if (size > 0) { + for (j = 0; j < size; j++) { + rm = (RenderMolecule)dirtyRenderMoleculeList.get(j); + cv.dirtyRenderMoleculeList.add(rm); + } + cv.dirtyDisplayList = true; + } + // Take care of texture that should be freed + size = toBeAddedTextureResourceFreeList.size(); + int id; + for (j=0; j < size; j++) { + tex = (TextureRetained)toBeAddedTextureResourceFreeList.get(j); + id = tex.objectId; + if ((id >= cv.textureIDResourceTable.size()) || + (id <= 0) || + (cv.textureIDResourceTable.get(id) != tex)) { + // tex.objectId may change by another Renderer thread, + // need find original texID from searching + // rdr.textureIdResourceTable + id = cv.textureIDResourceTable.indexOf(tex); + + if (id <= 0) { + continue; + } + } + + + if ((tex.resourceCreationMask & cv.canvasBit) != 0) { + texIdObj = new Integer(id); + cv.textureIdResourceFreeList.add(texIdObj); + tex.resourceCreationMask &= ~cv.canvasBit; + } + if (tex instanceof Texture2DRetained) { + dtex = ((Texture2DRetained) tex).detailTexture; + if ((dtex != null) && + ((dtex.resourceCreationMask[tex.format] & cv.canvasBit) != 0)) { + id = dtex.objectIds[tex.format]; + if ((id >= cv.textureIDResourceTable.size()) || + (cv.textureIDResourceTable.get(id) != dtex)) { + id = cv.textureIDResourceTable.indexOf(dtex); + if (id <= 0) { + continue; + } + } + texIdObj = new Integer(id); + if (cv.textureIdResourceFreeList.contains(texIdObj)) { + cv.textureIdResourceFreeList.add(texIdObj); + dtex.resourceCreationMask[tex.format] &= ~cv.canvasBit; + } + } + } + } + // Take care of display list that should be freed + size = displayListResourceFreeList.size(); + for (j=0; j < size; j++) { + cv.displayListResourceFreeList.add(displayListResourceFreeList.get(j)); + } + // Take care of display list that should be freed + size = removeDlistPerRinfo.size(); + for (j=0; j < size; j++) { + RenderAtomListInfo ra = (RenderAtomListInfo)removeDlistPerRinfo.get(j); + cv.displayListResourceFreeList.add(new Integer(ra.renderAtom.dlistIds[ra.index])); + ra.groupType = 0; + ra.renderAtom.dlistIds[ra.index] = -1; + + } + } + + } + + void processMessages(long referenceTime) { + int i,j, index; + Object[] nodes; + J3dMessage messages[], m; + int component; + + messages = getMessages(referenceTime); + int nMsg = getNumMessage(); + + if (nMsg > 0) { + for (i=0; i < nMsg; i++) { + m = messages[i]; + switch (m.type) { + case J3dMessage.INSERT_NODES: + insertNodes(m); + m.decRefcount(); + break; + case J3dMessage.REMOVE_NODES: + removeNodes(m); + m.decRefcount(); + break; + case J3dMessage.TRANSFORM_CHANGED: + transformMsg = true; + m.decRefcount(); + break; + case J3dMessage.LIGHT_CHANGED: + // if none of the mirror lights are scoped to this view + // ignore this message + LightRetained[] mLts =(LightRetained[])m.args[3] ; + for (int k = 0; k < mLts.length; k++) { + if (universe.renderingEnvironmentStructure.isLightScopedToThisView(mLts[k], view)) { + lightMessageList.add(m); + break; + } + + } + break; + case J3dMessage.SWITCH_CHANGED: + visGAIsDirty = true; + visQuery = true; + processSwitchChanged(m, referenceTime); + // may need to process dirty switched-on transform + if (universe.transformStructure.getLazyUpdate()) { + transformMsg = true; + } + m.decRefcount(); + break; + case J3dMessage.BACKGROUND_CHANGED: + BackgroundRetained bg = (BackgroundRetained)m.args[0]; + if (universe.renderingEnvironmentStructure.isBgScopedToThisView(bg, view)) + reEvaluateBg = true; + m.decRefcount(); + break; + case J3dMessage.CLIP_CHANGED: + ClipRetained c = (ClipRetained)m.args[0]; + if (universe.renderingEnvironmentStructure.isClipScopedToThisView(c, view)) + reEvaluateClip = true; + m.decRefcount(); + break; + case J3dMessage.TRANSPARENCYATTRIBUTES_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained) m.args[0]; + GeometryAtom[] gaArr = (GeometryAtom[])m.args[3]; + RenderAtom ra = null; + int start = -1; + + // Get the first ra that is visible + for (int k = 0; (k < gaArr.length && (start < 0)); k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + else { + start = k; + } + } + + if (start >= 0) { + boolean restructure = (nc.mirror.changedFrequent == 0 || + ra.renderMolecule.definingTransparency != nc.mirror); + processRenderMoleculeNodeComponentChanged(m.args, + RenderMolecule.TRANSPARENCY_DIRTY, + start, restructure); + } + m.decRefcount(); + break; + } + case J3dMessage.POLYGONATTRIBUTES_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained) m.args[0]; + GeometryAtom[] gaArr = (GeometryAtom[])m.args[3]; + RenderAtom ra = null; + int start = -1; + + // Get the first ra that is visible + // Get the first ra that is visible + for (int k = 0; (k < gaArr.length && (start < 0)); k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + else { + start = k; + } + } + + if (start >= 0) { + boolean restructure = (nc.mirror.changedFrequent == 0 || + ra.renderMolecule.definingPolygonAttributes != nc.mirror); + processRenderMoleculeNodeComponentChanged(m.args, + RenderMolecule.POLYGONATTRS_DIRTY, + start, restructure); + } + m.decRefcount(); + break; + } + case J3dMessage.LINEATTRIBUTES_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained) m.args[0]; + GeometryAtom[] gaArr = (GeometryAtom[])m.args[3]; + RenderAtom ra = null; + int start = -1; + + // Get the first ra that is visible + // Get the first ra that is visible + for (int k = 0; (k < gaArr.length && (start < 0)); k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + else { + start = k; + } + } + + if (start >= 0) { + boolean restructure = (nc.mirror.changedFrequent == 0 || + ra.renderMolecule.definingLineAttributes != nc.mirror); + processRenderMoleculeNodeComponentChanged(m.args, + RenderMolecule.LINEATTRS_DIRTY, + start, restructure); + } + m.decRefcount(); + break; + } + case J3dMessage.POINTATTRIBUTES_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained) m.args[0]; + GeometryAtom[] gaArr = (GeometryAtom[])m.args[3]; + RenderAtom ra = null; + int start = -1; + // Get the first ra that is visible + // Get the first ra that is visible + for (int k = 0; (k < gaArr.length && (start < 0)); k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + else { + start = k; + } + } + + if (start >= 0) { + boolean restructure = (nc.mirror.changedFrequent == 0 || + ra.renderMolecule.definingPointAttributes != nc.mirror); + + processRenderMoleculeNodeComponentChanged(m.args, + RenderMolecule.POINTATTRS_DIRTY, + start, restructure); + } + m.decRefcount(); + break; + } + case J3dMessage.MATERIAL_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained) m.args[0]; + GeometryAtom[] gaArr = (GeometryAtom[])m.args[3]; + RenderAtom ra = null; + int start = -1; + + // Get the first ra that is visible + // Get the first ra that is visible + for (int k = 0; (k < gaArr.length && (start < 0)); k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + else { + start = k; + } + } + + if (start >= 0) { + boolean restructure = (nc.mirror.changedFrequent == 0 || + ra.renderMolecule.definingMaterial != nc.mirror); + processRenderMoleculeNodeComponentChanged(m.args, + RenderMolecule.MATERIAL_DIRTY, + start, restructure); + } + m.decRefcount(); + break; + } + case J3dMessage.COLORINGATTRIBUTES_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained) m.args[0]; + GeometryAtom[] gaArr = (GeometryAtom[])m.args[3]; + RenderAtom ra = null; + int start = -1; + + // Get the first ra that is visible + // Get the first ra that is visible + for (int k = 0; (k < gaArr.length && (start < 0)); k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + else { + start = k; + } + } + + if (start >= 0) { + boolean restructure = (nc.mirror.changedFrequent == 0 || + ra.renderMolecule.definingColoringAttributes != nc.mirror); + processRenderMoleculeNodeComponentChanged(m.args, + RenderMolecule.COLORINGATTRS_DIRTY, + start, restructure); + } + m.decRefcount(); + break; + } + case J3dMessage.TEXTUREATTRIBUTES_CHANGED: + processTextureAttributesChanged( + (NodeComponentRetained) m.args[0], + (GeometryAtom[])m.args[3]); + m.decRefcount(); + break; + case J3dMessage.IMAGE_COMPONENT_CHANGED: + addDirtyNodeComponent((NodeComponentRetained)m.args[0]); + m.decRefcount(); + break; + case J3dMessage.TEXTURE_UNIT_STATE_CHANGED: + processTextureUnitStateChanged( + (NodeComponentRetained) m.args[0], + (GeometryAtom[])m.args[3]); + m.decRefcount(); + break; + case J3dMessage.TEXCOORDGENERATION_CHANGED: + processTexCoordGenerationChanged( (NodeComponentRetained) m.args[0], + (GeometryAtom[])m.args[3]); + m.decRefcount(); + break; + case J3dMessage.TEXTURE_CHANGED: + // Texture is always in a sole user position + processTextureChanged((NodeComponentRetained) m.args[0], + (GeometryAtom[])m.args[3], + m.args); + + m.decRefcount(); + break; + case J3dMessage.RENDERINGATTRIBUTES_CHANGED: + processAttributeBinNodeComponentChanged(m.args); + component = ((Integer)m.args[1]).intValue(); + if (component == RenderingAttributesRetained.VISIBLE) { + visGAIsDirty = true; + visQuery = true; + } + m.decRefcount(); + break; + case J3dMessage.APPEARANCE_CHANGED: + processAppearanceChanged(m.args); + m.decRefcount(); + break; + case J3dMessage.FOG_CHANGED: + FogRetained mfog = ((FogRetained)m.args[0]).mirrorFog; + if (universe.renderingEnvironmentStructure.isFogScopedToThisView(mfog, view)) { + processFogChanged(m.args); + } + m.decRefcount(); + break; + case J3dMessage.ALTERNATEAPPEARANCE_CHANGED: + AlternateAppearanceRetained maltapp = ((AlternateAppearanceRetained)m.args[0]).mirrorAltApp; + if (universe.renderingEnvironmentStructure.isAltAppScopedToThisView(maltapp, view)) { + altAppearanceDirty = true; + } + m.decRefcount(); + break; + case J3dMessage.MODELCLIP_CHANGED: + ModelClipRetained mc= ((ModelClipRetained)m.args[0]).mirrorModelClip; + if (universe.renderingEnvironmentStructure.isMclipScopedToThisView(mc, view)) { + processModelClipChanged(m.args); + } + m.decRefcount(); + break; + case J3dMessage.BOUNDINGLEAF_CHANGED: + processBoundingLeafChanged(m.args, + referenceTime); + m.decRefcount(); + break; + case J3dMessage.SHAPE3D_CHANGED: + processShapeChanged(m.args, referenceTime); + m.decRefcount(); + break; + case J3dMessage.ORIENTEDSHAPE3D_CHANGED: + processOrientedShape3DChanged((Object[])m.args[0]); + m.decRefcount(); + break; + case J3dMessage.MORPH_CHANGED: + processMorphChanged(m.args, referenceTime); + component = ((Integer)m.args[1]).intValue(); + if ((component & MorphRetained.GEOMETRY_CHANGED) == 0) { + visGAIsDirty = true; + visQuery = true; + } + m.decRefcount(); + break; + case J3dMessage.UPDATE_VIEW: + { + View v = (View)m.args[0]; + ViewPlatform vp = v.getViewPlatform(); + int comp = ((Integer)(m.args[2])).intValue(); + int value = ((Integer)(m.args[3])).intValue(); + if (comp == View.TRANSP_SORT_POLICY_CHANGED) { + if (value != transpSortMode) { + reEvaluateSortMode = true; + cachedTranspSortMode = value; + } + } else if (vp != null) { + if (value != transpSortMode) { + reEvaluateSortMode = true; + cachedTranspSortMode = value; + } + updateViewPlatform((ViewPlatformRetained)vp.retained, + ((Float)m.args[1]).floatValue()); + visQuery = true; + // TODO : Handle view.visibilityPolicy changed. + if(((View.VISIBILITY_POLICY_DIRTY != 0) && + (View.VISIBILITY_DRAW_ALL != view.viewCache.visibilityPolicy)) || + locale != ((ViewPlatformRetained) (vp.retained)).locale) { + + for (int n = (renderAtoms.size() - 1); n>=0 ; n--) { + removeARenderAtom((RenderAtom) renderAtoms.get(n)); + } + renderAtoms.clear(); + visGAIsDirty = true; + if (locale != ((ViewPlatformRetained) (vp.retained)).locale) { + locale = ((ViewPlatformRetained) (vp.retained)).locale; + localeChanged = true; + } + } + } + m.decRefcount(); + } + break; + case J3dMessage.UPDATE_VIEWPLATFORM: + updateViewPlatform((ViewPlatformRetained) m.args[0], + ((Float)m.args[1]).floatValue()); + m.decRefcount(); + break; + case J3dMessage.TEXT3D_DATA_CHANGED: + processDataChanged((Object[])m.args[0], + (Object[])m.args[1], + referenceTime); + m.decRefcount(); + break; + case J3dMessage.GEOMETRY_CHANGED: + processGeometryChanged(m.args); + visGAIsDirty = true; + visQuery = true; + m.decRefcount(); + break; + + case J3dMessage.BOUNDS_AUTO_COMPUTE_CHANGED: + case J3dMessage.REGION_BOUND_CHANGED: + processGeometryAtomsChanged((Object[])m.args[0]); + visGAIsDirty = true; + visQuery = true; + m.decRefcount(); + break; + case J3dMessage.TEXT3D_TRANSFORM_CHANGED: + processText3DTransformChanged((Object[])m.args[0], + (Object[])m.args[1], + referenceTime); + visQuery = true; + m.decRefcount(); + break; + case J3dMessage.ORDERED_GROUP_INSERTED: + processOrderedGroupInserted(m); + // Do not do decRefcount() here. We'll do it in updateObject(). + ogCIOList.add(m); + break; + case J3dMessage.ORDERED_GROUP_REMOVED: + processOrderedGroupRemoved(m); + // Do not do decRefcount() here. We'll do it in updateObject(). + ogCIOList.add(m); + break; + case J3dMessage.ORDERED_GROUP_TABLE_CHANGED: + // Do not do decRefcount() here. We'll do it in updateObject(). + ogCIOList.add(m); + break; + case J3dMessage.RENDER_OFFSCREEN: + offScreenMessage.add(m); + break; + case J3dMessage.VIEWSPECIFICGROUP_CHANGED: + processViewSpecificGroupChanged(m); + visQuery = true; + m.decRefcount(); + break; + default: + m.decRefcount(); + } + } + + if (transformMsg) { + processTransformChanged(referenceTime); + transformMsg = false; + } + if (lightMessageList.size() > 0) { + processLightChanged(); + lightMessageList.clear(); + } + VirtualUniverse.mc.addMirrorObject(this); + + // clear the array to prevent memory leaks + Arrays.fill(messages, 0, nMsg, null); + } + + if (reEvaluateBg) { + currentActiveBackground = universe.renderingEnvironmentStructure. + getApplicationBackground(vpSchedSphereInVworld, locale, view); + } + + + if (visQuery) { + GeometryAtom[] bgGeometryAtoms; + boolean allEnComp; + + // computeViewFrustumBox in VisibilityStructure. + computeViewFrustumBBox(viewFrustumBBox); + // System.out.println("viewFrustumBBox = " + this); + + ViewPlatform vp = view.getViewPlatform(); + if (vp != null) { + allEnComp = universe.geometryStructure. + getVisibleBHTrees(this, bhTreesArrList, viewFrustumBBox, + locale, referenceTime, + visGAIsDirty || reactivateView || localeChanged || + ((view.viewCache.vcDirtyMask & + View.VISIBILITY_POLICY_DIRTY) != 0), + view.viewCache.visibilityPolicy); + + reactivateView = false; + // process background geometry atoms + if (currentActiveBackground != null && + currentActiveBackground.geometryBranch != null) { + bgGeometryAtoms = + currentActiveBackground.getBackgroundGeometryAtoms(); + if (bgGeometryAtoms != null) { + processBgGeometryAtoms(bgGeometryAtoms, referenceTime); + } + } + + if(!allEnComp) { + // Increment the framecount for compaction ... + frameCount++; + if (frameCount > frameCountCutoff) { + frameCount = 0; + checkForCompaction(); + } + else if (frameCount == notVisibleCount) { + removeCutoffTime = referenceTime; + } + } + } + // Reset dirty bits. + visGAIsDirty = false; + visQuery = false; + + } + // Two environments are dirty + // If lights, fog or model clip have been added/removed, then + // reEvaluate RenderAtoms and mark the lightbin and + // env set dirty if applicable + if (envDirty == REEVALUATE_ALL_ENV || envDirty == 3 || + envDirty > 4) { + reEvaluateEnv(changedLts, changedFogs, changedModelClips, true, + altAppearanceDirty); + } + else if (envDirty == 0 && altAppearanceDirty) { + reEvaluateAlternateAppearance(); + } + else { + if ((envDirty & REEVALUATE_LIGHTS) != 0) { + reEvaluateLights(altAppearanceDirty); + } + else if ((envDirty & REEVALUATE_FOG) != 0) + reEvaluateFog(changedFogs, (changedFogs.size() > 0), altAppearanceDirty); + else if ((envDirty & REEVALUATE_MCLIP) != 0) + reEvaluateModelClip(changedModelClips, (changedModelClips.size() > 0), altAppearanceDirty); + } + + + + // do any pre-update node component screening + + if (updateCheckList.size() > 0) { + int size = updateCheckList.size(); + NodeComponentUpdate bin; + for (int k = 0; k < size; k++) { + bin = (NodeComponentUpdate) updateCheckList.get(k); + bin.updateNodeComponentCheck(); + } + updateCheckList.clear(); + } + + + changedLts.clear(); + changedFogs.clear(); + changedModelClips.clear(); + envDirty = 0; + altAppearanceDirty = false; + + view.renderBinReady = true; + + VirtualUniverse.mc.sendRunMessage(view, + J3dThread.RENDER_THREAD); + } + + + void processSwitchChanged(J3dMessage m, long refTime) { + int i; + UnorderList arrList; + int size; + Object[] nodes, nodesArr; + LeafRetained leaf; + + RenderingEnvironmentStructure rdrEnvStr = + universe.renderingEnvironmentStructure; + + UpdateTargets targets = (UpdateTargets)m.args[0]; + arrList = targets.targetList[Targets.ENV_TARGETS]; + + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (int h=0; h<size; h++) { + nodes = (Object[])nodesArr[h]; + for (i=0; i<nodes.length; i++) { + + if (nodes[i] instanceof LightRetained && + rdrEnvStr.isLightScopedToThisView(nodes[i], view)) { + envDirty |= REEVALUATE_LIGHTS; + } else if (nodes[i] instanceof FogRetained && + rdrEnvStr.isFogScopedToThisView(nodes[i], view)) { + envDirty |= REEVALUATE_FOG; + } else if (nodes[i] instanceof ModelClipRetained && + rdrEnvStr.isMclipScopedToThisView(nodes[i], view)) { + envDirty |= REEVALUATE_MCLIP; + } else if (nodes[i] instanceof BackgroundRetained && + rdrEnvStr.isBgScopedToThisView(nodes[i], view)) { + reEvaluateBg = true; + } else if (nodes[i] instanceof ClipRetained && + rdrEnvStr.isClipScopedToThisView(nodes[i], view)) { + reEvaluateClip = true; + } else if (nodes[i] instanceof AlternateAppearanceRetained && + rdrEnvStr.isAltAppScopedToThisView(nodes[i], view)) { + altAppearanceDirty = true; + } + } + } + } + + arrList = targets.targetList[Targets.BLN_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + Object[] objArr = (Object[])m.args[1]; + Object[] obj, users; + BoundingLeafRetained mbleaf; + + for (int h=0; h<size; h++) { + nodes = (Object[])nodesArr[h]; + obj = (Object[])objArr[h]; + for (i=0; i<nodes.length; i++) { + + users = (Object[])obj[i]; + mbleaf = (BoundingLeafRetained)nodes[i]; + for (int j = 0; j < users.length; j++) { + + if (users[j] instanceof FogRetained && + rdrEnvStr.isFogScopedToThisView(users[j], view)) { + envDirty |= REEVALUATE_FOG; + } else if (users[j] instanceof LightRetained && + rdrEnvStr.isLightScopedToThisView(users[j], view)) { + envDirty |= REEVALUATE_LIGHTS; + } else if (users[j] instanceof ModelClipRetained && + rdrEnvStr.isMclipScopedToThisView(users[j], view)) { + envDirty |= REEVALUATE_MCLIP; + } else if (users[j] instanceof + AlternateAppearanceRetained && + rdrEnvStr.isAltAppScopedToThisView(users[j], view)) { + altAppearanceDirty = true; + } else if (users[j] instanceof BackgroundRetained && + rdrEnvStr.isBgScopedToThisView(users[j], view)) { + reEvaluateBg = true; + } else if (users[j] instanceof ClipRetained && + rdrEnvStr.isClipScopedToThisView(users[j], view)) { + reEvaluateClip = true; + } + } + } + } + } + } + + + /** + * Transparency/Line/point/Poly attributes is different from other renderMolecule + * attributes since the renderatom could move from opaque bin + * to transparent bin + */ + void processPossibleBinChanged(Object[] args) { + int i; + GeometryAtom[] gaArr = (GeometryAtom[])args[3]; + for (i = 0; i < gaArr.length; i++) { + RenderAtom ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + // If renderAtom is in orderedGroup or with this + // change continues to be in the same higher level + // lightBin(transparent or opaque) then reInsert at + // the textureBin level, other Insert at the lightBin + // level + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + } + } + + + /** + * This processes a materiala and other rendermolecule node comp change. + */ + void processRenderMoleculeNodeComponentChanged(Object[] args, int mask, int start, + boolean restructure) { + int i; + NodeComponentRetained nc = (NodeComponentRetained) args[0]; + GeometryAtom[] gaArr = (GeometryAtom[])args[3]; + for (i = start; i < gaArr.length; i++) { + RenderAtom ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + // Check if the changed renderAtom is already in + // a separate bin - this is to handle the case + // when it has been changed to frequent, then to + // infrequent and then to frequent again! + // If the bin is in soleUser case and one of the components + // has been changed to frequent then remove the clone + // and point to the mirror + // System.out.println("restructure = "+restructure+" ra.renderMolecule.soleUser ="+ra.renderMolecule.soleUser); + if (restructure && !ra.renderMolecule.soleUser) { + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + /* + if (nc.mirror.changedFrequent != 0) { + if ((ra.renderMolecule.soleUserCompDirty& RenderMolecule.ALL_DIRTY_BITS) == 0 ) { + rmUpdateList.add(ra.renderMolecule); + } + ra.renderMolecule.soleUserCompDirty |= mask; + } + */ + } + else { + if ((ra.renderMolecule.soleUserCompDirty& RenderMolecule.ALL_DIRTY_BITS) == 0 ) { + rmUpdateList.add(ra.renderMolecule); + } + ra.renderMolecule.soleUserCompDirty |= mask; + } + } + + } + + + void processTextureAttributesChanged(NodeComponentRetained nc, + GeometryAtom[] gaArr) { + + RenderAtom ra = null; + TextureBin tb; + AttributeBin ab; + boolean reInsertNeeded = false; + + if (nc.mirror.changedFrequent == 0) { + reInsertNeeded = true; + } + + for (int k = 0; k < gaArr.length; k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + + tb = ra.renderMolecule.textureBin; + + if (!reInsertNeeded) { + + // if changedFrequent is not zero, then need + // to check if the node component is currently + // in an equivalent bin or not. If it is in an + // equivalent bin, then the affected ra needs to + // be reinserted to a bin with a soleUser + // TextureAttributes + + for (int t = 0; t < tb.texUnitState.length; t++) { + + if (tb.texUnitState[t] == null) { + continue; + + } else if (tb.texUnitState[t].texAttrs == nc.mirror) { + // the TextureAttributes is already in + // a sole user position, no need to do anything; + // can bail out now + return; + } + } + } + + if ((tb.tbFlag & TextureBin.SOLE_USER) != 0) { + + // if the TextureBin is a sole user, then + // no need to reInsert, just simply update the + // TextureAttributes references @update + + if (tb.soleUserCompDirty == 0) { + tbUpdateList.add(tb); + } + + tb.soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TA; + + } else { + ab= ra.renderMolecule.textureBin.attributeBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertTextureBin(ab, ra); + } + } + } + + void processTexCoordGenerationChanged(NodeComponentRetained nc, + GeometryAtom[] gaArr) { + + RenderAtom ra = null; + TextureBin tb; + AttributeBin ab; + boolean reInsertNeeded = false; + + if (nc.mirror.changedFrequent == 0) { + reInsertNeeded = true; + } + + for (int k = 0; k < gaArr.length; k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + + tb = ra.renderMolecule.textureBin; + + if (!reInsertNeeded) { + + // if changedFrequent is not zero, then need + // to check if the node component is currently + // in an equivalent bin or not. If it is in an + // equivalent bin, then the affected ra needs to + // be reinserted to a bin with a soleUser + // TexCoordGeneration + + for (int t = 0; t < tb.texUnitState.length; t++) { + + if (tb.texUnitState[t] == null) { + continue; + + } else if (tb.texUnitState[t].texGen == nc.mirror) { + // the TexCoordGeneration is already in + // a sole user position, no need to do anything; + // can bail out now + return; + } + } + } + + if ((tb.tbFlag & TextureBin.SOLE_USER) != 0) { + + // if the TextureBin is a sole user, then + // no need to reInsert, just simply update the + // TexCoordGeneration references @update + + if (tb.soleUserCompDirty == 0) { + tbUpdateList.add(tb); + } + + tb.soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TC; + + } else { + ab= ra.renderMolecule.textureBin.attributeBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertTextureBin(ab, ra); + } + } + } + + + void processTextureChanged(NodeComponentRetained nc, + GeometryAtom[] gaArr, + Object args[]) { + + RenderAtom ra = null; + TextureBin tb; + AttributeBin ab; + boolean reInsertNeeded = false; + int command = ((Integer)args[1]).intValue(); + + switch (command) { + case TextureRetained.ENABLE_CHANGED: { + for (int i = 0; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + + if (ra== null || !ra.inRenderBin()) + continue; + + tb = ra.renderMolecule.textureBin; + + if (tb.soleUserCompDirty == 0) { + // put this texture unit state on the sole user + // update list if it's not already there + tbUpdateList.add(tb); + } + + tb.soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TEXTURE; + } + break; + } + case TextureRetained.IMAGE_CHANGED: { + + TextureRetained texture = (TextureRetained)nc.mirror; + Object imgChangedArgs[] = (Object[])args[2]; + int level = ((Integer)imgChangedArgs[0]).intValue(); + int face = ((Integer)imgChangedArgs[2]).intValue(); + ImageComponent newImage = (ImageComponent)imgChangedArgs[1]; + ImageComponentRetained oldImage; + + // first remove the old image from the RenderBin's + // node component list if necessary. + // Note: image reference in the texture mirror object + // is not updated yet, so it's ok to reference + // the mirror object for the old image reference + + oldImage = texture.images[face][level]; + + // it is possible that oldImage.source == null because + // the mipmap could have been created by the library, and + // hence don't have source + + if (oldImage != null) { + this.removeNodeComponent(oldImage); + } + + // then add the new one to the list if it is byReference or + // modifiable. + + if (newImage != null ) { + this.addNodeComponent(newImage.retained); + } + break; + } + case TextureRetained.IMAGES_CHANGED: { + Object imgChangedArgs[] = (Object [])args[2]; + ImageComponent images[] = (ImageComponent [])imgChangedArgs[0]; + int face = ((Integer)imgChangedArgs[1]).intValue(); + TextureRetained texture = (TextureRetained)nc.mirror; + ImageComponentRetained oldImage; + + for (int i = 0; i < texture.maxLevels; i++) { + + // first remove the old image from the RenderBin's + // node component list if necessary. + // Note: image reference in the texture mirror object + // is not updated yet, so it's ok to reference + // the mirror object for the old image reference + + oldImage = texture.images[face][i]; + + // it is possible that oldImage.source == null because + // the mipmap could have been created by the library, and + // hence don't have source + + if (oldImage != null) { + this.removeNodeComponent(oldImage); + } + + // then add the new one to the list if it is byReference + if (images[i] != null ) { + this.addNodeComponent(((ImageComponent)images[i]).retained); + } + } + break; + } + } + } + + + void processTextureUnitStateChanged(NodeComponentRetained nc, + GeometryAtom[] gaArr) { + RenderAtom ra = null; + TextureBin tb; + AttributeBin ab; + boolean mirrorSet = false; + boolean firstTextureBin = true; + + for (int k = 0; k < gaArr.length; k++) { + ra = gaArr[k].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + + tb = ra.renderMolecule.textureBin; + + if (firstTextureBin) { + + for (int t = 0; + t < tb.texUnitState.length && !mirrorSet; + t++) { + + if (tb.texUnitState[t] == null) { + continue; + + } else if (tb.texUnitState[t].mirror == nc.mirror) { + mirrorSet = true; + firstTextureBin = false; + } + } + firstTextureBin = false; + } + + if (mirrorSet) { + + if (tb.soleUserCompDirty == 0) { + tbUpdateList.add(tb); + } + + tb.soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TUS; + + } else { + ab= ra.renderMolecule.textureBin.attributeBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertTextureBin(ab, ra); + } + } + } + + + /** + * This processes a rendering attribute change. + */ + + void processAttributeBinNodeComponentChanged(Object[] args) { + int i; + GeometryAtom[] gaArr = (GeometryAtom[])args[3]; + int component = ((Integer)args[1]).intValue(); + NodeComponentRetained nc = (NodeComponentRetained) args[0]; + + RenderAtom ra = null; + int start = -1; + + // Get the first ra that is visible + for (i = 0; (i < gaArr.length && (start < 0)); i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + else { + start = i; + } + } + if (start >= 0) { + // Force restucture, when changedFrequent is zero OR + // when it is changed to changedFrequent the first time OR + // when the ignoreVC changedFrequent flag is set for the first time + // when the last one is set for the first time, we need to force + // any separate dlist in RMs to go thru VA + boolean restructure = (nc.mirror.changedFrequent == 0 || + ra.renderMolecule.textureBin.attributeBin.definingRenderingAttributes != nc.mirror); + + if (component != RenderingAttributesRetained.VISIBLE) { + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + if (restructure && !ra.renderMolecule.textureBin.attributeBin.soleUser) { + EnvironmentSet e= ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + /* + // If changed Frequent the first time, + // then the cached value + // may not be up-to-date since the nc is + // updated in updateObject + // So, add it to the list so that the cached value can + // be updated + if (nc.mirror.changedFrequent != 0) { + AttributeBin aBin = ra.renderMolecule.textureBin.attributeBin; + if ((aBin.onUpdateList & AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST) == 0 ) { + aBinUpdateList.add(aBin); + aBin.onUpdateList |= AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST; + } + } + */ + } + else { + AttributeBin aBin = ra.renderMolecule.textureBin.attributeBin; + if ((aBin.onUpdateList & AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST) == 0 ) { + aBinUpdateList.add(aBin); + aBin.onUpdateList |= AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST; + } + } + } + } + else { + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + + if (ra== null || !ra.inRenderBin()) + continue; + renderAtoms.remove(renderAtoms.indexOf(ra)); + removeARenderAtom(ra); + } + } + } + } + + + void processFogChanged(Object[] args) { + FogRetained fog = (FogRetained)args[0]; + EnvironmentSet e; + int component = ((Integer)args[1]).intValue(); + + if ((component &(FogRetained.SCOPE_CHANGED | + FogRetained.BOUNDS_CHANGED | + FogRetained.BOUNDINGLEAF_CHANGED)) != 0){ + envDirty |= REEVALUATE_FOG; + } + else { + UnorderList list = fog.mirrorFog.environmentSets; + synchronized (list) { + EnvironmentSet envsets[] = (EnvironmentSet []) list.toArray(false); + int size = list.size(); + for (int i = 0; i < size; i++) { + e = envsets[i]; + e.canvasDirty |= Canvas3D.FOG_DIRTY; + if (!e.onUpdateList) { + objUpdateList.add(e); + e.onUpdateList = true; + } + } + } + } + } + + + /** + * This routine get called whenever a component of the appearance + * changes + */ + void processAppearanceChanged(Object[] args){ + int component = ((Integer)args[1]).intValue(); + int i; + GeometryAtom[] gaArr = (GeometryAtom[] )args[3]; + GeometryAtom ga; + RenderAtom ra = null; + AppearanceRetained app = (AppearanceRetained) args[0]; + int TEXTURE_STATE_CHANGED = + AppearanceRetained.TEXTURE_UNIT_STATE | + AppearanceRetained.TEXTURE | + AppearanceRetained.TEXTURE_ATTR | + AppearanceRetained.TEXCOORD_GEN ; + + int start = -1; + + // Get the first ra that is visible + for (i = 0; (i < gaArr.length && (start < 0)); i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) { + continue; + } + else { + start = i; + } + } + + if (start >= 0) { + + if ((component & TEXTURE_STATE_CHANGED) != 0) { + + + if (((app.changedFrequent & TEXTURE_STATE_CHANGED) != 0) && + ((ra.renderMolecule.textureBin.tbFlag & + TextureBin.SOLE_USER) != 0)) { + +/* +System.out.println("renderbin. texture state changed tb sole user " + + ra.renderMolecule.textureBin + " tb.tbFlag= " + + ra.renderMolecule.textureBin.tbFlag); +*/ + + TextureBin tb; + + + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + tb = ra.renderMolecule.textureBin; + if (tb.soleUserCompDirty == 0) { + tbUpdateList.add(tb); + } + + // mark that the texture unit state ref is changed + // also mark that the TextureBin needs to reevaluate + // number of active textures + tb.soleUserCompDirty |= + TextureBin.SOLE_USER_DIRTY_REF; + } + } else { +/* +System.out.println("renderbin. texture state changed tb not sole user " + + ra.renderMolecule.textureBin + " tb.tbFlag= " + + ra.renderMolecule.textureBin.tbFlag); + +System.out.println("......tb.soleUser= " + + ((ra.renderMolecule.textureBin.tbFlag & TextureBin.SOLE_USER) != 0) + + " app.changedFrequent= " + + ((app.changedFrequent & TEXTURE_STATE_CHANGED) != 0)); + +*/ + + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + AttributeBin ab = ra.renderMolecule.textureBin.attributeBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertTextureBin(ab, ra); + } + } + } else if ((component & AppearanceRetained.RENDERING) != 0) { + boolean visible = ((Boolean)args[4]).booleanValue(); + visGAIsDirty = true; + visQuery = true; + if (!visible) { + // remove all gaAttrs + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + + if (ra== null || !ra.inRenderBin()) + continue; + renderAtoms.remove(renderAtoms.indexOf(ra)); + removeARenderAtom(ra); + } + } + else { + if ((app.mirror.changedFrequent & component) != 0 && + ra.renderMolecule.textureBin.attributeBin.soleUser) { + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + AttributeBin aBin = ra.renderMolecule.textureBin.attributeBin; + if ((aBin.onUpdateList & AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST) == 0 ) { + aBinUpdateList.add(aBin); + aBin.onUpdateList |= AttributeBin.ON_CHANGED_FREQUENT_UPDATE_LIST; + } + + } + } + else { + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + EnvironmentSet e = ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + } + } + } + + else if ((component & (AppearanceRetained.COLOR | + AppearanceRetained.MATERIAL| + AppearanceRetained.TRANSPARENCY| + AppearanceRetained.POLYGON | + AppearanceRetained.LINE| + AppearanceRetained.POINT)) != 0) { + // System.out.println("AppearanceRetained.POINT = "+AppearanceRetained.POINT); + // System.out.println("(app.mirror.changedFrequent & component) != 0 "+app.mirror.changedFrequent ); + // System.out.println("ra.renderMolecule.soleUser "+ra.renderMolecule.soleUser); + if ((app.mirror.changedFrequent & component) != 0 && + ra.renderMolecule.soleUser) { + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + if ((ra.renderMolecule.soleUserCompDirty& RenderMolecule.ALL_DIRTY_BITS) == 0 ) { + rmUpdateList.add(ra.renderMolecule); + } + ra.renderMolecule.soleUserCompDirty |= component; + + } + + } + else { + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + + } + } + } + } else { + // Nothing is visible + if ((component & AppearanceRetained.RENDERING) != 0) { + // Rendering attributes change + visGAIsDirty = true; + visQuery = true; + } + } + } + + + + + void processModelClipChanged(Object[] args) { + ModelClipRetained modelClip = + (ModelClipRetained)args[0]; + EnvironmentSet e; + int component = ((Integer)args[1]).intValue(); + + if ((component & (ModelClipRetained.SCOPE_CHANGED | + ModelClipRetained.BOUNDS_CHANGED | + ModelClipRetained.BOUNDINGLEAF_CHANGED)) != 0){ + envDirty |= REEVALUATE_MCLIP; + + } else if ((component & (ModelClipRetained.ENABLE_CHANGED | + ModelClipRetained.ENABLES_CHANGED)) != 0) { + // need to render modelclip + if (!changedModelClips.contains(modelClip.mirrorModelClip)) + changedModelClips.add(modelClip.mirrorModelClip); + + // need to reevaluate envset + envDirty |= REEVALUATE_MCLIP; + + } else { + UnorderList list = modelClip.mirrorModelClip.environmentSets; + synchronized (list) { + EnvironmentSet envsets[] = (EnvironmentSet []) list.toArray(false); + int size = list.size(); + for (int i = 0; i < size; i++) { + e = envsets[i]; + e.canvasDirty |= Canvas3D.MODELCLIP_DIRTY; + if (!e.onUpdateList) { + objUpdateList.add(e); + e.onUpdateList = true; + } + } + } + } + } + + + /** + * This routine get called whenever a region of the boundingleaf + * changes + */ + void processBoundingLeafChanged(Object[] args, long refTime){ + // Notify all users of this bounding leaf, it may + // result in the re-evaluation of the lights/fogs/backgrounds + Object[] users = (Object[])(args[3]); + int i; + + // TODO: Handle other object affected by bounding leaf changes + for (i = 0; i < users.length; i++) { + LeafRetained leaf = (LeafRetained)users[i]; + switch(leaf.nodeType) { + case NodeRetained.AMBIENTLIGHT: + case NodeRetained.POINTLIGHT: + case NodeRetained.SPOTLIGHT: + case NodeRetained.DIRECTIONALLIGHT: + if (universe.renderingEnvironmentStructure.isLightScopedToThisView(leaf, view)) + envDirty |= REEVALUATE_LIGHTS; + break; + case NodeRetained.LINEARFOG: + case NodeRetained.EXPONENTIALFOG: + if (universe.renderingEnvironmentStructure.isFogScopedToThisView(leaf, view)) + envDirty |= REEVALUATE_FOG; + break; + case NodeRetained.BACKGROUND: + if (universe.renderingEnvironmentStructure.isBgScopedToThisView(leaf, view)) + reEvaluateBg = true; + break; + case NodeRetained.CLIP: + if (universe.renderingEnvironmentStructure.isClipScopedToThisView(leaf, view)) + reEvaluateClip = true; + break; + case NodeRetained.MODELCLIP: + if (universe.renderingEnvironmentStructure.isMclipScopedToThisView(leaf, view)) + envDirty |= REEVALUATE_MCLIP; + break; + case NodeRetained.ALTERNATEAPPEARANCE: + if (universe.renderingEnvironmentStructure.isAltAppScopedToThisView(leaf, view)) altAppearanceDirty = true; + break; + default: + break; + } + } + + } + + void processOrientedShape3DChanged(Object[] gaArr) { + + RenderAtom ra; + for (int i = 0; i < gaArr.length; i++) { + ra = ((GeometryAtom)gaArr[i]).getRenderAtom(view); + if (ra!= null && ra.inRenderBin() && !ra.inDirtyOrientedRAs()) { + dirtyOrientedRAs.add(ra); + ra.dirtyMask |= RenderAtom.IN_DIRTY_ORIENTED_RAs; + } + } + } + + + void processShapeChanged(Object[] args, long refTime) { + + int component = ((Integer)args[1]).intValue(); + int i; + RenderAtom ra; + RenderAtom raNext; + EnvironmentSet e; + TextureBin tb; + if ((component & Shape3DRetained.APPEARANCE_CHANGED) != 0) { + GeometryAtom[] gaArr = (GeometryAtom[])args[4]; + if (gaArr.length > 0) { + if (!gaArr[0].source.appearanceOverrideEnable) { + for (i =0; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra == null || !ra.inRenderBin()) { + continue; + } + ra.app = ra.geometryAtom.source.appearance; + e = ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + } + else { + for (i =0; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra == null || !ra.inRenderBin()) { + continue; + } + // if its using the alternate appearance continue .. + if (ra.app == ra.geometryAtom.source.otherAppearance) + continue; + ra.app = ra.geometryAtom.source.appearance; + e = ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + } + } + } + else if ((component & Shape3DRetained.GEOMETRY_CHANGED) != 0) { + processDataChanged((Object[])args[2], (Object[])args[3], refTime); + } + else if ((component & Shape3DRetained.APPEARANCEOVERRIDE_CHANGED) != 0) { + AppearanceRetained app, saveApp = null; + Shape3DRetained saveShape = null; + GeometryAtom[] gaArr = (GeometryAtom[])args[4]; + Object[] retVal; + for (i =0; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra == null || !ra.inRenderBin()) + continue; + // Once shape could have many geometryAtoms, add the + // mirrorShape as a user of an appearance only once + + if (saveShape != ra.geometryAtom.source) { + saveShape = ra.geometryAtom.source; + if (ra.geometryAtom.source.appearanceOverrideEnable) { + retVal =universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + saveShape.otherAppearance = (AppearanceRetained)retVal[1]; + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + if (app != null) { + app.sgApp.addAMirrorUser(saveShape); + } + } + else {// use the default + app = ra.geometryAtom.source.appearance; + } + } + else { + // If it were using the alternate appearance + // remove itself as the user + if (ra.app == saveShape.otherAppearance && + ra.app != null) { + ra.app.sgApp.removeAMirrorUser(saveShape); + } + app = ra.geometryAtom.source.appearance; + saveShape.otherAppearance = null; + } + saveApp = app; + } + else { + app = saveApp; + } + ra.app = app; + e = ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + } + + } + + + /** + * Process a Text3D data change. This involves removing all the + * old geometry atoms in the list, and the creating new ones. + */ + void processDataChanged(Object[] oldGaList, + Object[] newGaList, long referenceTime) { + Shape3DRetained s, src; + RenderAtom ra; + RenderMolecule rm; + int i, j; + Transform3D trans; + ArrayList rmChangedList = new ArrayList(5); + GeometryRetained geo; + GeometryAtom ga; + + for (i=0; i<oldGaList.length; i++) { + ga = ((GeometryAtom)oldGaList[i]); + + // Make sure that there is atleast one geo that is non-null + geo = null; + for (int k = 0; (k < ga.geometryArray.length && geo == null); k++) { + geo = ga.geometryArray[k]; + } + if (geo == null) + continue; + + + ra = ga.getRenderAtom(view); + + if (ra != null && ra.inRenderBin()) { + renderAtoms.remove(renderAtoms.indexOf(ra)); + removeARenderAtom(ra); + } + } + + visQuery = true; + visGAIsDirty = true; + } + + + void processMorphChanged(Object[] args, long refTime) { + + int component = ((Integer)args[1]).intValue(); + int i; + RenderAtom ra; + TextureBin tb; + EnvironmentSet e; + RenderAtom raNext; + if ((component & MorphRetained.APPEARANCE_CHANGED) != 0) { + GeometryAtom[] gaArr = (GeometryAtom[])args[4]; + if (gaArr.length > 0) { + if (!gaArr[0].source.appearanceOverrideEnable) { + for (i =0; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra == null || !ra.inRenderBin()) { + continue; + } + ra.app = ra.geometryAtom.source.appearance; + e = ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + } + else { + for (i =0; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra == null || !ra.inRenderBin()) + continue; + + // if its using the alternate appearance continue .. + if (ra.app == ra.geometryAtom.source.otherAppearance) + continue; + ra.app = ra.geometryAtom.source.appearance; + e = ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + } + } + } + else if ((component & MorphRetained.APPEARANCEOVERRIDE_CHANGED) != 0) { + AppearanceRetained app, saveApp = null; + Shape3DRetained saveShape = null; + GeometryAtom[] gaArr = (GeometryAtom[])args[4]; + Object[] retVal; + + for (i =0; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra == null || !ra.inRenderBin()) + continue; + // Once shape could have many geometryAtoms, add the + // mirrorShape as a user of an appearance only once + + if (saveShape != ra.geometryAtom.source) { + saveShape = ra.geometryAtom.source; + if (ra.geometryAtom.source.appearanceOverrideEnable) { + retVal =universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + saveShape.otherAppearance = (AppearanceRetained)retVal[1]; + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + if (app != null) { + app.sgApp.addAMirrorUser(saveShape); + } + } + else {// use the default + app = ra.geometryAtom.source.appearance; + } + } + else { + // If it were using the alternate appearance + // remove itself as the user + if (ra.app == saveShape.otherAppearance && + ra.app != null) { + ra.app.sgApp.removeAMirrorUser(saveShape); + } + app = ra.geometryAtom.source.appearance; + saveShape.otherAppearance = null; + } + saveApp = app; + } + else { + app = saveApp; + } + ra.app = app; + e = ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + } + + } + + + + /** + * This routine gets called whenever the position of the view platform + * has changed. + */ + void updateViewPlatform(ViewPlatformRetained vp, float radius) { + Transform3D trans = null; + ViewPlatform viewP = view.getViewPlatform(); + if (viewP != null && (ViewPlatformRetained)viewP.retained == vp) { + vpcToVworld = vp.getCurrentLocalToVworld(null); + vpcToVworldDirty = true; + synchronized(vp) { + vp.vprDirtyMask |= View.VPR_VIEWPLATFORM_DIRTY; + } + + // vp schedSphere is already set and transform in + // BehaviorStructure thread which is run before + // RenderBin using vp.updateActivationRadius() + vpSchedSphereInVworld = vp.schedSphere; + reEvaluateBg = true; + reEvaluateClip = true; + } + + } + + + + /** + * This routine removes the GeometryAtoms from RenderBin + */ + void processGeometryAtomsChanged(Object[] gaArr) { + int i; + RenderAtom ra; + + for (i = 0; i < gaArr.length; i++) { + ra = ((GeometryAtom)gaArr[i]).getRenderAtom(view); + if (ra != null && ra.inRenderBin()) { + renderAtoms.remove(renderAtoms.indexOf(ra)); + removeARenderAtom(ra); + } + } + } + + /** + * process Geometry changed, mark the display list + * in which renderMolecule is as dirty + */ + void processGeometryChanged(Object[] args) { + + Object[] gaList = (Object[]) args[0]; + + GeometryRetained g = (GeometryRetained)args[1]; + GeometryAtom ga; + + int i; + + for (i = 0; i < gaList.length; i++) { + ga = ((GeometryAtom)gaList[i]); + RenderAtom renderAtom = ga.getRenderAtom(view); + if (renderAtom == null || !renderAtom.inRenderBin()) { + continue; + } + + + // Add the renderMolecule to the dirty list so that + // display list will be recreated + int j = 0; + for ( j = 0; j < renderAtom.rListInfo.length; j++) { + if (g == renderAtom.rListInfo[j].geometry()) + break; + } + RenderAtomListInfo ra = (RenderAtomListInfo)renderAtom.rListInfo[j]; + if ((ra.groupType & RenderAtom.DLIST) != 0) + addDirtyRenderMolecule(ra.renderAtom.renderMolecule); + + if ((ra.groupType & RenderAtom.SEPARATE_DLIST_PER_RINFO) != 0) { + addDlistPerRinfo.add(ra); + } + + if ((ra.groupType & RenderAtom.SEPARATE_DLIST_PER_GEO) != 0) + addGeometryDlist(ra); + + // Raster send this message only for setImage() + if (g instanceof RasterRetained) { + Object[] objs = (Object[]) args[2]; + ImageComponentRetained oldImage = (ImageComponentRetained) objs[0]; + ImageComponentRetained newImage = (ImageComponentRetained) objs[1]; + + RasterRetained geo = (RasterRetained)ra.geometry(); + if (oldImage != null && oldImage.isByReference()) { + removeNodeComponent(oldImage); + } + if (newImage != null && newImage.isByReference()) { + addNodeComponent(newImage); + } + } + + } + + } + + void addTextureBin(TextureBin tb) { + textureBinList.add(tb); + } + + + void removeTextureBin(TextureBin tb) { + textureBinList.remove(tb); + } + + void addDirtyRenderMolecule(RenderMolecule rm) { + int i; + + if ((rm.onUpdateList & RenderMolecule.IN_DIRTY_RENDERMOLECULE_LIST) == 0) { + if (rm.onUpdateList == 0) { + objUpdateList.add(rm); + } + rm.onUpdateList |= RenderMolecule.IN_DIRTY_RENDERMOLECULE_LIST; + dirtyRenderMoleculeList.add(rm); + } + } + + + + void removeDirtyRenderMolecule(RenderMolecule rm) { + int i; + if ((rm.onUpdateList & RenderMolecule.IN_DIRTY_RENDERMOLECULE_LIST) != 0) { + rm.onUpdateList &= ~RenderMolecule.IN_DIRTY_RENDERMOLECULE_LIST; + if (rm.onUpdateList == 0) { + objUpdateList.remove(rm); + } + dirtyRenderMoleculeList.remove(dirtyRenderMoleculeList.indexOf(rm)); + } + } + + void updateDirtyDisplayLists(Canvas3D cv, + ArrayList rmList, ArrayList dlistPerRinfoList, + ArrayList raList, boolean useSharedCtx ) { + int size, i, bitMask; + long ctx, timeStamp; + + if (useSharedCtx) { + ctx = cv.screen.renderer.sharedCtx; + cv.makeCtxCurrent(ctx); + bitMask = cv.screen.renderer.rendererBit; + timeStamp = cv.screen.renderer.sharedCtxTimeStamp; + } else { + ctx = cv.ctx; + bitMask = cv.canvasBit; + timeStamp = cv.ctxTimeStamp; + } + + size = rmList.size(); + + if (size > 0) { + for (i = size-1; i >= 0; i--) { + RenderMolecule rm = (RenderMolecule)rmList.get(i); + rm.updateDisplayList(cv); + } + rmList.clear(); + } + + size = dlistPerRinfoList.size(); + + if (size > 0) { + for (i = size-1; i >= 0 ; i--) { + Object[] obj = (Object[])dlistPerRinfoList.get(i); + dlistRenderMethod.buildDlistPerRinfo((RenderAtomListInfo)obj[0], (RenderMolecule)obj[1], cv); + } + dlistPerRinfoList.clear(); + } + + size = raList.size(); + if (size > 0) { + RenderAtomListInfo ra; + GeometryArrayRetained geo; + + for (i = size-1; i >= 0; i--) { + ra = (RenderAtomListInfo)raList.get(i); + geo = (GeometryArrayRetained) ra.geometry(); + geo.resourceCreationMask &= ~bitMask; + } + + for (i = size-1; i >= 0; i--) { + ra = (RenderAtomListInfo)raList.get(i); + geo = (GeometryArrayRetained) ra.geometry(); + if ((geo.resourceCreationMask & bitMask) == 0) { + dlistRenderMethod.buildIndividualDisplayList(ra, cv, ctx); + geo.resourceCreationMask |= bitMask; + geo.setDlistTimeStamp(bitMask, timeStamp); + } + } + raList.clear(); + } + + if (useSharedCtx) { + cv.makeCtxCurrent(cv.ctx); + } + } + + void removeRenderMolecule(RenderMolecule rm) { + renderMoleculeFreelist.add(rm); + + if ((rm.primaryMoleculeType &(RenderMolecule.DLIST_MOLECULE|RenderMolecule.SEPARATE_DLIST_PER_RINFO_MOLECULE)) != 0) + renderMoleculeList.remove(rm); + } + + void updateAllRenderMolecule(Canvas3D cv) { + int i; + int size = renderMoleculeList.size(); + + if (size > 0) { + RenderMolecule[] rmArr = (RenderMolecule[]) + renderMoleculeList.toArray(false); + for (i = size-1 ; i >= 0; i--) { + rmArr[i].updateAllPrimaryDisplayLists(cv); + } + } + + size = sharedDList.size(); + if (size > 0) { + RenderAtomListInfo ra; + GeometryArrayRetained geo; + RenderAtomListInfo arr[] = + (RenderAtomListInfo []) sharedDList.toArray(false); + int bitMask = cv.canvasBit; + + // We need two passes to avoid extra buildDisplayList + // when geo are the same. The first pass clean the + // rendererBit. Note that we can't rely on + // resourceCreation since it is a force recreate. + + for (i = size-1; i >= 0; i--) { + geo = (GeometryArrayRetained) arr[i].geometry(); + geo.resourceCreationMask &= ~bitMask; + } + + for (i = size-1; i >= 0; i--) { + ra = arr[i]; + geo = (GeometryArrayRetained) ra.geometry(); + if ((geo.resourceCreationMask & bitMask) == 0) { + dlistRenderMethod.buildIndividualDisplayList(ra, cv, cv.ctx); + geo.resourceCreationMask |= bitMask; + geo.setDlistTimeStamp(bitMask, cv.ctxTimeStamp); + } + } + } + } + + /** + * This method is called to update all renderMolecule + * for a shared context of a renderer + */ + void updateAllRenderMolecule(Renderer rdr, Canvas3D cv) { + int i; + boolean setCtx = false; + GeometryArrayRetained geo; + int size = renderMoleculeList.size(); + + if (size > 0) { + RenderMolecule[] rmArr = (RenderMolecule[]) + renderMoleculeList.toArray(false); + + cv.makeCtxCurrent(rdr.sharedCtx); + setCtx = true; + for (i = size-1 ; i >= 0; i--) { + rmArr[i].updateAllPrimaryDisplayLists(cv); + } + } + + size = sharedDList.size(); + if (size > 0) { + RenderAtomListInfo arr[] = + (RenderAtomListInfo []) sharedDList.toArray(false); + RenderAtomListInfo ra; + + if (!setCtx) { + cv.makeCtxCurrent(rdr.sharedCtx); + setCtx = true; + } + + // We need two passes to avoid extra buildDisplayList + // when geo are the same. The first pass clean the + // rendererBit. + int bitMask = cv.screen.renderer.rendererBit; + long timeStamp = cv.screen.renderer.sharedCtxTimeStamp; + + for (i = size-1; i >= 0; i--) { + geo = (GeometryArrayRetained) arr[i].geometry(); + geo.resourceCreationMask &= ~bitMask; + } + + for (i = size-1; i >= 0; i--) { + ra = arr[i]; + geo = (GeometryArrayRetained) ra.geometry(); + if ((geo.resourceCreationMask & bitMask) == 0) { + dlistRenderMethod.buildIndividualDisplayList(ra, cv, + rdr.sharedCtx); + geo.resourceCreationMask |= bitMask; + geo.setDlistTimeStamp(bitMask, timeStamp); + } + } + } + if (setCtx) { + cv.makeCtxCurrent(cv.ctx); + } + } + + void processText3DTransformChanged(Object[] list, + Object[] transforms, + long referenceTime) { + int i, j, numShapes; + GeometryAtom ga; + RenderMolecule rm; + RenderAtom ra; + + if (transforms.length != 0) { + numShapes = list.length; + for (i=0; i<numShapes; i++) { + + ga = (GeometryAtom)list[i]; + ra = ga.getRenderAtom(view); + if (ra == null || !ra.inRenderBin()) { + continue; + } + /* + System.out.println("numShapes is " + numShapes + + " transforms.length is " + transforms.length); + */ + for (j=0; j<transforms.length; j++) { + + ga.lastLocalTransformArray[j] = (Transform3D)transforms[j]; + + + + + for(int k = 0; k < ra.rListInfo.length; k++) { + if (ra.rListInfo[k].localToVworld == null) { + ra.rListInfo[k].localToVworld = VirtualUniverse.mc.getTransform3D(null); + } + } + + if (ra.isOriented() && !ra.inDirtyOrientedRAs()) { + dirtyOrientedRAs.add(ra); + ra.dirtyMask |= RenderAtom.IN_DIRTY_ORIENTED_RAs; + } else if (!ra.onUpdateList()) { + ra.dirtyMask |= RenderAtom.ON_UPDATELIST; + objUpdateList.add(ra); + } + } + } + } + } + + + void processOrderedGroupRemoved(J3dMessage m) { + int i, n; + Object[] ogList = (Object[])m.args[0]; + Object[] ogChildIdList = (Object[])m.args[1]; + OrderedGroupRetained og; + int index; + int val; + OrderedBin ob; + OrderedChildInfo cinfo = null; + + /* + System.out.println("RB : processOrderedGroupRemoved message " + m); + System.out.println("RB : processOrderedGroupRemoved - ogList.length is " + + ogList.length); + System.out.println("RB : processOrderedGroupRemoved - obList " + + obList); + */ + for (n = 0; n < ogList.length; n++) { + og = (OrderedGroupRetained)ogList[n]; + index = ((Integer)ogChildIdList[n]).intValue(); + + ob = og.getOrderedBin(view.viewIndex); + // System.out.println("Removed, index = "+index+" ob = "+ob); + if (ob != null) { + // Add at the end of the childInfo, for remove we don't care about + // the childId + cinfo = new OrderedChildInfo(OrderedChildInfo.REMOVE, index, -1, null); + ob.addChildInfo(cinfo); + + if (!ob.onUpdateList) { + obList.add(ob); + ob.onUpdateList = true; + } + } + } + + } + + + void processOrderedGroupInserted(J3dMessage m) { + Object[] ogList = (Object[])m.args[0]; + Object[] ogChildIdList = (Object[])m.args[1]; + Object[] ogOrderedIdList = (Object[])m.args[2]; + + + OrderedGroupRetained og;; + int index; + int orderedId; + OrderedBin ob; + OrderedChildInfo cinfo; + // System.out.println("Inserted OG, index = "+index+" orderedId = "+orderedId+" og = "+og+" og.orderedBin = "+og.orderedBin); + // System.out.println("Inserted OG, orderedId = "+orderedId); + // System.out.println("Inserted, index = "+index+" oid = "+orderedId+" ob = "+ob); + + if(ogList == null) + return; + + for (int n = 0; n < ogList.length; n++) { + og = (OrderedGroupRetained)ogList[n]; + index = ((Integer)ogChildIdList[n]).intValue(); + orderedId = ((Integer)ogOrderedIdList[n]).intValue(); + ob = og.getOrderedBin(view.viewIndex); + cinfo = null; + + + if (ob != null) { + // Add at the end of the childInfo + cinfo = new OrderedChildInfo(OrderedChildInfo.ADD, index, orderedId, null); + ob.addChildInfo(cinfo); + + if (!ob.onUpdateList) { + obList.add(ob); + ob.onUpdateList = true; + } + } + } + } + + void processTransformChanged(long referenceTime) { + int i, j, k, numRenderMolecules, n; + Shape3DRetained s; + RenderMolecule rm; + RenderAtom ra; + Transform3D trans; + LightRetained[] lights; + FogRetained fog; + ModelClipRetained modelClip; + AppearanceRetained app; + Object[] list, nodesArr; + UnorderList arrList; + int size; + + targets = universe.transformStructure.getTargetList(); + + // process geometry atoms + arrList = targets.targetList[Targets.GEO_TARGETS]; + if (arrList != null) { + Object[] retVal; + size = arrList.size(); + nodesArr = arrList.toArray(false); + + //System.out.println("GS:"); + for (n = 0; n < size; n++) { + list = (Object[])nodesArr[n]; + + for (i=0; i<list.length; i++) { + + GeometryAtom ga = (GeometryAtom) list[i]; + //System.out.println(" ga " + ga); + ra = ga.getRenderAtom(view); + if (ra == null || !ra.inRenderBin()) + continue; + + rm = ra.renderMolecule; + + if (rm != null && rm.renderBin == this) { + + if (ga.source.inBackgroundGroup && (rm.onUpdateList & + RenderMolecule.UPDATE_BACKGROUND_TRANSFORM) == 0) { + if (rm.onUpdateList == 0) { + objUpdateList.add(rm); + } + rm.onUpdateList |= + RenderMolecule.UPDATE_BACKGROUND_TRANSFORM; + } + + lights = universe.renderingEnvironmentStructure. + getInfluencingLights(ra, view); + fog = universe.renderingEnvironmentStructure. + getInfluencingFog(ra, view); + modelClip = universe.renderingEnvironmentStructure. + getInfluencingModelClip(ra, view); + + if (ra.geometryAtom.source.appearanceOverrideEnable) { + retVal = universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + } + else { + app = ra.geometryAtom.source.appearance; + } + } + else { + app = ra.geometryAtom.source.appearance; + } + // TODO: Should we do a more extensive equals + // app? + if (ra.envSet.equals(ra, lights, fog, modelClip) && + app == ra.app) { + + if (ra.hasSeparateLocaleVwcBounds() + && !ra.onLocaleVwcBoundsUpdateList()) { + ra.dirtyMask |= ra.ON_LOCALE_VWC_BOUNDS_UPDATELIST; + raLocaleVwcBoundsUpdateList.add(ra); + } + + // If the locale are different and the xform has changed + // then we need to translate the rm's localToVworld by + // the locale differences + if (locale != ga.source.locale) { + if (rm.onUpdateList == 0) { + objUpdateList.add(rm); + } + rm.onUpdateList |= RenderMolecule.LOCALE_TRANSLATION; + + } + if ((rm.primaryMoleculeType & RenderMolecule.DLIST_MOLECULE) != 0) { + if (rm.onUpdateList == 0) { + objUpdateList.add(rm); + } + rm.onUpdateList |= RenderMolecule.BOUNDS_RECOMPUTE_UPDATE; + + } + // Note that the rm LOCALE Translation update should ocuur + // Before the ra is added to the object update list + // It is a Text3D Molecule + else if ((rm.primaryMoleculeType & RenderMolecule.TEXT3D_MOLECULE) != 0){ + + if (!ra.onUpdateList()) { + ra.dirtyMask |= RenderAtom.ON_UPDATELIST; + objUpdateList.add(ra); + } + } + if (ra.isOriented() && !ra.inDirtyOrientedRAs()) { + dirtyOrientedRAs.add(ra); + ra.dirtyMask |= RenderAtom.IN_DIRTY_ORIENTED_RAs; + + } + // If not opaque or in OG or is not a transparent bg geometry + // and transp sort mode is sort_geometry, then .. + if (!ra.renderMolecule.isOpaqueOrInOG && ra.geometryAtom.source.geometryBackground == null && transpSortMode == View.TRANSPARENCY_SORT_GEOMETRY && !ra.inDepthSortList()) { + // Do the updating of the centroid + // when the render is running + ra.geometryAtom.updateCentroid(); + // System.out.println("========> adding to the dirty list .., transpSortMode = "+transpSortMode); + dirtyDepthSortRenderAtom.add(ra); + ra.dirtyMask |= RenderAtom.IN_SORTED_POS_DIRTY_TRANSP_LIST; + numDirtyTinfo += ra.rListInfo.length; + + } + continue; + } + // If the appearance has changed .. + if (ra.app != app) { + if (ra.geometryAtom.source.appearanceOverrideEnable) { + // If it was using the alternate appearance, then .. + if (ra.app == ra.geometryAtom.source.otherAppearance) { + if (ra.app != null) { + // remove this mirror shape from the user list + ra.geometryAtom.source.otherAppearance.sgApp.removeAMirrorUser(ra.geometryAtom.source); + ra.geometryAtom.source.otherAppearance = null; + } + } + // if we are using the alternate app, add the mirror + // shape to the userlist + if (app != ra.geometryAtom.source.appearance) { + // Second check is needed to prevent, + // the mirror shape + // that has multiple ra's to be added more than + // once + if (app != null && app != ra.geometryAtom.source.otherAppearance) { + app.sgApp.addAMirrorUser(ra.geometryAtom.source); + ra.geometryAtom.source.otherAppearance = app; + } + } + + } + } + + + // Remove the renderAtom from the current + // renderMolecule and reinsert + getNewEnvironment(ra, lights, fog, modelClip, app); + } + } + } + } + + // process misc environment nodes + arrList = targets.targetList[Targets.ENV_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + for (n = 0; n < size; n++) { + list = (Object[])nodesArr[n]; + for (i=0; i<list.length; i++) { + + if (list[i] instanceof LightRetained && universe.renderingEnvironmentStructure.isLightScopedToThisView(list[i], view)) { + if (!changedLts.contains(list[i]) ) + changedLts.add(list[i]); + envDirty |= REEVALUATE_LIGHTS; // mark the canvas as dirty as well + } + else if (list[i] instanceof ModelClipRetained && universe.renderingEnvironmentStructure.isMclipScopedToThisView(list[i], view)) { + if (!changedModelClips.contains(list[i])) + changedModelClips.add(list[i]); + envDirty |= REEVALUATE_MCLIP; // mark the canvas as dirty as well + } + else if (list[i] instanceof FogRetained && universe.renderingEnvironmentStructure.isFogScopedToThisView(list[i], view)) { + if (!changedFogs.contains(list[i])) + changedFogs.add(list[i]); + envDirty |= REEVALUATE_FOG; // mark the canvas as dirty as well + } + else if (list[i] instanceof AlternateAppearanceRetained && universe.renderingEnvironmentStructure.isAltAppScopedToThisView(list[i], view)) { + altAppearanceDirty = true; + } + } + } + } + + // process ViewPlatform nodes + arrList = targets.targetList[Targets.VPF_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + for (n = 0; n < size; n++) { + list = (Object[])nodesArr[n]; + for (i=0; i<list.length; i++) { + float radius; + synchronized(list[i]) { + radius = (float)((ViewPlatformRetained)list[i]).sphere.radius; + } + updateViewPlatform((ViewPlatformRetained)list[i], radius); + } + } + } + + targets = null; + + blUsers = universe.transformStructure.getBlUsers(); + if (blUsers != null) { + size = blUsers.size(); + for (j = 0; j < size; j++) { + LeafRetained mLeaf = (LeafRetained)blUsers.get(j); + if (mLeaf instanceof LightRetained && universe.renderingEnvironmentStructure.isLightScopedToThisView(mLeaf, view)) { + envDirty |= REEVALUATE_LIGHTS; + } + else if (mLeaf instanceof FogRetained && universe.renderingEnvironmentStructure.isFogScopedToThisView(mLeaf, view)) { + envDirty |= REEVALUATE_FOG; + } + else if (mLeaf instanceof ModelClipRetained && universe.renderingEnvironmentStructure.isMclipScopedToThisView(mLeaf, view)) { + envDirty |= REEVALUATE_MCLIP; + } + else if (mLeaf instanceof AlternateAppearanceRetained && universe.renderingEnvironmentStructure.isAltAppScopedToThisView(mLeaf, view)) { + altAppearanceDirty = true; + } + } + blUsers = null; + } + + visQuery = true; + + } + + + + /** + * This processes a LIGHT change. + */ + void processLightChanged() { + int i, j, k, l, n; + LightRetained lt; + EnvironmentSet e; + Object[] args; + LightRetained[] mLts; + int component; + int lightSize = lightMessageList.size(); + + for (n = 0; n < lightSize; n++) { + J3dMessage msg = (J3dMessage)lightMessageList.get(n); + args = msg.args; + mLts = (LightRetained[])args[3]; + component = ((Integer)args[1]).intValue(); + lt = (LightRetained) args[0]; + + + if ((component &(LightRetained.SCOPE_CHANGED | + LightRetained.BOUNDS_CHANGED | + LightRetained.BOUNDINGLEAF_CHANGED)) != 0){ + envDirty |= REEVALUATE_LIGHTS; + component &= ~(LightRetained.SCOPE_CHANGED | + LightRetained.BOUNDS_CHANGED | + LightRetained.BOUNDINGLEAF_CHANGED); + } + // This is a light that is not a part of any + // environment set, first check if it is enabled + // if it is then reEvaluate all renderAtoms in the + // scene, otherwise do nothing + if (component != 0) { + if (lt.nodeType == LightRetained.AMBIENTLIGHT) { + UnorderList list; + EnvironmentSet envsets[]; + for (i = 0; i < mLts.length; i++) { + LightRetained lti = mLts[i]; + list = lti.environmentSets; + synchronized (list) { + int size = list.size(); + if (size > 0) { + envsets = (EnvironmentSet []) list.toArray(false); + for (j = 0; j < size; j++) { + e = envsets[j]; + e.canvasDirty |= Canvas3D.AMBIENTLIGHT_DIRTY; + if (!e.onUpdateList) { + objUpdateList.add(e); + e.onUpdateList = true; + } + } + } else { + if ((component & LightRetained.ENABLE_CHANGED) != 0) { + boolean value = lti.lightOn; + if (value) { + if (!changedLts.contains(lti)) + changedLts.add(lti); + envDirty |= REEVALUATE_LIGHTS; + } + } + } + } + } + } else { + for (i = 0; i < mLts.length; i++) { + LightRetained lti = mLts[i]; + if ((component & LightRetained.ENABLE_CHANGED) != 0) { + boolean value = ((Boolean)args[4]).booleanValue(); + if (value) { + if (!changedLts.contains(lti)) + changedLts.add(lti); + + envDirty |= REEVALUATE_LIGHTS; + } + } + UnorderList list = lti.environmentSets; + EnvironmentSet envsets[]; + synchronized (list) { + int size = list.size(); + int lsize; + if (size > 0) { + envsets = (EnvironmentSet []) list.toArray(false); + if ((component & LightRetained.ENABLE_CHANGED) != 0) { + boolean value = ((Boolean)args[4]).booleanValue(); + for (j = 0; j <size; j++) { + e = envsets[j]; + lsize = e.lights.size(); + for (k = 0; k < lsize; k++) { + if (e.lights.get(k) == lti) { + if (value == true) + e.enableMaskCache |= (1 << e.ltPos[k]); + else + e.enableMaskCache &= ~(1 << e.ltPos[k]); + break; + } + } + e.canvasDirty |= Canvas3D.LIGHTENABLES_DIRTY; + if (!e.onUpdateList) { + objUpdateList.add(e); + e.onUpdateList = true; + } + } + } else { + for (j = 0; j < size; j++) { + e = envsets[j]; + lsize = e.lights.size(); + for (k = 0; k < lsize; k++) { + if (e.lights.get(k) == lti) { + e.lightBin.canvasDirty |= Canvas3D.LIGHTBIN_DIRTY; + e.lightBin.lightDirtyMaskCache |= (1 << e.ltPos[k]); + if (!e.lightBin.onUpdateList) { + e.lightBin.onUpdateList = true; + objUpdateList.add(e.lightBin); + } + break; + } + } + } + } + } + } // end sync. + } + } + } + msg.decRefcount(); + } + + } + + void processGeometryAtom(GeometryAtom ga, long referenceTime) { + RenderAtom renderAtom; + RenderMolecule rm; + + // System.out.println("+"); + + + GeometryRetained geo = null; + for (int k = 0; (k < ga.geometryArray.length && geo == null); k++) { + geo = ga.geometryArray[k]; + } + if (geo == null) + return; + + + renderAtom = ga.getRenderAtom(view); + + if (renderAtom != null) { + renderAtom.lastVisibleTime = referenceTime; + } + + if (renderAtom == null || renderAtom.inRenderBin()) { + return; + } + + + // If the geometry is all null , don't insert + // Make sure that there is atleast one geo that is non-null + + if (renderAtom.geometryAtom.source.viewList != null) { + if (renderAtom.geometryAtom.source.viewList.contains(view)) { + // System.out.println("Inserting RenderAtom, ra = "+renderAtom); + // System.out.println("ga = "+renderAtom.geometryAtom+" renderAtom.geometryAtom.source.viewList = "+renderAtom.geometryAtom.source.viewList); + rm = insertRenderAtom(renderAtom); + } + } + // No view specific scpoing + else { + rm = insertRenderAtom(renderAtom); + } + + } + + + + void processBgGeometryAtoms(GeometryAtom[] nodes, long referenceTime) { + int i; + GeometryAtom ga; + RenderAtom renderAtom; + RenderMolecule rm; + RenderAtomListInfo ra; + GeometryRetained geo; + + for (i=0; i<nodes.length; i++) { + ga = nodes[i]; + + // Make sure that there is atleast one geo that is non-null + geo = null; + for (int k = 0; (k < ga.geometryArray.length && geo == null); k++) { + geo = ga.geometryArray[k]; + } + if (geo == null) + continue; + + + renderAtom = ga.getRenderAtom(view); + if (renderAtom == null) + return; + + renderAtom.lastVisibleTime = referenceTime; + if (renderAtom.inRenderBin()) { + continue; + } + + + // This means that the renderAtom was not visible in the last + // frame ,so , no contention with the renderer ... + rm = insertRenderAtom(renderAtom); + } + + } + + /** + * This method looks through the list of RenderAtoms to see if + * compaction is needed. + */ + void checkForCompaction() { + int i, numRas; + int numDead = 0; + int numAlive = 0; + RenderAtom ra; + + if (!VirtualUniverse.mc.doCompaction) { + return; + } + + numRas = renderAtoms.size(); + for (i=0; i<numRas; i++) { + ra = (RenderAtom)renderAtoms.get(i); + // If the renderatom has not been visible for "notVisibleCount" then + // add it to the deadlist + if (ra.lastVisibleTime < removeCutoffTime) { + numDead++; + } + + } + numAlive = numRas - numDead; + if (numAlive*2 < numDead) { + compact(); + } + } + + /** + * This sets the number of frames to render before changing the + * removeCutoffTime + */ + void setFrameCountCutoff(int cutoff) { + frameCountCutoff = cutoff; + } + + /** + * This method stores the timestamp of the frame frameCountCuttoff + * frames ago. It also does compaction if it is needed. + */ + void compact() { + RenderAtom ra; + + for (int i=0; i < renderAtoms.size();) { + ra = (RenderAtom)renderAtoms.get(i); + if (ra.lastVisibleTime < removeCutoffTime) { + renderAtoms.remove(i); + removeARenderAtom(ra); + continue; + } + i++; + } + + } + + void reEvaluateAlternateAppearance() { + AppearanceRetained app; + EnvironmentSet e; + Object[] retVal; + int sz = renderAtoms.size(); + + for (int n = 0; n < sz; n++) { + RenderAtom ra = (RenderAtom)renderAtoms.get(n); + if (!ra.inRenderBin() || !ra.geometryAtom.source.appearanceOverrideEnable) + continue; + + retVal = universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + } + else { + app = ra.geometryAtom.source.appearance; + } + + if (app == ra.app) + continue; + + if (ra.geometryAtom.source.otherAppearance != app) { + if (ra.geometryAtom.source.otherAppearance != null) { + ra.geometryAtom.source.otherAppearance.sgApp.removeAMirrorUser(ra.geometryAtom.source); + } + if (app != ra.geometryAtom.source.appearance) { + if (app != null) { + app.sgApp.addAMirrorUser(ra.geometryAtom.source); + } + ra.geometryAtom.source.otherAppearance = app; + } + else { + ra.geometryAtom.source.otherAppearance = null; + } + } + ra.app = app; + e = ra.envSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + + } + + void reEvaluateAllRenderAtoms(boolean altAppDirty) { + + int sz = renderAtoms.size(); + + for (int n = 0; n < sz; n++) { + LightRetained[] lights; + FogRetained newfog; + ModelClipRetained newModelClip; + AppearanceRetained app; + RenderAtom ra = (RenderAtom)renderAtoms.get(n); + Object[] retVal; + + if (!ra.inRenderBin()) + continue; + + lights = universe.renderingEnvironmentStructure.getInfluencingLights(ra, view); + newfog = universe.renderingEnvironmentStructure.getInfluencingFog(ra, view); + newModelClip = universe.renderingEnvironmentStructure.getInfluencingModelClip(ra, view); + + + if (altAppDirty) { + if (ra.geometryAtom.source.appearanceOverrideEnable) { + retVal = universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + } + else { + app = ra.geometryAtom.source.appearance; + } + + } + else { + app = ra.geometryAtom.source.appearance; + } + } + else { + app = ra.app; + } + + // If the lights/fog/model_clip of the render atom is the same + // as the old set of lights/fog/model_clip, then move on to the + // next renderAtom + // TODO: Should app test for equivalent? + if (ra.envSet.equals(ra, lights, newfog, newModelClip) && + app == ra.app) + continue; + + if (altAppDirty && ra.geometryAtom.source.appearanceOverrideEnable) { + if (app != ra.app) { + if (ra.geometryAtom.source.otherAppearance != app) { + if (ra.geometryAtom.source.otherAppearance != null) + ra.geometryAtom.source.otherAppearance.sgApp.removeAMirrorUser(ra.geometryAtom.source); + // If it is not the default appearance + if (app != ra.geometryAtom.source.appearance) { + if (app != null) { + app.sgApp.addAMirrorUser(ra.geometryAtom.source); + } + ra.geometryAtom.source.otherAppearance = app; + } + else { + ra.geometryAtom.source.otherAppearance = null; + } + } + } + } + getNewEnvironment(ra, lights, newfog, newModelClip, app); + + } + } + + + + void getNewEnvironment(RenderAtom ra, LightRetained[] lights, + FogRetained fog, ModelClipRetained modelClip, + AppearanceRetained app) { + + LightBin currentBin, lightBin; + EnvironmentSet currentEnvSet, newBin; + EnvironmentSet eNew = null; + AttributeBin attributeBin; + TextureBin textureBin; + RenderMolecule renderMolecule; + FogRetained newfog; + LightBin addBin; + OrderedCollection oc = null; + int i; + + // Remove this renderAtom from this render Molecule + ra.renderMolecule.removeRenderAtom(ra); + + eNew = null; + if (ra.geometryAtom.source.geometryBackground == null) { + if (ra.geometryAtom.source.orderedPath != null) { + oc = findOrderedCollection(ra.geometryAtom, false); + currentBin = oc.nextFrameLightBin; + addBin = oc.addLightBins; + } else { + addBin = addOpaqueBin; + currentBin= opaqueBin; + } + } else { + if (ra.geometryAtom.source.orderedPath != null) { + oc = findOrderedCollection(ra.geometryAtom, true); + currentBin = oc.nextFrameLightBin; + addBin = oc.addLightBins; + } else { + addBin = bgAddOpaqueBin; + currentBin= bgOpaqueBin; + } + } + lightBin = currentBin; + + while (currentBin != null && eNew == null) { + + // this test is always true for non-backgroundGeo bins + if (currentBin.geometryBackground == + ra.geometryAtom.source.geometryBackground) { + + currentEnvSet = currentBin.environmentSetList; + while (currentEnvSet != null) { + if (currentEnvSet.equals(ra, lights, fog, modelClip)) { + eNew = currentEnvSet; + break; + } + currentEnvSet = currentEnvSet.next; + } + // If envSet set is not found + // Check the "to-be-added" list of environmentSets for a match + if (eNew == null) { + int size = currentBin.insertEnvSet.size(); + for (i = 0; i < size; i++) { + newBin = (EnvironmentSet)currentBin.insertEnvSet.get(i); + if (newBin.equals(ra, lights, fog, modelClip)) { + eNew = newBin; + break; + } + } + } + } + currentBin = currentBin.next; + } + + // Now check the to-be added lightbins + if (eNew == null) { + currentBin = addBin; + while (currentBin != null) { + + // this test is always true for non-backgroundGeo bins + if (currentBin.geometryBackground == + ra.geometryAtom.source.geometryBackground) { + + // Check the "to-be-added" list of environmentSets for a match + int size = currentBin.insertEnvSet.size(); + for (i = 0; i < size; i++) { + newBin = (EnvironmentSet)currentBin.insertEnvSet.get(i); + if (newBin.equals(ra, lights, fog, modelClip)) { + eNew = newBin; + break; + } + } + } + currentBin = currentBin.next; + } + } + + + if (eNew == null) { + // Need a new one + currentEnvSet = getEnvironmentSet(ra, lights, fog, modelClip); + // Find a lightbin that envSet fits into + currentBin = lightBin; + while (currentBin != null) { + + // the first test is always true for non-backgroundGeo bins + if (currentBin.geometryBackground == + ra.geometryAtom.source.geometryBackground && + currentBin.willEnvironmentSetFit(currentEnvSet)) { + + // there may be new lights define which needs to + // call native updateLight(). + // When using existing lightBin we have to force + // reevaluate Light. + for (i=0; i < lights.length; i++) { + if (!changedLts.contains(lights[i])) + changedLts.add(lights[i]); + envDirty |= REEVALUATE_LIGHTS; + + } + break; + } + currentBin = currentBin.next; + } + + // Now check the to-be added lightbins + if (currentBin == null) { + currentBin = addBin; + while (currentBin != null) { + + // the first test is always true for non-backgroundGeo bins + if (currentBin.geometryBackground == + ra.geometryAtom.source.geometryBackground && + currentBin.willEnvironmentSetFit(currentEnvSet)) { + + // there may be new lights define which needs to + // call native updateLight(). + // When using existing lightBin we have to force + // reevaluate Light. + for (i=0; i < lights.length; i++) { + if (!changedLts.contains(lights[i])) + changedLts.add(lights[i]); + envDirty |= REEVALUATE_LIGHTS; + + } + break; + } + currentBin = currentBin.next; + } + } + + if (currentBin == null) { + // Need a new lightbin + currentBin = getLightBin(maxLights, + ra.geometryAtom.source.geometryBackground, false); + if (addBin != null) { + currentBin.next = addBin; + addBin.prev = currentBin; + } + if (ra.geometryAtom.source.orderedPath != null) { + if (!oc.onUpdateList) { + objUpdateList.add(oc); + oc.onUpdateList = true; + } + oc.addLightBins = currentBin; + } else { + if (ra.geometryAtom.source.geometryBackground == null) + addOpaqueBin = currentBin; + else + bgAddOpaqueBin = currentBin; + } + + } + eNew = currentEnvSet; + currentBin.addEnvironmentSet(eNew, this); + + } + ra.fog = fog; + ra.lights = lights; + ra.modelClip = modelClip; + ra.app = app; + reInsertAttributeBin(eNew, ra); + + } + void reInsertAttributeBin(EnvironmentSet e, RenderAtom ra) { + AttributeBin ab; + // Just go up to the environment and re-insert + ab = findAttributeBin(e, ra); + reInsertTextureBin(ab, ra); + } + + + void reInsertTextureBin(AttributeBin ab, RenderAtom ra) { + TextureBin tb; + + tb = findTextureBin(ab, ra); + reInsertRenderAtom(tb, ra); + } + + void reInsertRenderAtom(TextureBin tb, RenderAtom ra) { + RenderMolecule newRm; + // Just go up to the texture bin and re-insert + newRm = findRenderMolecule(tb, ra); + } + + void computeViewFrustumBBox(BoundingBox viewFrustumBBox) { + //Initial view frustumBBox BBox + viewFrustumBBox.lower.x = Float.POSITIVE_INFINITY; + viewFrustumBBox.lower.y = Float.POSITIVE_INFINITY; + viewFrustumBBox.lower.z = Float.POSITIVE_INFINITY; + viewFrustumBBox.upper.x = Float.NEGATIVE_INFINITY; + viewFrustumBBox.upper.y = Float.NEGATIVE_INFINITY; + viewFrustumBBox.upper.z = Float.NEGATIVE_INFINITY; + + Canvas3D canvases[] = view.getCanvases(); + for (int i=0; i< canvases.length; i++) { + Canvas3D canvas = canvases[i]; + + //Initial view frustumBBox BBox + canvasFrustumBBox.lower.x = Float.POSITIVE_INFINITY; + canvasFrustumBBox.lower.y = Float.POSITIVE_INFINITY; + canvasFrustumBBox.lower.z = Float.POSITIVE_INFINITY; + canvasFrustumBBox.upper.x = Float.NEGATIVE_INFINITY; + canvasFrustumBBox.upper.y = Float.NEGATIVE_INFINITY; + canvasFrustumBBox.upper.z = Float.NEGATIVE_INFINITY; + + canvas.updateViewCache(true, null, canvasFrustumBBox, false); + + if(viewFrustumBBox.lower.x > canvasFrustumBBox.lower.x) + viewFrustumBBox.lower.x = canvasFrustumBBox.lower.x; + if(viewFrustumBBox.lower.y > canvasFrustumBBox.lower.y) + viewFrustumBBox.lower.y = canvasFrustumBBox.lower.y; + if(viewFrustumBBox.lower.z > canvasFrustumBBox.lower.z) + viewFrustumBBox.lower.z = canvasFrustumBBox.lower.z; + + if(viewFrustumBBox.upper.x < canvasFrustumBBox.upper.x) + viewFrustumBBox.upper.x = canvasFrustumBBox.upper.x; + if(viewFrustumBBox.upper.y < canvasFrustumBBox.upper.y) + viewFrustumBBox.upper.y = canvasFrustumBBox.upper.y; + if(viewFrustumBBox.upper.z < canvasFrustumBBox.upper.z) + viewFrustumBBox.upper.z = canvasFrustumBBox.upper.z; + } + } + + + + /** + * This inserts a RenderAtom into the appropriate bin. + */ + RenderMolecule insertRenderAtom(RenderAtom ra) { + LightBin lightBin; + EnvironmentSet environmentSet; + AttributeBin attributeBin; + TextureBin textureBin; + RenderMolecule renderMolecule; + OrderedCollection oc; + AppearanceRetained app; + Object[] retVal; + GeometryAtom ga = ra.geometryAtom; + + // System.out.println("insertRenderAtom ga " + ra.geometryAtom); + // determine if a separate copy of localeVwcBounds is needed + // based on the locale info + + if (ra.localeVwcBounds == null) { + // Handle multiple locales + if (!locale.hiRes.equals(ga.source.locale.hiRes)) { + ga.source.locale.hiRes.difference(locale.hiRes, + localeTranslation); + ra.localeVwcBounds = new BoundingBox(); + ra.localeVwcBounds.translate(ga.source.vwcBounds, + localeTranslation); + ra.dirtyMask |= RenderAtom.HAS_SEPARATE_LOCALE_VWC_BOUNDS; + } + else { + ra.dirtyMask &= ~RenderAtom.HAS_SEPARATE_LOCALE_VWC_BOUNDS; + ra.localeVwcBounds = ga.source.vwcBounds; + } + } + + + // If the appearance is overrideable, then get the + // applicable appearance + if (ga.source.appearanceOverrideEnable) { + retVal = universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + // If its a valid alternate appaearance + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + ra.app = app; + if (ga.source.otherAppearance != app) { + if (ga.source.otherAppearance != null) + ga.source.otherAppearance.sgApp. + removeAMirrorUser(ga.source); + ga.source.otherAppearance = app; + if (app != null) + ra.app.sgApp.addAMirrorUser(ga.source); + } + } + else { + ra.app = ga.source.appearance; + + } + } else { + ra.app = ga.source.appearance; + } + // Call environment set, only after the appearance has been + // determined + environmentSet = findEnvironmentSet(ra); + attributeBin = findAttributeBin(environmentSet, ra); + textureBin = findTextureBin(attributeBin, ra); + renderMolecule = findRenderMolecule(textureBin, ra); + ra.setRenderBin(true); + renderAtoms.add(ra); + + if (ga.source instanceof OrientedShape3DRetained) { + // dirty initially + dirtyOrientedRAs.add(ra); + ra.dirtyMask |= RenderAtom.IN_DIRTY_ORIENTED_RAs; + ra.dirtyMask |= RenderAtom.IS_ORIENTED; + for(int k = 0; k < ra.rListInfo.length; k++) { + if (ra.rListInfo[k].localToVworld == null) { + ra.rListInfo[k].localToVworld = VirtualUniverse.mc.getTransform3D(null); + } + } + } + + if (renderMolecule.primaryMoleculeType == + RenderMolecule.TEXT3D_MOLECULE) { + if (!ra.onUpdateList()) { + ra.dirtyMask |= RenderAtom.ON_UPDATELIST; + objUpdateList.add(ra); + } + } + + // ra.needSeparateLocaleVwcBounds flag is determined in + // RenderMolecule.addRenderAtom based on the render method type. + // That's why the localeVwcBounds has to be reevaluated here again + // If after compaction being added in, then we just need to + // set the updated vwcBounds, there is no need to allocate + if (ra.needSeparateLocaleVwcBounds()) { + if (!ra.hasSeparateLocaleVwcBounds()) { + ra.dirtyMask |= RenderAtom.HAS_SEPARATE_LOCALE_VWC_BOUNDS; + ra.localeVwcBounds = new BoundingBox(ga.source.vwcBounds); + ra.dirtyMask |= ra.ON_LOCALE_VWC_BOUNDS_UPDATELIST; + raLocaleVwcBoundsUpdateList.add(ra); + } + else { + ra.localeVwcBounds.set(ga.source.vwcBounds); + ra.dirtyMask |= ra.ON_LOCALE_VWC_BOUNDS_UPDATELIST; + raLocaleVwcBoundsUpdateList.add(ra); + } + } + return (renderMolecule); + } + + OrderedCollection findOrderedCollection(GeometryAtom ga, + boolean doBackground) { + int i, n; + int oi; // an id which identifies a children of the orderedGroup + int ci; // child index of the ordered group + int index; + ArrayList list = null; + int val; + + OrderedGroupRetained og; + OrderedCollection oc = null; + ArrayList ocs; + ArrayList parentChildOrderedBins; + OrderedBin parentOrderedBin; + int parentOrderedChildId; + OrderedBin ob; + OrderedPathElement ope; + + // Since the table has been incremented, in response to OG addition, + // but the ordered collecyions has not been added yet, we need to + // check what the original index into the ordered collection + // should be + int adjustment; + + if (doBackground) { + parentChildOrderedBins = bgOrderedBins; + } else { + parentChildOrderedBins = orderedBins; + } + + parentOrderedBin = null; + parentOrderedChildId = -1; + + for (i=0; i<ga.source.orderedPath.pathElements.size(); i++) { + ope = (OrderedPathElement)ga.source.orderedPath.pathElements.get(i); + og = ope.orderedGroup; + oi = ope.childId.intValue(); + + ob = og.getOrderedBin(view.viewIndex); + if (ob == null) { + // create ordered bin tree + ob = new OrderedBin(og.childCount, og); + og.setOrderedBin(ob, view.viewIndex); + + index = -1; + for (n = 0; n < orderedBinsList.size(); n++) { + if (parentChildOrderedBins == orderedBinsList.get(n)) { + index = n; + break; + } + + } + if (index == -1) { + orderedBinsList.add(parentChildOrderedBins); + list = new ArrayList(5); + list.add(ob); + toBeAddedBinList.add(list); + } + else { + list = (ArrayList)toBeAddedBinList.get(index); + list.add(ob); + } + } + ocs = ob.orderedCollections; + OrderedChildInfo cinfo = ob.lastChildInfo; + boolean found = false; + // Check if a oc is already creates for this oi + // Start from the last child that was added and work backwards, + // in case the child + // was added and removed and then added back the same frame, we get the + // correct oc + while (cinfo != null && !found) { + if (cinfo.type == OrderedChildInfo.ADD) { + if (cinfo.orderedId == oi) { + oc = cinfo.value; + if (oc == null) { + oc = new OrderedCollection(); + cinfo.value = oc; + } + found = true; + } + } + cinfo = cinfo.prev; + } + // If we are in the update_view case then check the oi + // exists in the setOCForOI list .. + for (n = 0; n < ob.setOCForOI.size(); n++) { + val = ((Integer)ob.setOCForOI.get(n)).intValue(); + if (oi == val) { + oc = (OrderedCollection)ob.valueOfSetOCForOI.get(n); + found = true; + } + } + // The list is not going to be modified by any additions + // that have happened ... + // Then this child must exists from the previous frame, so + // get the location + if (!found) { + // The case below happens when there have been some insert + // ordered nodes, but update_view happens later and + // so the earlier insert ordered nodes are not + // seen by renderBin! + if (og.orderedChildIdTable == null || oi >= og.orderedChildIdTable.length) { + // Create a new category that adds Info based only on oi + // which will be added to the orderedBin after the + // idTable reflects the correct childId for the next frame + ob.setOCForOI.add(new Integer(oi)); + oc = new OrderedCollection(); + ob.valueOfSetOCForOI.add(oc); + if (!ob.onUpdateList) { + obList.add(ob); + ob.onUpdateList = true; + } + } + else { + ci = og.orderedChildIdTable[oi]; + + for (n = 0; n < ob.setOCForCI.size(); n++) { + val = ((Integer)ob.setOCForCI.get(n)).intValue(); + if (val == ci) { + + oc=(OrderedCollection)ob.valueOfSetOCForCI.get(n); + if (oc == null) { + oc = new OrderedCollection(); + ob.valueOfSetOCForCI.set(n, oc); + } + + break; + } + } + if (n == ob.setOCForCI.size()) { + oc = (OrderedCollection)ocs.get(ci); + if (oc == null) { + oc = new OrderedCollection(); + ob.setOCForCI.add(new Integer(ci)); + ob.valueOfSetOCForCI.add(oc); + if (!ob.onUpdateList) { + obList.add(ob); + ob.onUpdateList = true; + } + } + } + } + } + if (oc.nextFrameLightBin == null) { + oc.nextFrameLightBin = getLightBin(maxLights, + ga.source.geometryBackground, false); + oc.nextFrameLightBin.setOrderedInfo(oc); + + if (!oc.onUpdateList) { + objUpdateList.add(oc); + oc.onUpdateList = true; + } + } + + parentChildOrderedBins = oc.childOrderedBins; + parentOrderedBin = ob; + parentOrderedChildId = oi; + } + return (oc); + } + + void removeOrderedHeadLightBin(LightBin lightBin) { + int i, k; + int oi; // an id which identifies a children of the orderedGroup + int ci; // child index of the ordered group + ArrayList ocs; + OrderedCollection oc; + OrderedBin ob, savedOb; + int n, val; + + + + oc = lightBin.orderedCollection; + + oc.lightBin = lightBin.next; + oc.nextFrameLightBin = oc.lightBin; + + + + if (oc.lightBin != null) { + // Make this lightBin the head of the lightBin; + oc.lightBin.prev = null; + oc.lightBin.orderedCollection = oc; + } + + + } + + + /** + * This gets a new EnviornmentSet. It creates one if there are none + * on the freelist. + */ + EnvironmentSet getEnvironmentSet(RenderAtom ra, LightRetained[] lights, + FogRetained fog, ModelClipRetained modelClip) { + EnvironmentSet envSet; + + if (envSetFreelist.size() > 0) { + envSet = (EnvironmentSet)envSetFreelist.remove( + envSetFreelist.size()-1); + envSet.reset(ra, lights, fog, modelClip); + } else { + envSet = new EnvironmentSet(ra, lights, fog, modelClip, this); + } + return (envSet); + } + /** + * This finds or creates an AttributeBin for a given RenderAtom. + */ + AttributeBin findAttributeBin(EnvironmentSet envSet, RenderAtom ra) { + int i; + AttributeBin currentBin; + RenderingAttributesRetained renderingAttributes; + if (ra.app == null) { + renderingAttributes = null; + } else { + renderingAttributes = ra.app.renderingAttributes; + } + + + currentBin = envSet.attributeBinList; + while (currentBin != null) { + if (currentBin.equals(renderingAttributes, ra)) { + return(currentBin); + } + currentBin = currentBin.next; + } + + // Check the "to-be-added" list of attributeBins for a match + for (i = 0; i < envSet.addAttributeBins.size(); i++) { + currentBin = (AttributeBin)envSet.addAttributeBins.get(i); + if (currentBin.equals(renderingAttributes, ra)) { + return(currentBin); + } + } + + currentBin = getAttributeBin(ra.app, renderingAttributes); + envSet.addAttributeBin(currentBin, this); + return(currentBin); + } + + /** + * This finds or creates a TextureBin for a given RenderAtom. + */ + TextureBin findTextureBin(AttributeBin attributeBin, RenderAtom ra) { + + + int i; + TextureBin currentBin; + TextureRetained texture; + TextureUnitStateRetained texUnitState[]; + + RenderingAttributesRetained rAttrs = + (ra.geometryAtom.source.appearance == null)? null: + ra.geometryAtom.source.appearance.renderingAttributes; + + if (ra.app == null) { + texUnitState = null; + } else { + texUnitState = ra.app.texUnitState; + } + + currentBin = attributeBin.textureBinList; + while (currentBin != null) { + if (currentBin.equals(texUnitState, ra)) { + //System.out.println("1: Equal"); + return(currentBin); + } + currentBin = currentBin.next; + } + // Check the "to-be-added" list of TextureBins for a match + for (i = 0; i < attributeBin.addTBs.size(); i++) { + currentBin = (TextureBin)attributeBin.addTBs.get(i); + if (currentBin.equals(texUnitState, ra)) { + //System.out.println("2: Equal"); + return(currentBin); + } + } + // get a new texture bin for this texture unit state + currentBin = getTextureBin(texUnitState, ra.app); + attributeBin.addTextureBin(currentBin, this, ra); + return(currentBin); + } + + /** + * This finds or creates a RenderMolecule for a given RenderAtom. + */ + RenderMolecule findRenderMolecule(TextureBin textureBin, + RenderAtom ra) { + + RenderMolecule currentBin; + PolygonAttributesRetained polygonAttributes; + LineAttributesRetained lineAttributes; + PointAttributesRetained pointAttributes; + MaterialRetained material; + ColoringAttributesRetained coloringAttributes; + TransparencyAttributesRetained transparencyAttributes; + int i; + ArrayList list; + TextureUnitStateRetained texUnitState[]; + RenderingAttributesRetained renderingAttributes; + HashMap rmap = null, addmap = null; + + if (ra.app == null) { + polygonAttributes = null; + lineAttributes = null; + pointAttributes = null; + material = null; + coloringAttributes = null; + transparencyAttributes = null; + renderingAttributes = null; + texUnitState = null; + } else { + polygonAttributes = ra.app.polygonAttributes; + lineAttributes = ra.app.lineAttributes; + pointAttributes = ra.app.pointAttributes; + material = ra.app.material; + coloringAttributes = ra.app.coloringAttributes; + transparencyAttributes = ra.app.transparencyAttributes; + renderingAttributes = ra.app.renderingAttributes; + texUnitState = ra.app.texUnitState; + } + + // Get the renderMoleculelist for this xform + if (ra.isOpaque()) { + rmap = textureBin.opaqueRenderMoleculeMap; + addmap = textureBin.addOpaqueRMs; + } + else { + rmap = textureBin.transparentRenderMoleculeMap; + addmap = textureBin.addTransparentRMs; + } + currentBin = (RenderMolecule)rmap.get(ra.geometryAtom.source.localToVworld[0]); + + while (currentBin != null) { + if (currentBin.equals(ra, + polygonAttributes, lineAttributes, + pointAttributes, material, + coloringAttributes, + transparencyAttributes, + ra.geometryAtom.source.localToVworld[0])) { + + currentBin.addRenderAtom(ra, this); + ra.envSet = ra.renderMolecule.textureBin.attributeBin.environmentSet; + // If the locale has changed for an existing renderMolecule + // handle the RmlocaleToVworld + return(currentBin); + } + currentBin = currentBin.next; + } + // Check the "to-be-added" list of renderMolecules for a match + if ((list = (ArrayList)addmap.get(ra.geometryAtom.source.localToVworld[0])) != null) { + for (i = 0; i < list.size(); i++) { + currentBin = (RenderMolecule)list.get(i); + if (currentBin.equals(ra, + polygonAttributes, lineAttributes, + pointAttributes, material, + coloringAttributes, + transparencyAttributes, + ra.geometryAtom.source.localToVworld[0])) { + currentBin.addRenderAtom(ra, this); + return(currentBin); + } + } + } + + + currentBin = getRenderMolecule(ra.geometryAtom, + polygonAttributes, + lineAttributes, + pointAttributes, + material, + coloringAttributes, + transparencyAttributes, + renderingAttributes, + texUnitState, + ra.geometryAtom.source.localToVworld[0], + ra.geometryAtom.source.localToVworldIndex[0]); + textureBin.addRenderMolecule(currentBin, this); + currentBin.addRenderAtom(ra, this); + return(currentBin); + } + + + /** + * This gets a new AttributeBin. It creates one if there are none + * on the freelist. + */ + AttributeBin getAttributeBin(AppearanceRetained app, RenderingAttributesRetained ra) { + AttributeBin attrBin; + if (attrBinFreelist.size() > 0) { + attrBin = (AttributeBin)attrBinFreelist.remove( + attrBinFreelist.size()-1); + attrBin.reset(app, ra, this); + } else { + + + attrBin = new AttributeBin(app, ra, this); + } + return (attrBin); + } + + /** + * This gets a new LightBin. It creates one if there are none + * on the freelist. + */ + LightBin getLightBin(int maxLights, BackgroundRetained bg, boolean inOpaque) { + LightBin lightBin; + + if (lightBinFreelist.size() > 0) { + lightBin = (LightBin)lightBinFreelist.remove( + lightBinFreelist.size()-1); + lightBin.reset(inOpaque); + } else { + lightBin = new LightBin(maxLights, this, inOpaque); + } + lightBin.geometryBackground = bg; + return (lightBin); + } + + /** + * This gets a new TextureBin. It creates one if there are none + * on the freelist. + */ + TextureBin getTextureBin(TextureUnitStateRetained texUnitState[], + AppearanceRetained app) { + TextureBin textureBin; + + if (textureBinFreelist.size() > 0) { + textureBin = (TextureBin)textureBinFreelist.remove( + textureBinFreelist.size()-1); + textureBin.reset(texUnitState, app); + } else { + textureBin = new TextureBin(texUnitState, app, this); + } + + return (textureBin); + } + + /** + * This gets a new RenderMolecule. It creates one if there are none + * on the freelist. + */ + RenderMolecule getRenderMolecule(GeometryAtom ga, + PolygonAttributesRetained polya, + LineAttributesRetained linea, + PointAttributesRetained pointa, + MaterialRetained material, + ColoringAttributesRetained cola, + TransparencyAttributesRetained transa, + RenderingAttributesRetained ra, + TextureUnitStateRetained[] texUnits, + Transform3D[] transform, + int[] transformIndex) { + RenderMolecule renderMolecule; + + if (renderMoleculeFreelist.size() > 0) { + renderMolecule = (RenderMolecule)renderMoleculeFreelist.remove( + renderMoleculeFreelist.size()-1); + renderMolecule.reset(ga, polya, linea, pointa, material, + cola, transa, ra, texUnits, transform, transformIndex); + } else { + renderMolecule = new RenderMolecule(ga, polya, linea, pointa, + material, cola, transa, ra, + texUnits, + transform, transformIndex, this); + } + return (renderMolecule); + } + + + /** + * This finds or creates an EnviornmentSet for a given RenderAtom. + * This also deals with empty LightBin lists. + */ + EnvironmentSet findEnvironmentSet(RenderAtom ra) { + LightBin currentBin, lightBin ; + EnvironmentSet currentEnvSet, newBin; + int i; + LightBin addBin = null; + OrderedCollection oc = null; + + if (ra.geometryAtom.source.geometryBackground == null) { + if (ra.geometryAtom.source.orderedPath != null) { + oc = findOrderedCollection(ra.geometryAtom, false); + currentBin = oc.nextFrameLightBin; + addBin = oc.addLightBins; + } else { + currentBin = opaqueBin; + addBin = addOpaqueBin; + } + } else { + if (ra.geometryAtom.source.orderedPath != null) { + oc = findOrderedCollection(ra.geometryAtom, true); + currentBin = oc.nextFrameLightBin; + addBin = oc.addLightBins; + } else { + currentBin = bgOpaqueBin; + addBin = bgAddOpaqueBin; + + } + } + lightBin = currentBin; + + + ra.lights = universe.renderingEnvironmentStructure. + getInfluencingLights(ra, view); + ra.fog = universe.renderingEnvironmentStructure. + getInfluencingFog(ra, view); + ra.modelClip = universe.renderingEnvironmentStructure. + getInfluencingModelClip(ra, view); + + while (currentBin != null) { + // this test is always true for non-backgroundGeo bins + if (currentBin.geometryBackground == + ra.geometryAtom.source.geometryBackground) { + + currentEnvSet = currentBin.environmentSetList; + while (currentEnvSet != null) { + if (currentEnvSet.equals(ra, ra.lights, ra.fog, ra.modelClip)) { + return(currentEnvSet); + } + currentEnvSet = currentEnvSet.next; + } + // Check the "to-be-added" list of environmentSets for a match + for (i = 0; i < currentBin.insertEnvSet.size(); i++) { + newBin = (EnvironmentSet)currentBin.insertEnvSet.get(i); + if (newBin.equals(ra, ra.lights, ra.fog, ra.modelClip)) { + return(newBin); + } + } + } + currentBin = currentBin.next; + } + + // Now check the to-be added lightbins + currentBin = addBin; + while (currentBin != null) { + + // this following test is always true for non-backgroundGeo bins + if (currentBin.geometryBackground == + ra.geometryAtom.source.geometryBackground) { + + // Check the "to-be-added" list of environmentSets for a match + for (i = 0; i < currentBin.insertEnvSet.size(); i++) { + newBin = (EnvironmentSet)currentBin.insertEnvSet.get(i); + if (newBin.equals(ra, ra.lights, ra.fog, ra.modelClip)) { + return(newBin); + } + } + } + currentBin = currentBin.next; + } + + + // Need a new one + currentEnvSet = getEnvironmentSet(ra, ra.lights, ra.fog, ra.modelClip); + currentBin = lightBin; + + // Find a lightbin that envSet fits into + while (currentBin != null) { + + // the first test is always true for non-backgroundGeo bins + if (currentBin.geometryBackground == + ra.geometryAtom.source.geometryBackground && + currentBin.willEnvironmentSetFit(currentEnvSet)) { + break; + } + currentBin = currentBin.next; + } + + // Now check the to-be added lightbins + if (currentBin == null) { + currentBin = addBin; + while (currentBin != null) { + + // the first test is always true for non-backgroundGeo bins + if (currentBin.geometryBackground == + ra.geometryAtom.source.geometryBackground && + currentBin.willEnvironmentSetFit(currentEnvSet)) { + + break; + } + currentBin = currentBin.next; + } + } + + if (currentBin == null) { + // Need a new lightbin + currentBin = getLightBin(maxLights, + ra.geometryAtom.source.geometryBackground, false); + if (addBin != null) { + currentBin.next = addBin; + addBin.prev = currentBin; + } + if (ra.geometryAtom.source.orderedPath != null) { + if (!oc.onUpdateList) { + objUpdateList.add(oc); + oc.onUpdateList = true; + } + oc.addLightBins = currentBin; + } else { + if (ra.geometryAtom.source.geometryBackground == null) + addOpaqueBin = currentBin; + else + bgAddOpaqueBin = currentBin; + } + } + + currentBin.addEnvironmentSet(currentEnvSet, this); + return (currentEnvSet); + } + + void removeLightBin(LightBin lbin) { + if (lbin.prev == null) { // At the head of the list + + if (lbin.orderedCollection != null) + removeOrderedHeadLightBin(lbin); + + if (lbin.geometryBackground == null) { + if (opaqueBin == lbin) { + opaqueBin = lbin.next; + } + } else { + if (bgOpaqueBin == lbin) { + bgOpaqueBin = lbin.next; + } + } + if (lbin.next != null) { + lbin.next.prev = null; + } + } else { // In the middle or at the end. + lbin.prev.next = lbin.next; + if (lbin.next != null) { + lbin.next.prev = lbin.prev; + } + } + Canvas3D canvases[] = view.getCanvases(); + for (int i = 0; i < canvases.length; i++) { + // Mark the environmentSet cached by all the canvases as null + // to force to reEvaluate when it comes back from the freelist + // During LightBin::render(), we only check for the pointers not + // being the same, so we need to take care of the env set + // gotten from the freelist from one frame to another + canvases[i].lightBin = null; + } + lightBinFreelist.add(lbin); + lbin.prev = null; + lbin.next = null; + } + + void addDisplayListResourceFreeList(RenderMolecule rm) { + displayListResourceFreeList.add(rm.displayListIdObj); + } + + /** + * This renders the background scene graph. + */ + void renderBackground(Canvas3D cv) { + LightBin currentBin; + boolean savedDepthBufferWriteEnable; + + cv.setDepthBufferWriteEnableOverride(true); + savedDepthBufferWriteEnable = cv.depthBufferWriteEnable; + cv.setDepthBufferWriteEnable(false); + // render background opaque + currentBin = bgOpaqueBin; + while (currentBin != null) { + if (currentBin.geometryBackground == geometryBackground) + currentBin.render(cv); + currentBin = currentBin.next; + } + + // render background ordered + if (bgOrderedBins.size() > 0) { + renderOrderedBins(cv, bgOrderedBins, true); + } + + TransparentRenderingInfo tinfo = bgTransparentInfo; + while (tinfo != null) { + tinfo.render(cv); + tinfo = tinfo.next; + } + cv.setDepthBufferWriteEnableOverride(false); + cv.setDepthBufferWriteEnable(savedDepthBufferWriteEnable); + } + + /** + * This renders the opaque objects + */ + void renderOpaque(Canvas3D cv) { + LightBin currentBin = opaqueBin; + // System.out.println("========> renderOpaque"); + while (currentBin != null) { + // System.out.println("====> rendering Opaque Bin "); + currentBin.render(cv); + currentBin = currentBin.next; + } + + } + + /** + * This renders the transparent objects + */ + void renderTransparent(Canvas3D cv) { + boolean savedDepthBufferWriteEnable = true; + + TransparentRenderingInfo tinfo = transparentInfo; + if (tinfo != null) { + // System.out.println("====> rendering transparent Bin"); + + if (cv.view.depthBufferFreezeTransparent) { + cv.setDepthBufferWriteEnableOverride(true); + savedDepthBufferWriteEnable = cv.depthBufferWriteEnable; + cv.setDepthBufferWriteEnable(false); + } + + if (transpSortMode == View.TRANSPARENCY_SORT_NONE) { + while (tinfo != null) { + tinfo.render(cv); + tinfo = tinfo.next; + } + } + else if (transpSortMode == View.TRANSPARENCY_SORT_GEOMETRY) { + while (tinfo != null ) { + tinfo.sortRender(cv); + tinfo = tinfo.next; + } + } + if (cv.view.depthBufferFreezeTransparent) { + cv.setDepthBufferWriteEnableOverride(false); + cv.setDepthBufferWriteEnable(savedDepthBufferWriteEnable); + } + } + } + + /** + * This renders the ordered objects + */ + void renderOrdered(Canvas3D cv) { + // System.out.println("******renderOrdered, orderedBins.size() = "+orderedBins.size()+" RenderBin = "+this); + if (orderedBins.size() > 0) + renderOrderedBins(cv, orderedBins, false); + } + + void renderOrderedBins(Canvas3D cv, ArrayList bins, boolean doInfinite) { + int sz = bins.size(); + + for (int i=0; i <sz; i++) { + renderOrderedBin(cv, + (OrderedBin) bins.get(i), + doInfinite); + } + } + + void renderOrderedBin(Canvas3D cv, OrderedBin orderedBin, + boolean doInfinite) { + int i, index; + LightBin currentBin; + OrderedCollection oc; + boolean depthBufferEnable = true; + OrderedGroupRetained og = orderedBin.source; + boolean isDecal = (og instanceof DecalGroupRetained) && + ((cv.extensionsSupported & Canvas3D.STENCIL_BUFFER) != 0); + + int size = orderedBin.orderedCollections.size(); + + // System.out.println("RB : orderedBin.orderedCollections.size() " + size); + for (i=0; i<size; i++) { + if((og != null) && (og.childIndexOrder != null)) { + index = og.childIndexOrder[i]; + } + else { + index = i; + } + oc = (OrderedCollection)orderedBin.orderedCollections.get(index); + if (isDecal) { + if (index==0) { // first child + cv.setDepthBufferEnableOverride(true); + depthBufferEnable = cv.decal1stChildSetup(cv.ctx); + } else if (index==1) { // second child + // decalNthChildSetup will disable depth test + cv.decalNthChildSetup(cv.ctx); + } + } + if (oc != null) { + currentBin = oc.lightBin; + while (currentBin != null) { + if (!doInfinite || + currentBin.geometryBackground == geometryBackground) { + currentBin.render(cv); + } + currentBin = currentBin.next; + } + renderOrderedBins(cv, oc.childOrderedBins, doInfinite); + } + } + if (isDecal) { // reset + cv.decalReset(cv.ctx, depthBufferEnable); + cv.setDepthBufferEnableOverride(false); + } + } + + + /** + * Sets the new background color. + */ + void setBackground(BackgroundRetained back) { + + boolean cvDirty = false; + BackgroundRetained oldGeomBack = geometryBackground; + geometryBackground = null; + + if (back != null) { + background.initColor(back.color); + background.initImageScaleMode(back.imageScaleMode); + background.geometryBranch = back.geometryBranch; + if (background.geometryBranch != null) { + geometryBackground = back; + } + if (background.image != null) { + if (background.image.isByReference()) + removeNodeComponent(background.image); + } + if (back.image != null) { + // May need to optimize later + background.initImage((ImageComponent2D)back.image.source); + if (back.image.isByReference()) { + addNodeComponent(back.image); + } + } else { + background.initImage(null); + } + if (oldGeomBack == null) { + cvDirty = true; + } + } else { + background.initColor(black); + background.geometryBranch = null; + background.initImage(null); + if (oldGeomBack != null) { + cvDirty = true; + } + } + + // Need to reEvaluate View cache since doInfinite + // flag is changed in Renderer.updateViewCache() + Canvas3D canvases[] = view.getCanvases(); + for (int i=0; i< canvases.length; i++) { + Canvas3D canvas = canvases[i]; + if(cvDirty) + canvas.cvDirtyMask |= Canvas3D.BACKGROUND_DIRTY; + canvas.cvDirtyMask |= Canvas3D.BACKGROUND_IMAGE_DIRTY; + } + } + + + void reEvaluateFog(ArrayList fogs, boolean updateDirty, + boolean altAppDirty) { + EnvironmentSet e; + RenderAtom ra; + FogRetained newfog; + int i, j, n; + AppearanceRetained app; + Object[] retVal; + + int sz = renderAtoms.size(); + for (i = 0; i < sz; i++) { + ra = (RenderAtom)renderAtoms.get(i); + if (!ra.inRenderBin()) + continue; + + newfog = universe.renderingEnvironmentStructure.getInfluencingFog(ra, view); + // If the fog of the render atom is the same + // as the old fog, then move on to the + // next renderAtom + if (altAppDirty&&ra.geometryAtom.source.appearanceOverrideEnable) { + retVal = universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + } + else { + app = ra.geometryAtom.source.appearance; + } + + if (app == ra.app) { + if (ra.envSet.fog == newfog) + continue; + else { + getNewEnvironment(ra, ra.lights, newfog, ra.modelClip, ra.app); + } + } + else { + if (ra.geometryAtom.source.otherAppearance != app) { + if (ra.geometryAtom.source.otherAppearance != null) + ra.geometryAtom.source.otherAppearance.sgApp.removeAMirrorUser(ra.geometryAtom.source); + if (app != ra.geometryAtom.source.appearance) { + if (app != null) { + app.sgApp.addAMirrorUser(ra.geometryAtom.source); + } + ra.geometryAtom.source.otherAppearance = app; + } + else { + ra.geometryAtom.source.otherAppearance = null; + } + } + + if (ra.envSet.fog == newfog) { + ra.app = app; + e = ra.envSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + else { + getNewEnvironment(ra, ra.lights, newfog, ra.modelClip, + app); + } + } + } + else { + if (ra.envSet.fog == newfog) + continue; + getNewEnvironment(ra, ra.lights, newfog, ra.modelClip, ra.app); + }; + } + + // Only done for new fogs added to the system + if (updateDirty) + updateCanvasForDirtyFog(fogs); + } + + + void updateCanvasForDirtyFog(ArrayList fogs) { + int i, j; + EnvironmentSet e; + UnorderList list; + EnvironmentSet envsets[]; + int envsize; + int sz = fogs.size(); + + for (i = 0; i < sz; i++) { + FogRetained fog = (FogRetained)fogs.get(i); + list = fog.environmentSets; + synchronized (list) { + envsize = list.size(); + envsets = (EnvironmentSet []) list.toArray(false); + for (j = 0; j < envsize; j++) { + e = envsets[j]; + e.canvasDirty |= Canvas3D.FOG_DIRTY; + if (!e.onUpdateList) { + objUpdateList.add(e); + e.onUpdateList = true; + } + } + } + } + } + + void reEvaluateModelClip(ArrayList modelClips, + boolean updateDirty, + boolean altAppDirty) { + EnvironmentSet e; + RenderAtom ra; + ModelClipRetained newModelClip; + int i, j, n; + AppearanceRetained app; + Object[] retVal; + int sz = renderAtoms.size(); + for (i = 0; i < sz; i++) { + ra = (RenderAtom)renderAtoms.get(i); + if (!ra.inRenderBin()) + continue; + + newModelClip = + universe.renderingEnvironmentStructure.getInfluencingModelClip(ra, view); + + // If the model clip of the render atom is the same + // as the old model clip, then move on to the + // next renderAtom + if (altAppDirty&&ra.geometryAtom.source.appearanceOverrideEnable) { + retVal = universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + } + else { + app = ra.geometryAtom.source.appearance; + } + + if (app == ra.app) { + if (ra.envSet.modelClip == newModelClip) + continue; + else { + getNewEnvironment(ra, ra.lights, ra.fog, + ra.envSet.modelClip, ra.app); + } + } + else { + if (ra.geometryAtom.source.otherAppearance != app) { + if (ra.geometryAtom.source.otherAppearance != null) + ra.geometryAtom.source.otherAppearance.sgApp.removeAMirrorUser(ra.geometryAtom.source); + if (app != ra.geometryAtom.source.appearance) { + if (app != null) { + app.sgApp.addAMirrorUser(ra.geometryAtom.source); + } + ra.geometryAtom.source.otherAppearance = app; + } + else { + ra.geometryAtom.source.otherAppearance = null; + } + } + if (ra.envSet.modelClip == newModelClip) { + ra.app = app; + e = ra.envSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + else { + + getNewEnvironment(ra, ra.lights, ra.fog, newModelClip, + app); + } + } + } + else { + if (ra.envSet.modelClip == newModelClip) + continue; + getNewEnvironment(ra, ra.lights, ra.fog, newModelClip, ra.app); + }; + } + + // Only done for new modelClip added to the system + if (updateDirty) + updateCanvasForDirtyModelClip(modelClips); + } + + + void updateCanvasForDirtyModelClip(ArrayList modelClips) { + int i, j; + EnvironmentSet e; + int enableMCMaskCache = 0; + UnorderList list; + EnvironmentSet envsets[]; + int sz = modelClips.size(); + int envsize; + + for (i = 0; i < sz; i++) { + ModelClipRetained modelClip = (ModelClipRetained)modelClips.get(i); + + // evaluate the modelClip enable mask + enableMCMaskCache = 0; + for (j = 0; j < 6; j++) { + if (modelClip.enables[j]) + enableMCMaskCache |= 1 << j; + } + list = modelClip.environmentSets; + synchronized (list) { + envsize = list.size(); + envsets = (EnvironmentSet []) list.toArray(false); + for (j = 0; j < envsize; j++) { + e = envsets[j]; + e.canvasDirty |= Canvas3D.MODELCLIP_DIRTY; + e.enableMCMaskCache = enableMCMaskCache; + if (!e.onUpdateList) { + objUpdateList.add(e); + e.onUpdateList = true; + } + } + } + } + } + + void reEvaluateLights(boolean altAppDirty) { + EnvironmentSet e; + RenderAtom ra; + LightRetained[] lights; + int i, n; + AppearanceRetained app; + Object[] retVal; + int sz = renderAtoms.size(); + for (i = 0; i < sz; i++) { + ra = (RenderAtom)renderAtoms.get(i); + if (!ra.inRenderBin()) + continue; + + lights = universe.renderingEnvironmentStructure.getInfluencingLights(ra, view); + // If the lights of the render atom is the same + // as the old set of lights, then move on to the + // next renderAtom + if (altAppDirty&&ra.geometryAtom.source.appearanceOverrideEnable) { + retVal = universe.renderingEnvironmentStructure.getInfluencingAppearance(ra, view); + if (retVal[0] == Boolean.TRUE) { + app = (AppearanceRetained)retVal[1]; + } + else { + app = ra.geometryAtom.source.appearance; + } + + if (app == ra.app) { + if (ra.lights == lights || ra.envSet.equalLights(lights)) + continue; + else { + getNewEnvironment(ra, lights, ra.fog, ra.modelClip, ra.app); + } + } + else { + if (ra.geometryAtom.source.otherAppearance != app) { + if (ra.geometryAtom.source.otherAppearance != null) + ra.geometryAtom.source.otherAppearance.sgApp.removeAMirrorUser(ra.geometryAtom.source); + if (app != ra.geometryAtom.source.appearance) { + if (app != null) { + app.sgApp.addAMirrorUser(ra.geometryAtom.source); + } + ra.geometryAtom.source.otherAppearance = app; + } + else { + ra.geometryAtom.source.otherAppearance = null; + } + } + if (ra.lights == lights || ra.envSet.equalLights(lights)) { + ra.app = app; + e = ra.envSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + else { + getNewEnvironment(ra, lights, ra.fog, ra.modelClip, app); + } + } + } + else { + if (ra.lights == lights || ra.envSet.equalLights(lights)) + continue; + getNewEnvironment(ra, lights, ra.fog, ra.modelClip, ra.app); + } + } + // Only done for new lights added to the system + if (changedLts.size() > 0) + updateCanvasForDirtyLights(changedLts); + + } + + void updateCanvasForDirtyLights(ArrayList mLts) { + int n, i, j, lmask; + EnvironmentSet e; + UnorderList list; + EnvironmentSet envsets[]; + int sz = mLts.size(); + int envsize; + int ltsize; + + for (n = 0; n < sz; n++) { + LightRetained lt = (LightRetained)mLts.get(n); + list = lt.environmentSets; + synchronized (list) { + envsets = (EnvironmentSet []) list.toArray(false); + envsize = list.size(); + + if (lt.nodeType == LightRetained.AMBIENTLIGHT) { + for (i = 0; i < envsize; i++) { + e = envsets[i]; + e.canvasDirty |= Canvas3D.AMBIENTLIGHT_DIRTY; + if (!e.onUpdateList) { + objUpdateList.add(e); + e.onUpdateList = true; + } + } + } else { + for (i = 0; i < envsize; i++) { + e = envsets[i]; + lmask = 0; + ltsize = e.lights.size(); + for (j = 0; j < ltsize; j++) { + LightRetained curLt = (LightRetained)e.lights.get(j); + if (lt == curLt) { + lmask = (1 << e.ltPos[j]); + if (curLt.lightOn == true) { + e.enableMaskCache |= (1 << e.ltPos[j]); + } + else { + e.enableMaskCache &= (1 << e.ltPos[j]); + } + break; + } + } + e.canvasDirty |= Canvas3D.LIGHTENABLES_DIRTY; + if (!e.onUpdateList) { + objUpdateList.add(e); + e.onUpdateList = true; + } + if(e.lightBin != null) { + e.lightBin.canvasDirty |= Canvas3D.LIGHTBIN_DIRTY; + e.lightBin.lightDirtyMaskCache |= lmask; + if (!e.lightBin.onUpdateList) { + e.lightBin.onUpdateList = true; + objUpdateList.add(e.lightBin); + } + } + } + } + } + } + } + + void addTextureResourceFreeList(TextureRetained tex) { + toBeAddedTextureResourceFreeList.add(tex); + } + + + void reEvaluateEnv(ArrayList mLts, ArrayList fogs, + ArrayList modelClips, + boolean updateDirty, + boolean altAppDirty) { + + reEvaluateAllRenderAtoms(altAppDirty); + + // Done only for xform changes, not for bounding leaf change + if (updateDirty) { + // Update canvases for dirty lights and fog + if (mLts.size()> 0) + updateCanvasForDirtyLights(mLts); + if (fogs.size() > 0) + updateCanvasForDirtyFog(fogs); + if (modelClips.size() > 0) + updateCanvasForDirtyModelClip(modelClips); + } + + } + + void updateInfVworldToVpc() { + vworldToVpc.getRotation(infVworldToVpc); + } + + + // Lock all geometry before rendering into the any canvas + // in the case of display list, for each renderer, + // release after building the display list (which happens + // for the first canvas rendered) + void lockGeometry() { + GeometryRetained geo; + int i, size; + + + // Vertex array is locked for every time renderer is run + size = lockGeometryList.size(); + for (i = 0; i < size; i++) { + geo = (GeometryRetained) lockGeometryList.get(i); + geo.geomLock.getLock(); + + } + + // dlist is locked only when they are rebuilt + size = dlistLockList.size(); + for (i = 0; i < size ; i++) { + geo = (GeometryRetained) dlistLockList.get(i); + geo.geomLock.getLock(); + + } + + // Lock all the by reference image components + size = nodeComponentList.size(); + for (i = 0; i < size; i++) { + ImageComponentRetained nc = (ImageComponentRetained)nodeComponentList.get(i); + nc.geomLock.getLock(); + } + } + + // Release all geometry after rendering to the last canvas + void releaseGeometry() { + GeometryRetained geo; + int i, size; + + size = lockGeometryList.size(); + for (i = 0; i < size; i++) { + geo = (GeometryRetained) lockGeometryList.get(i); + geo.geomLock.unLock(); + } + + size = dlistLockList.size(); + for (i = 0; i < size; i++) { + geo = (GeometryRetained) dlistLockList.get(i); + geo.geomLock.unLock(); + } + // Clear the display list clear list + dlistLockList.clear(); + // Lock all the by reference image components + size = nodeComponentList.size(); + for (i = 0; i < size; i++) { + ImageComponentRetained nc = (ImageComponentRetained)nodeComponentList.get(i); + nc.geomLock.unLock(); + } + } + + void addGeometryToLockList(Object geo) { + // just add it to the list, if its a shared geometry + // it may be added more than once, thats OK since we + // now have nested locks! + lockGeometryList.add(geo); + } + + void removeGeometryFromLockList(Object geo) { + lockGeometryList.remove(geo); + + } + + + void addDirtyReferenceGeometry(Object geo) { + // just add it to the list, if its a shared geometry + // it may be added more than once, thats OK since we + // now have nested locks! + dirtyReferenceGeomList.add(geo); + } + + + void addNodeComponent(Object nc) { + newNodeComponentList.add(nc); + } + + void removeNodeComponent (Object nc) { + removeNodeComponentList.add(nc); + } + + void addDirtyNodeComponent(Object nc) { + dirtyNodeComponentList.add(nc); + } + + + void clearDirtyOrientedRAs() { + int i, nRAs; + Canvas3D cv; + RenderAtom ra; + OrientedShape3DRetained os; + nRAs = dirtyOrientedRAs.size(); + + // clear the dirtyMask + for(i=0; i<nRAs; i++) { + ra = (RenderAtom)dirtyOrientedRAs.get(i); + ra.dirtyMask &= ~RenderAtom.IN_DIRTY_ORIENTED_RAs; + } + dirtyOrientedRAs.clear(); + } + + // Called from MasterControl when viewCache changes or if there are + // dirtyOrientedShapes + void updateOrientedRAs() { + int i, nRAs; + Canvas3D cv; + RenderAtom ra; + OrientedShape3DRetained os; + + cv = (Canvas3D)view.getCanvas3D(0); + if (view.viewCache.vcDirtyMask != 0) { + nRAs = orientedRAs.size(); + + // Update ra's localToVworld given orientedTransform + // Mark Oriented shape as dirty, since multiple ra could point + // to the same OrientShape3D, compute the xform only once + for(i=0; i<nRAs; i++) { + ra = (RenderAtom)orientedRAs.get(i); + os = (OrientedShape3DRetained)ra.geometryAtom.source; + os.orientedTransformDirty = true; + } + // Update ra's localToVworld given orientedTransform + for(i=0; i<nRAs; i++) { + ra = (RenderAtom)orientedRAs.get(i); + os = (OrientedShape3DRetained)ra.geometryAtom.source; + if (os.orientedTransformDirty) { + os.updateOrientedTransform(cv, view.viewIndex); + os.orientedTransformDirty = false; + } + ra.updateOrientedTransform(); + } + } else { + nRAs = cachedDirtyOrientedRAs.size(); + // Update ra's localToVworld given orientedTransform + // Mark Oriented shape as dirty, since multiple ra could point + // to the same OrientShape3D, compute the xform only once + for(i=0; i<nRAs; i++) { + ra = (RenderAtom)cachedDirtyOrientedRAs.get(i); + os = (OrientedShape3DRetained)ra.geometryAtom.source; + os.orientedTransformDirty = true; + } + // Update ra's localToVworld given orientedTransform + for(i=0; i<nRAs; i++) { + ra = (RenderAtom)cachedDirtyOrientedRAs.get(i); + os = (OrientedShape3DRetained)ra.geometryAtom.source; + if (os.orientedTransformDirty) { + os.updateOrientedTransform(cv, view.viewIndex); + os.orientedTransformDirty = false; + + } + ra.updateOrientedTransform(); + } + } + cachedDirtyOrientedRAs.clear(); + + } + + + // This removes a renderAtom and also does the necessary changes + // for a orientShape3D + void removeARenderAtom(RenderAtom ra) { + // System.out.println("===> remove ga = "+ra.geometryAtom); + ra.setRenderBin(false); + ra.renderMolecule.removeRenderAtom(ra); + if (ra.inDirtyOrientedRAs()) { + dirtyOrientedRAs.remove(dirtyOrientedRAs.indexOf(ra)); + ra.dirtyMask &= ~RenderAtom.IN_DIRTY_ORIENTED_RAs; + } + // TODO: Should I remove the ra from dirtyDepthSortRenderAtom? + if (ra.inDepthSortList()) { + dirtyDepthSortRenderAtom.remove(dirtyDepthSortRenderAtom.indexOf(ra)); + ra.dirtyMask &= ~RenderAtom.IN_SORTED_POS_DIRTY_TRANSP_LIST; + numDirtyTinfo -= ra.rListInfo.length; + } + } + + void removeAllRenderAtoms() { + int i; + J3dMessage m; + RenderAtom ra; + RenderMolecule rm; + int sz = renderAtoms.size(); + + for (i = 0; i < sz; i++) { + ra = (RenderAtom) renderAtoms.get(i); + rm = ra.renderMolecule; + removeARenderAtom(ra); + rm.updateRemoveRenderAtoms(); + } + renderAtoms.clear(); + + clearAllUpdateObjectState(); + + // Clear the arrayList that are kept from one frame to another + renderMoleculeList.clear(); + sharedDList.clear(); + lockGeometryList.clear(); + // clear out this orderedBin's entry in the orderedGroup + for (i = 0; i < orderedBins.size(); i++) { + removeOrderedBin((OrderedBin) orderedBins.get(i)); + } + orderedBins.clear(); + bgOrderedBins.clear(); + nodeComponentList.clear(); + orientedRAs.clear(); + bhTreesArrList.clear(); + lightBinFreelist.clear(); + envSetFreelist.clear(); + attrBinFreelist.clear(); + textureBinFreelist.clear(); + renderMoleculeFreelist.clear(); + + // clean up any messages that are queued up, since they are + // irrelevant + // clearMessages(); + geometryBackground = null; + } + + void removeOrderedBin(OrderedBin ob) { + int i, k; + for (i = 0; i < ob.orderedCollections.size(); i++) { + OrderedCollection oc = (OrderedCollection) ob.orderedCollections.get(i); + if (oc == null) + continue; + + for (k = 0; k < oc.childOrderedBins.size(); k++) { + removeOrderedBin((OrderedBin)(oc.childOrderedBins.get(k))); + } + } + if (ob.source != null) { + ob.source.setOrderedBin(null, view.viewIndex); + ob.source = null; + } + } + + + void removeGeometryDlist(RenderAtomListInfo ra) { + removeDlist.add(ra); + } + + + void addGeometryDlist(RenderAtomListInfo ra) { + addDlist.add(ra); + } + + + void dumpBin(LightBin bin) { + LightBin obin = bin; + while (obin != null) { + System.out.println("LightBin = "+obin); + EnvironmentSet envSet = obin.environmentSetList; + while (envSet != null) { + System.out.println(" EnvSet = "+envSet); + AttributeBin abin = envSet.attributeBinList; + while (abin != null) { + System.out.println(" ABin = "+abin); + TextureBin tbin = abin.textureBinList; + while (tbin != null) { + System.out.println(" Tbin = "+tbin); + RenderMolecule rm = tbin.opaqueRMList; + System.out.println("===> Begin Dumping OpaqueBin"); + dumpRM(rm); + System.out.println("===> End Dumping OpaqueBin"); + rm = tbin.transparentRMList; + System.out.println("===> Begin Dumping transparentBin"); + dumpRM(rm); + System.out.println("===> End Dumping transparentBin"); + tbin = tbin.next; + } + abin = abin.next; + } + + envSet = envSet.next; + } + obin = obin.next; + } + + } + + void dumpRM(RenderMolecule rm) { + while (rm != null) { + System.out.println(" rm = "+rm+" numRAs = "+rm.numRenderAtoms); + System.out.println(" primaryRenderAtomList = "+ + rm.primaryRenderAtomList); + RenderAtomListInfo rinfo = rm.primaryRenderAtomList; + while (rinfo != null) { + System.out.println(" rinfo = "+rinfo); + System.out.println(" rinfo.ra.localeVwcBounds = " + + rinfo.renderAtom.localeVwcBounds); + System.out.println(" rinfo.ra.ga.so.vwcBounds = " + + rinfo.renderAtom.geometryAtom.source.vwcBounds); + System.out.println(" geometry = "+rinfo.geometry()); + + rinfo = rinfo.next; + } + System.out.println(" separateDlistRenderAtomList = "+ + rm.separateDlistRenderAtomList); + rinfo = rm.separateDlistRenderAtomList; + while (rinfo != null) { + System.out.println(" rinfo = "+rinfo); + System.out.println(" rinfo.ra.localeVwcBounds = " + + rinfo.renderAtom.localeVwcBounds); + System.out.println(" rinfo.ra.ga.so.vwcBounds = " + + rinfo.renderAtom.geometryAtom.source.vwcBounds); + System.out.println(" geometry = "+rinfo.geometry()); + rinfo = rinfo.next; + } + System.out.println(" vertexArrayRenderAtomList = "+ + rm.vertexArrayRenderAtomList); + if (rm.next == null) { + rm= rm.nextMap; + } + else { + rm = rm.next; + } + } + } + + void removeTransparentObject (Object obj) { + // System.out.println("&&&&&&&&&&&&removeTransparentObject r = "+obj); + if (obj instanceof TextureBin) { + TextureBin tb = (TextureBin) obj; + if (tb.attributeBin.environmentSet.lightBin.geometryBackground != null) { + TransparentRenderingInfo t = tb.parentTInfo; + + // Remove the element from the transparentInfo struct + if (t == bgTransparentInfo) { + bgTransparentInfo = bgTransparentInfo.next; + if (bgTransparentInfo != null) + bgTransparentInfo.prev = null; + } + else { + t.prev.next = t.next; + if (t.next != null) + t.next.prev = t.prev; + } + t.prev = null; + t.next = null; + transparentInfoFreeList.add(t); + tb.parentTInfo = null; + } + else { + int index = allTransparentObjects.indexOf(obj); + if (index == -1) { + // System.out.println("==> DEBUG1: Should never come here!"); + return; + } + allTransparentObjects.remove(index); + + TransparentRenderingInfo t = tb.parentTInfo; + + // Remove the element from the transparentInfo struct + if (t == transparentInfo) { + transparentInfo = transparentInfo.next; + if (transparentInfo != null) + transparentInfo.prev = null; + } + else { + t.prev.next = t.next; + if (t.next != null) + t.next.prev = t.prev; + } + t.prev = null; + t.next = null; + transparentInfoFreeList.add(t); + tb.parentTInfo = null; + } + + } + else { + int index = allTransparentObjects.indexOf(obj); + if (index == -1) { + // System.out.println("==> DEBUG2: Should never come here!"); + return; + } + + allTransparentObjects.remove(index); + RenderAtom r = (RenderAtom)obj; + for (int i = 0; i < r.parentTInfo.length; i++) { + // Remove the element from the transparentInfo struct + TransparentRenderingInfo t = r.parentTInfo[i]; + // This corresponds to null geometry + if (t == null) + continue; + + // Remove the element from the transparentInfo struct + if (t == transparentInfo) { + transparentInfo = transparentInfo.next; + if (transparentInfo != null) + transparentInfo.prev = null; + } + else { + t.prev.next = t.next; + if (t.next != null) + t.next.prev = t.prev; + } + t.prev = null; + t.next = null; + transparentInfoFreeList.add(t); + nElements--; + r.parentTInfo[i] = null; + } + } + + } + + void updateTransparentInfo(RenderAtom r) { + // System.out.println("===> update transparent Info"); + for (int i = 0; i < r.parentTInfo.length; i++) { + + if (r.parentTInfo[i] == null) + continue; + /* + r.parentTInfo[i].lightBin = r.envSet.lightBin; + r.parentTInfo[i].envSet = r.envSet; + r.parentTInfo[i].aBin = r.renderMolecule.textureBin.attributeBin; + */ + r.parentTInfo[i].rm = r.renderMolecule; + } + } + + void addTransparentObject (Object obj) { + // System.out.println("&&&&&&&&&&&&addTransparentObject r = "+obj); + if (obj instanceof TextureBin) { + TextureBin tb = (TextureBin) obj; + // Background geometry + if (tb.attributeBin.environmentSet.lightBin.geometryBackground != null) { + bgTransparentInfo = computeDirtyAcrossTransparentBins(tb, bgTransparentInfo); + } + else { + allTransparentObjects.add(obj); + transparentInfo = computeDirtyAcrossTransparentBins(tb, transparentInfo); + } + } + else { + allTransparentObjects.add(obj); + RenderAtom r = (RenderAtom)obj; + if (r.parentTInfo == null) { + r.parentTInfo = new TransparentRenderingInfo[r.rListInfo.length]; + } + computeDirtyAcrossTransparentBins(r); + // System.out.println("update Centroid 2, ga = "+r.geometryAtom); + r.geometryAtom.updateCentroid(); + dirtyDepthSortRenderAtom.add(r); + r.dirtyMask |= RenderAtom.IN_SORTED_POS_DIRTY_TRANSP_LIST; + numDirtyTinfo += r.rListInfo.length; + // System.out.println("transparentInfo ="+transparentInfo); + } + } + + TransparentRenderingInfo getTransparentInfo() { + TransparentRenderingInfo tinfo; + + if (transparentInfoFreeList.size() > 0) { + tinfo = (TransparentRenderingInfo)transparentInfoFreeList.remove(transparentInfoFreeList.size()-1); + } else { + tinfo = new TransparentRenderingInfo(); + } + return (tinfo); + + } + + TransparentRenderingInfo computeDirtyAcrossTransparentBins(TextureBin tb, TransparentRenderingInfo startinfo) { + TransparentRenderingInfo tinfo = getTransparentInfo(); + /* + tinfo.lightBin = tb.attributeBin.environmentSet.lightBin; + tinfo.envSet = tb.attributeBin.environmentSet; + tinfo.aBin = tb.attributeBin; + */ + tinfo.rm = tb.transparentRMList; + tb.parentTInfo = tinfo; + if (startinfo == null) { + startinfo = tinfo; + tinfo.prev = null; + tinfo.next = null; + + } + else { + tinfo.next = startinfo; + startinfo.prev = tinfo; + startinfo = tinfo; + } + return startinfo; + } + void computeDirtyAcrossTransparentBins(RenderAtom r) { + + for (int i = 0; i < r.parentTInfo.length; i++) { + if (r.rListInfo[i].geometry() == null) { + r.parentTInfo[i] = null; + continue; + } + nElements++; + TransparentRenderingInfo tinfo = getTransparentInfo(); + /* + tinfo.lightBin = r.envSet.lightBin; + tinfo.envSet = r.envSet; + tinfo.aBin = r.renderMolecule.textureBin.attributeBin; + */ + tinfo.rm = r.renderMolecule; + tinfo.rInfo = r.rListInfo[i]; + r.parentTInfo[i] = tinfo; + if (transparentInfo == null) { + transparentInfo = tinfo; + tinfo.prev = null; + tinfo.next = null; + } + else { + tinfo.prev = null; + tinfo.next = transparentInfo; + transparentInfo.prev = tinfo; + transparentInfo = tinfo; + } + + } + + } + + void processRenderAtomTransparentInfo(RenderAtomListInfo rinfo, ArrayList newList) { + while (rinfo != null) { + // If either the renderAtom has never been in transparent mode + // or if it was previously in that mode and now going back + // to that mode + if (rinfo.renderAtom.parentTInfo == null) { + rinfo.renderAtom.parentTInfo = new TransparentRenderingInfo[rinfo.renderAtom.rListInfo.length]; + computeDirtyAcrossTransparentBins(rinfo.renderAtom); + rinfo.renderAtom.geometryAtom.updateCentroid(); + newList.add(rinfo.renderAtom); + } + else { + GeometryRetained geo = null; + int i = 0; + while (geo == null && i < rinfo.renderAtom.rListInfo.length) { + geo = rinfo.renderAtom.rListInfo[i].geometry(); + i++; + } + // If there is atleast one non-null geometry in this renderAtom + if (geo != null) { + if (rinfo.renderAtom.parentTInfo[i-1] == null) { + computeDirtyAcrossTransparentBins(rinfo.renderAtom); + rinfo.renderAtom.geometryAtom.updateCentroid(); + newList.add(rinfo.renderAtom); + } + } + } + rinfo = rinfo.next; + + } + } + + void convertTransparentRenderingStruct(int oldMode, int newMode) { + int i, size; + ArrayList newList = new ArrayList(5); + RenderAtomListInfo rinfo; + // Reset the transparentInfo; + transparentInfo = null; + if (oldMode == View.TRANSPARENCY_SORT_NONE && newMode == View.TRANSPARENCY_SORT_GEOMETRY) { + size = allTransparentObjects.size(); + + for (i = 0; i < size; i++) { + TextureBin tb = (TextureBin)allTransparentObjects.get(i); + transparentInfoFreeList.add(tb.parentTInfo); + tb.parentTInfo = null; + RenderMolecule r = tb.transparentRMList; + // For each renderMolecule + while (r != null) { + // If this was a dlist molecule, since we will be rendering + // as separate dlist per rinfo, destroy the display list + if ((r.primaryMoleculeType &RenderMolecule.DLIST_MOLECULE) != 0) { + // System.out.println("&&&&&&&&& changing from dlist to dlist_per_rinfo"); + addDisplayListResourceFreeList(r); + removeDirtyRenderMolecule(r); + + r.vwcBounds.set(null); + r.displayListId = 0; + r.displayListIdObj = null; + // Change the group type for all the rlistInfo in the primaryList + rinfo = r.primaryRenderAtomList; + while (rinfo != null) { + rinfo.groupType = RenderAtom.SEPARATE_DLIST_PER_RINFO; + if (rinfo.renderAtom.dlistIds == null) { + rinfo.renderAtom.dlistIds = new int[rinfo.renderAtom.rListInfo.length]; + + for (int k = 0; k < rinfo.renderAtom.dlistIds.length; k++) { + rinfo.renderAtom.dlistIds[k] = -1; + } + } + if (rinfo.renderAtom.dlistIds[rinfo.index] == -1) { + rinfo.renderAtom.dlistIds[rinfo.index] = VirtualUniverse.mc.getDisplayListId().intValue(); + addDlistPerRinfo.add(rinfo); + } + rinfo = rinfo.next; + } + r.primaryMoleculeType = RenderMolecule.SEPARATE_DLIST_PER_RINFO_MOLECULE; + } + // Get all the renderAtoms in the list + processRenderAtomTransparentInfo(r.primaryRenderAtomList, newList); + processRenderAtomTransparentInfo(r.vertexArrayRenderAtomList, newList); + processRenderAtomTransparentInfo(r.separateDlistRenderAtomList, newList); + if (r.next == null) { + r = r.nextMap; + } + else { + r = r.next; + } + } + } + allTransparentObjects = newList; + } + else if (oldMode == View.TRANSPARENCY_SORT_GEOMETRY && newMode == View.TRANSPARENCY_SORT_NONE) { + // System.out.println("oldMode = TRANSPARENCY_SORT_GEOMETRY, newMode = TRANSPARENCY_SORT_NONE"); + size = allTransparentObjects.size(); + for (i = 0; i < size; i++) { + RenderAtom r= (RenderAtom)allTransparentObjects.get(i); + r.dirtyMask &= ~RenderAtom.IN_SORTED_POS_DIRTY_TRANSP_LIST; + for (int j = 0; j < r.parentTInfo.length; j++) { + // Corresponds to null geometry + if (r.parentTInfo[j] == null) + continue; + + transparentInfoFreeList.add(r.parentTInfo[j]); + r.parentTInfo[j] = null; + } + if (r.renderMolecule.textureBin.parentTInfo == null) { + transparentInfo = computeDirtyAcrossTransparentBins(r.renderMolecule.textureBin, transparentInfo); + newList.add(r.renderMolecule.textureBin); + } + } + allTransparentObjects = newList; + dirtyDepthSortRenderAtom.clear(); + numDirtyTinfo = 0; + } + } + + TransparentRenderingInfo mergeDepthSort(TransparentRenderingInfo oldList, TransparentRenderingInfo newList) { + TransparentRenderingInfo input1 = oldList , input2 = newList, nextN; + TransparentRenderingInfo lastInput1 = oldList; + double zval1, zval2; + // System.out.println("&&&&&&&&mergeDepthSort"); + /* + TransparentRenderingInfo t = oldList; + System.out.println(""); + while (t != null) { + System.out.println("==> old t = "+t); + t = t.next; + } + System.out.println(""); + t = newList; + while (t != null) { + System.out.println("==> new t = "+t); + t = t.next; + } + */ + while (input1 != null && input2 != null) { + lastInput1 = input1; + nextN = input2.next; + zval1 = input1.zVal; + zval2 = input2.zVal; + // Put the newList before the current one + if (zval2 > zval1) { + // System.out.println("===> path1"); + if (input1.prev == null) { + input1.prev = input2; + input2.prev = null; + input2.next = oldList; + oldList = input2; + } + else { + // System.out.println("===> path2"); + input2.prev = input1.prev; + input1.prev.next = input2; + input2.next = input1; + input1.prev = input2; + } + input2 = nextN; + } + else { + // System.out.println("===> path3"); + input1 = input1.next; + } + + + } + if (input1 == null && input2 != null) { + // add at then end + if (lastInput1 == null) { + oldList = input2; + input2.prev = null; + } + else { + lastInput1.next = input2; + input2.prev = lastInput1; + } + } + return oldList; + } + + void insertDepthSort(RenderAtom r) { + TransparentRenderingInfo tinfo = null; + // System.out.println("&&&&&&&&insertDepthSort"); + for (int i = 0; i < r.rListInfo.length; i++) { + if (r.parentTInfo[i] == null) + continue; + + if (transparentInfo == null) { + transparentInfo = r.parentTInfo[i]; + transparentInfo.prev = null; + transparentInfo.next = null; + } + else { + tinfo = transparentInfo; + TransparentRenderingInfo prevInfo = transparentInfo; + while (tinfo != null&& r.parentTInfo[i].zVal < tinfo.zVal) { + prevInfo = tinfo; + tinfo = tinfo.next; + } + r.parentTInfo[i].prev = prevInfo; + if (prevInfo.next != null) { + prevInfo.next.prev = r.parentTInfo[i]; + } + r.parentTInfo[i].next = prevInfo.next; + prevInfo.next = r.parentTInfo[i]; + + } + + } + } + + + + TransparentRenderingInfo collectDirtyTRInfo( TransparentRenderingInfo dirtyList, + RenderAtom r) { + + for (int i = 0; i < r.rListInfo.length; i++) { + TransparentRenderingInfo t = r.parentTInfo[i]; + if (t == null) + continue; + if (t == transparentInfo) { + transparentInfo = transparentInfo.next; + if (transparentInfo != null) + transparentInfo.prev = null; + } + else { + t.prev.next = t.next; + if (t.next != null) + t.next.prev = t.prev; + } + if (dirtyList == null) { + dirtyList = t; + t.prev = null; + t.next = null; + } else { + t.next = dirtyList; + t.prev = null; + dirtyList.prev = t; + dirtyList = t; + } + } + + return dirtyList; + } + + + TransparentRenderingInfo depthSortAll(TransparentRenderingInfo startinfo) { + TransparentRenderingInfo tinfo, previnfo, nextinfo; + double curZ; + // System.out.println("&&&&&&&&&&&depthSortAll"); + // Do insertion sort + /* + tinfo = startinfo; + while (tinfo != null) { + System.out.println("Soreted tinfo= "+tinfo+" tinfo.prev = "+tinfo.prev+" tinfo.next = "+tinfo.next); + tinfo = tinfo.next; + } + */ + tinfo = startinfo.next; + while (tinfo != null) { + // System.out.println("====> Doing tinfo = "+tinfo); + nextinfo = tinfo.next; + curZ = tinfo.zVal; + previnfo = tinfo.prev; + // Find the correct location for tinfo + + while (previnfo != null && previnfo.zVal < curZ) { + previnfo = previnfo.prev; + + } + if (tinfo.prev != previnfo) { + if (previnfo == null) { + if (tinfo.next != null) { + tinfo.next.prev = tinfo.prev; + } + // tinfo.prev is not null + tinfo.prev.next = tinfo.next; + tinfo.next = startinfo; + startinfo.prev = tinfo; + startinfo = tinfo; + tinfo.prev = null; + } + else { + if (tinfo.next != null) { + tinfo.next.prev = tinfo.prev; + } + if (tinfo.prev != null) { + tinfo.prev.next = tinfo.next; + } + tinfo.next = previnfo.next; + if (previnfo.next != null) + previnfo.next.prev = tinfo; + tinfo.prev = previnfo; + previnfo.next = tinfo; + // System.out.println("path2, tinfo.prev = "+tinfo.prev); + // System.out.println("path2, tinfo.next = "+tinfo.next); + } + + } + /* + TransparentRenderingInfo tmp = startinfo; + while (tmp != null) { + System.out.println("Soreted tmp= "+tmp+" tmp.prev = "+tmp.prev+" tmp.next = "+tmp.next); + tmp = tmp.next; + } + */ + + tinfo = nextinfo; + + } + /* + tinfo = startinfo; + double prevZ = 0.0; + while (tinfo != null) { + tinfo.render = false; + curZ = ((double[])distMap.get(tinfo.rInfo.renderAtom))[tinfo.rInfo.index]; + nextinfo = tinfo.next; + if (nextinfo != null) { + double nextZ = ((double[])distMap.get(nextinfo.rInfo.renderAtom))[tinfo.rInfo.index]; + if (Math.abs(curZ - nextZ) < 1.0e-6 && curZ < 400) { + tinfo.render = true; + } + } + + if (Math.abs(curZ - prevZ) < 1.0e-6 && curZ < 400) { + tinfo.render = true; + } + + prevZ = curZ; + tinfo = tinfo.next; + + } + tinfo = startinfo; + while (tinfo != null) { + System.out.println("z = "+((double[])distMap.get(tinfo.rInfo.renderAtom))[tinfo.rInfo.index]+" ga = "+tinfo.rInfo.renderAtom.geometryAtom); + tinfo = tinfo.next; + } + System.out.println("\n\n"); + tinfo = startinfo; + while (tinfo != null) { + if (tinfo.render) { + System.out.println("same z = "+((double[])distMap.get(tinfo.rInfo.renderAtom))[tinfo.rInfo.index]+" ga = "+tinfo.rInfo.renderAtom.geometryAtom); + GeometryAtom ga = tinfo.rInfo.renderAtom.geometryAtom; + System.out.println("ga.geometryArray.length = "+ga.geometryArray.length); + for (int k = 0; k < ga.geometryArray.length; k++) { + System.out.println("geometry "+k+" = "+ga.geometryArray[k]); + if (ga.geometryArray[k] != null) { + System.out.println(" vcount = "+((GeometryArrayRetained)ga.geometryArray[k]).getVertexCount()); + ((GeometryArrayRetained)ga.geometryArray[k]).printCoordinates(); + } + } + } + tinfo = tinfo.next; + } + */ + return startinfo; + } + + void processViewSpecificGroupChanged(J3dMessage m) { + int component = ((Integer)m.args[0]).intValue(); + Object[] objAry = (Object[])m.args[1]; + if (((component & ViewSpecificGroupRetained.ADD_VIEW) != 0) || + ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { + int i; + Object obj; + View v = (View)objAry[0]; + ArrayList leafList = (ArrayList)objAry[2]; + // View being added is this view + if (v == view) { + int size = leafList.size(); + for (i = 0; i < size; i++) { + obj = leafList.get(i); + if (obj instanceof LightRetained) { + envDirty |= REEVALUATE_LIGHTS; + if (!changedLts.contains(obj)) + changedLts.add(obj); + } + else if (obj instanceof FogRetained) { + envDirty |= REEVALUATE_FOG; + if (!changedFogs.contains(obj)) + changedFogs.add(obj); + } + else if (obj instanceof AlternateAppearanceRetained) { + altAppearanceDirty = true; + + } + else if (obj instanceof ModelClipRetained) { + envDirty |= REEVALUATE_MCLIP; + if (!changedModelClips.contains(obj)) + changedModelClips.add(obj); + } + else if (obj instanceof BackgroundRetained) { + reEvaluateBg = true; + } + + else if (obj instanceof ClipRetained) { + reEvaluateClip = true; + + } else if (obj instanceof GeometryAtom) { + visGAIsDirty = true; + visQuery = true; + } + } + + } + + } + if (((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0)|| + ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { + int i; + Object obj; + ArrayList leafList; + View v; + + if ((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0) { + v = (View)objAry[0]; + leafList = (ArrayList)objAry[2]; + } + else { + v = (View)objAry[4]; + leafList = (ArrayList)objAry[6]; + } + if (v == view) { + int size = leafList.size(); + for (i = 0; i < size; i++) { + obj = leafList.get(i); + if (obj instanceof GeometryAtom) { + RenderAtom ra = ((GeometryAtom)obj).getRenderAtom(view); + if (ra != null && ra.inRenderBin()) { + renderAtoms.remove(renderAtoms.indexOf(ra)); + removeARenderAtom(ra); + } + } + else if (obj instanceof LightRetained) { + envDirty |= REEVALUATE_LIGHTS; + } + else if (obj instanceof FogRetained) { + envDirty |= REEVALUATE_FOG; + } + else if (obj instanceof AlternateAppearanceRetained) { + altAppearanceDirty = true; + + } + else if (obj instanceof ModelClipRetained) { + envDirty |= REEVALUATE_MCLIP; + + } + else if (obj instanceof BackgroundRetained) { + reEvaluateBg = true; + } + + else if (obj instanceof ClipRetained) { + reEvaluateClip = true; + + } + } + } + } + + } + + void insertNodes(J3dMessage m) { + Object nodes[]; + ArrayList viewScopedNodes = (ArrayList)m.args[3]; + ArrayList scopedNodesViewList = (ArrayList)m.args[4]; + int i, j; + Object n; + nodes = (Object[])m.args[0]; + for (j = 0; j < nodes.length; j++) { + if (nodes[j] instanceof LightRetained) { + envDirty |= REEVALUATE_LIGHTS; + if (!changedLts.contains(nodes[j])) + changedLts.add(nodes[j]); + } else if (nodes[j] instanceof FogRetained) { + envDirty |= REEVALUATE_FOG; + if (!changedFogs.contains(nodes[j])) + changedFogs.add(nodes[j]); + } else if (nodes[j] instanceof BackgroundRetained) { + // If a new background is inserted, then + // re_evaluate to determine if this background + // should be used + reEvaluateBg = true; + } else if (nodes[j] instanceof ClipRetained) { + reEvaluateClip = true; + } else if (nodes[j] instanceof ModelClipRetained) { + envDirty |= REEVALUATE_MCLIP; + if (!changedModelClips.contains(nodes[j])) + changedModelClips.add(nodes[j]); + } else if (nodes[j] instanceof GeometryAtom) { + visGAIsDirty = true; + visQuery = true; + } else if (nodes[j] instanceof AlternateAppearanceRetained) { + altAppearanceDirty = true; + } + } + + // Handle ViewScoped Nodes + if (viewScopedNodes != null) { + int size = viewScopedNodes.size(); + int vlsize; + for (i = 0; i < size; i++) { + n = (NodeRetained)viewScopedNodes.get(i); + ArrayList vl = (ArrayList) scopedNodesViewList.get(i); + // If the node object is scoped to this view, then .. + if (vl.contains(view)) { + if (n instanceof LightRetained) { + envDirty |= REEVALUATE_LIGHTS; + if (!changedLts.contains(n)) + changedLts.add(n); + } else if (n instanceof FogRetained) { + envDirty |= REEVALUATE_FOG; + if (!changedFogs.contains(n)) + changedFogs.add(n); + } else if (n instanceof BackgroundRetained) { + // If a new background is inserted, then + // re_evaluate to determine if this backgrouns + // should be used + reEvaluateBg = true; + } else if (n instanceof ClipRetained) { + reEvaluateClip = true; + } else if (n instanceof ModelClipRetained) { + envDirty |= REEVALUATE_MCLIP; + if (!changedModelClips.contains(n)) + changedModelClips.add(n); + } else if (n instanceof AlternateAppearanceRetained) { + altAppearanceDirty = true; + } + } + // Note: geometryAtom is not part of viewScopedNodes + // Its a part of orginal nodes even if scoped + + } + } + } + + + void removeNodes(J3dMessage m) { + Object[] nodes; + ArrayList viewScopedNodes = (ArrayList)m.args[3]; + ArrayList scopedNodesViewList = (ArrayList)m.args[4]; + int i, j; + nodes = (Object[])m.args[0]; + for (int n = 0; n < nodes.length; n++) { + if (nodes[n] instanceof GeometryAtom) { + visGAIsDirty = true; + visQuery = true; + RenderAtom ra = + ((GeometryAtom)nodes[n]).getRenderAtom(view); + if (ra != null && ra.inRenderBin()) { + renderAtoms.remove(renderAtoms.indexOf(ra)); + removeARenderAtom(ra); + } + } + else if (nodes[n] instanceof AlternateAppearanceRetained) { + altAppearanceDirty = true; + } + else if (nodes[n] instanceof BackgroundRetained) { + reEvaluateBg = true; + } + else if (nodes[n] instanceof ClipRetained) { + reEvaluateClip = true; + } else if (nodes[n] instanceof ModelClipRetained) { + envDirty |= REEVALUATE_MCLIP; + } else if (nodes[n] instanceof FogRetained) { + envDirty |= REEVALUATE_FOG; + } + if (nodes[n] instanceof LightRetained) { + envDirty |= REEVALUATE_LIGHTS; + } + } + // Handle ViewScoped Nodes + if (viewScopedNodes != null) { + int size = viewScopedNodes.size(); + int vlsize; + Object node; + for (i = 0; i < size; i++) { + node = (NodeRetained)viewScopedNodes.get(i); + ArrayList vl = (ArrayList) scopedNodesViewList.get(i); + // If the node object is scoped to this view, then .. + if (vl.contains(view)) { + if (node instanceof LightRetained) { + envDirty |= REEVALUATE_LIGHTS; + } else if (node instanceof FogRetained) { + envDirty |= REEVALUATE_FOG; + } else if (node instanceof BackgroundRetained) { + // If a new background is inserted, then + // re_evaluate to determine if this backgrouns + // should be used + reEvaluateBg = true; + } else if (node instanceof ClipRetained) { + reEvaluateClip = true; + } else if (node instanceof ModelClipRetained) { + envDirty |= REEVALUATE_MCLIP; + + } else if (node instanceof AlternateAppearanceRetained) { + altAppearanceDirty = true; + } + // Note: geometryAtom is not part of viewScopedNodes + // Its a part of orginal nodes even if scoped + } + + } + } + } + + void cleanup() { + releaseAllDisplayListID(); + removeAllRenderAtoms(); + } + + + void freeAllDisplayListResources(Canvas3D cv) { + int i; + int size = renderMoleculeList.size(); + Renderer rdr = cv.screen.renderer; + + if (size > 0) { + RenderMolecule[] rmArr = (RenderMolecule[]) + renderMoleculeList.toArray(false); + + for (i = 0 ; i < size; i++) { + rmArr[i].releaseAllPrimaryDisplayListResources(cv); + } + } + + size = sharedDList.size(); + if (size > 0) { + RenderAtomListInfo arr[] = + (RenderAtomListInfo []) sharedDList.toArray(false); + + GeometryArrayRetained geo; + int mask = (cv.useSharedCtx ? rdr.rendererBit : cv.canvasBit); + + for (i = 0; i < size; i++) { + geo = (GeometryArrayRetained)arr[i].geometry(); + if (geo.dlistId > 0) { + if (!cv.useSharedCtx) { + cv.freeDisplayList(cv.ctx, geo.dlistId); + } + + if (geo.decrDlistRefCount(mask) == 0) { + geo.resourceCreationMask &= ~mask; + if (cv.useSharedCtx) { + cv.freeDisplayList(cv.ctx, geo.dlistId); + } + } + } + } + } + } + + + // put displayListID back to MC + void releaseAllDisplayListID() { + int i; + int size = renderMoleculeList.size(); + + if (size > 0) { + RenderMolecule[] rmArr = (RenderMolecule[]) + renderMoleculeList.toArray(false); + + for (i = 0 ; i < size; i++) { + rmArr[i].releaseAllPrimaryDisplayListID(); + } + } + + size = sharedDList.size(); + if (size > 0) { + RenderAtomListInfo arr[] = + (RenderAtomListInfo []) sharedDList.toArray(false); + GeometryArrayRetained geo; + + for (i = 0; i < size; i++) { + geo = (GeometryArrayRetained)arr[i].geometry(); + if (geo.resourceCreationMask == 0) { + geo.freeDlistId(); + } + } + } + } + + + /* + void handleFrequencyBitChanged(J3dMessage m) { + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + GeometryAtom[] gaArr = (GeometryAtom[])m.args[3]; + int i; + RenderAtom ra; + Boolean value = (Boolean)m.args[1]; + int mask = ((Integer)m.args[2]).intValue(); + + // Currently, we do not handle the case of + // going from frequent to infrequent + if (value == Boolean.FALSE) + return; + + ra = null; + // Get the first ra that is visible + for (i = 0; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + } + + if (ra == null) + return; + + int start = i; + // Check if the removed renderAtom is already in + // a separate bin - this is to handle the case + // when it has been changed to frequent, then to + // infrequent and then to frequent again! + if ((nc instanceof MaterialRetained && ra.renderMolecule.definingMaterial != ra.renderMolecule.material) || + (nc instanceof AppearanceRetained && ((ra.renderMolecule.soleUser & AppearanceRetained.MATERIAL) == 0))) { + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + } + } + else if ((nc instanceof PolygonAttributesRetained && ra.renderMolecule.definingPolygonAttributes != ra.renderMolecule.polygonAttributes) || + (nc instanceof AppearanceRetained && ((ra.renderMolecule.soleUser & AppearanceRetained.POLYGON) == 0))) { + // Check if the removed renderAtom is already in + // a separate bin - this is to handle the case + // when it has been changed to frequent, then to + // infrequent and then to frequent again! + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + } + } + else if ((nc instanceof PointAttributesRetained && ra.renderMolecule.definingPointAttributes != ra.renderMolecule.pointAttributes) || + (nc instanceof AppearanceRetained && ((ra.renderMolecule.soleUser & AppearanceRetained.POINT) == 0))) { + // Check if the removed renderAtom is already in + // a separate bin - this is to handle the case + // when it has been changed to frequent, then to + // infrequent and then to frequent again! + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + } + } + else if ((nc instanceof LineAttributesRetained && ra.renderMolecule.definingLineAttributes != ra.renderMolecule.lineAttributes) || + (nc instanceof AppearanceRetained && ((ra.renderMolecule.soleUser & AppearanceRetained.LINE) == 0))) { + // Check if the removed renderAtom is already in + // a separate bin - this is to handle the case + // when it has been changed to frequent, then to + // infrequent and then to frequent again! + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + } + } + else if((nc instanceof TransparencyAttributesRetained&& ra.renderMolecule.definingTransparency != ra.renderMolecule.transparency) || + (nc instanceof AppearanceRetained && ((ra.renderMolecule.soleUser & AppearanceRetained.TRANSPARENCY) == 0))) { + // Check if the removed renderAtom is already in + // a separate bin - this is to handle the case + // when it has been changed to frequent, then to + // infrequent and then to frequent again! + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + } + } + else if ((nc instanceof ColoringAttributesRetained&& ra.renderMolecule.definingColoringAttributes != ra.renderMolecule.coloringAttributes) || + (nc instanceof AppearanceRetained && ((ra.renderMolecule.soleUser & AppearanceRetained.COLOR) == 0))) { + // Check if the removed renderAtom is already in + // a separate bin - this is to handle the case + // when it has been changed to frequent, then to + // infrequent and then to frequent again! + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + TextureBin tb = ra.renderMolecule.textureBin; + ra.renderMolecule.removeRenderAtom(ra); + reInsertRenderAtom(tb, ra); + } + } + else if ((nc instanceof RenderingAttributesRetained && ra.renderMolecule.textureBin.attributeBin.definingRenderingAttributes != ra.renderMolecule.textureBin.attributeBin.renderingAttrs) || + (nc instanceof AppearanceRetained && ((ra.renderMolecule.textureBin.attributeBin.soleUser & AppearanceRetained.RENDER) == 0))) { + for (i = start; i < gaArr.length; i++) { + ra = gaArr[i].getRenderAtom(view); + if (ra== null || !ra.inRenderBin()) + continue; + + EnvironmentSet e= ra.renderMolecule.textureBin.attributeBin.environmentSet; + ra.renderMolecule.removeRenderAtom(ra); + reInsertAttributeBin(e, ra); + } + } + else { + + // TODO: handle texture + } + + + } + */ + +} diff --git a/src/classes/share/javax/media/j3d/RenderMethod.java b/src/classes/share/javax/media/j3d/RenderMethod.java new file mode 100644 index 0000000..2febc16 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderMethod.java @@ -0,0 +1,27 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The RenderMethod interface is used to create various ways to render + * different geometries. + */ + +interface RenderMethod { + + /** + * The actual rendering code for this RenderMethod + */ + abstract boolean render(RenderMolecule rm, Canvas3D cv, int pass, + RenderAtomListInfo ra, int dirtyBits); +} diff --git a/src/classes/share/javax/media/j3d/RenderMolecule.java b/src/classes/share/javax/media/j3d/RenderMolecule.java new file mode 100644 index 0000000..7812862 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderMolecule.java @@ -0,0 +1,3122 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.*; + +/** + * The RenderMolecule manages a collection of RenderAtoms. + */ + +class RenderMolecule extends IndexedObject implements ObjectUpdate, NodeComponentUpdate { + + + // different types of IndexedUnorderedSet that store RenderMolecule + static final int REMOVE_RENDER_ATOM_IN_RM_LIST = 0; + static final int RENDER_MOLECULE_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + /** + * Values for the geometryType field + */ + static final int POINT = 0x01; + static final int LINE = 0x02; + static final int SURFACE = 0x04; + static final int RASTER = 0x08; + static final int COMPRESSED = 0x10; + + static int RM_COMPONENTS = (AppearanceRetained.POLYGON | + AppearanceRetained.LINE | + AppearanceRetained.POINT | + AppearanceRetained.MATERIAL | + AppearanceRetained.TRANSPARENCY| + AppearanceRetained.COLOR); + + // TODO: use definingMaterial etc. instead of these + // when sole user is completely implement + PolygonAttributesRetained polygonAttributes = null; + LineAttributesRetained lineAttributes = null; + PointAttributesRetained pointAttributes = null; + MaterialRetained material = null; + ColoringAttributesRetained coloringAttributes = null; + TransparencyAttributesRetained transparency = null; + + // Use Object instead of AppearanceRetained class for + // state caching optimation memory performance + + boolean normalPresent = true; + + + // Equivalent bits + static final int POINTATTRS_DIRTY = AppearanceRetained.POINT; + static final int LINEATTRS_DIRTY = AppearanceRetained.LINE; + static final int POLYGONATTRS_DIRTY = AppearanceRetained.POLYGON; + static final int MATERIAL_DIRTY = AppearanceRetained.MATERIAL; + static final int TRANSPARENCY_DIRTY = AppearanceRetained.TRANSPARENCY; + static final int COLORINGATTRS_DIRTY = AppearanceRetained.COLOR; + + static final int ALL_DIRTY_BITS = POINTATTRS_DIRTY | LINEATTRS_DIRTY | POLYGONATTRS_DIRTY | MATERIAL_DIRTY | TRANSPARENCY_DIRTY | COLORINGATTRS_DIRTY; + + /** + * bit mask of all attr fields that are equivalent across + * renderMolecules + */ + int dirtyAttrsAcrossRms = ALL_DIRTY_BITS; + + + // Mask set to true is any of the component have changed + int soleUserCompDirty = 0; + + /** + * The PolygonAttributes for this RenderMolecule + */ + PolygonAttributesRetained definingPolygonAttributes = null; + + /** + * The LineAttributes for this RenderMolecule + */ + LineAttributesRetained definingLineAttributes = null; + + /** + * The PointAttributes for this RenderMolecule + */ + PointAttributesRetained definingPointAttributes = null; + + /** + * The TextureBin that this RenderMolecule resides + */ + TextureBin textureBin = null; + + /** + * The localToVworld for this RenderMolecule + */ + Transform3D[] localToVworld = null; + int[] localToVworldIndex = null; + + /** + * The Material reference for this RenderMolecule + */ + MaterialRetained definingMaterial = null; + + + /** + * The ColoringAttribute reference for this RenderMolecule + */ + ColoringAttributesRetained definingColoringAttributes = null; + + + /** + * The Transparency reference for this RenderMolecule + */ + TransparencyAttributesRetained definingTransparency = null; + + /** + * Transform3D - point to the right one based on bg or not + */ + Transform3D[] trans = null; + + + /** + * specify whether scale is nonuniform + */ + boolean isNonUniformScale = false; + + /** + * number of renderAtoms to be rendered in this RenderMolecule + */ + int numRenderAtoms = 0; + + /** + * number of render atoms, used during the renderBin update time + */ + int numEditingRenderAtoms = 0; + + RenderAtom addRAs = null; + RenderAtom removeRAs = null; + + /** + * The cached ColoringAttributes color value. It is + * 1.0, 1.0, 1.0 if there is no ColoringAttributes. + */ + float red = 1.0f; + float green = 1.0f; + float blue = 1.0f; + + + /** + * Cached diffuse color value + */ + float dRed = 1.0f; + float dGreen = 1.0f; + float dBlue = 1.0f; + + + + /** + * The cached TransparencyAttributes transparency value. It is + * 0.0 if there is no TransparencyAttributes. + */ + float alpha = 0.0f; + + /** + * The geometry type for this RenderMolecule + */ + int geometryType = -1; + + /** + * A boolean indicating whether or not lighting should be on. + */ + boolean enableLighting = false; + + /** + * A boolean indicating whether or not this molecule rendered Text3D + */ + + int primaryMoleculeType = 0; + static int COMPRESSED_MOLECULE = 0x1; + static int TEXT3D_MOLECULE = 0x2; + static int DLIST_MOLECULE = 0x4; + static int RASTER_MOLECULE = 0x8; + static int ORIENTEDSHAPE3D_MOLECULE = 0x10; + static int SEPARATE_DLIST_PER_RINFO_MOLECULE = 0x20; + + + /** + * Cached values for polygonMode, line antialiasing, and point antialiasing + */ + int polygonMode = PolygonAttributes.POLYGON_FILL; + boolean lineAA = false; + boolean pointAA = false; + + /** + * The vertex format for this RenderMolecule. Only looked + * at for GeometryArray and CompressedGeometry objects. + */ + int vertexFormat = -1; + + /** + * The texCoordSetMap length for this RenderMolecule. + */ + int texCoordSetMapLen = 0; + + /** + * The primary renderMethod object for this RenderMolecule + * this is either a Text3D, display list, or compressed geometry renderer. + */ + RenderMethod primaryRenderMethod = null; + + /** + * The secondary renderMethod object for this RenderMolecule + * this is used for geometry that is shared + */ + RenderMethod secondaryRenderMethod = null; + + /** + * The RenderBino for this molecule + */ + RenderBin renderBin = null; + + /** + * The references to the next and previous RenderMolecule in the + * list. + */ + RenderMolecule next = null; + RenderMolecule prev = null; + + + /** + * The list of RenderAtoms in this RenderMolecule that are not using + * vertex arrays. + */ + RenderAtomListInfo primaryRenderAtomList = null; + + + /** + * The list of RenderAtoms in this RenderMolecule that are using + * separte dlist . + */ + RenderAtomListInfo separateDlistRenderAtomList = null; + + + /** + * The list of RenderAtoms in this RenderMolecule that are using vertex + * arrays. + */ + RenderAtomListInfo vertexArrayRenderAtomList = null; + + /** + * This BoundingBox is used for View Frustum culling on the primary + * list + */ + BoundingBox vwcBounds = null; + + + /** + * If this is end of the linked list for this xform, then + * this field is non-null, if there is a map after this + */ + RenderMolecule nextMap = null; + RenderMolecule prevMap = null; + + /** + * If the any of the node component of the appearance in RM will be changed + * frequently, then confine it to a separate bin + */ + boolean soleUser = false; + Object appHandle = null; + + + VertexArrayRenderMethod cachedVertexArrayRenderMethod = + (VertexArrayRenderMethod) + VirtualUniverse.mc.getVertexArrayRenderMethod(); + + // In D3D separate Quad/Triangle Geometry with others in RenderMolecule + // Since we need to dynamically switch whether to use DisplayList + // or not in render() as a group. + boolean isQuadGeometryArray = false; + boolean isTriGeometryArray = false; + + // display list id, valid id starts from 1 + int displayListId = 0; + Integer displayListIdObj = null; + + int onUpdateList = 0; + static int NEW_RENDERATOMS_UPDATE = 0x1; + static int BOUNDS_RECOMPUTE_UPDATE = 0x2; + static int LOCALE_TRANSLATION = 0x4; + static int UPDATE_BACKGROUND_TRANSFORM = 0x8; + static int IN_DIRTY_RENDERMOLECULE_LIST = 0x10; + static int LOCALE_CHANGED = 0x20; + static int ON_UPDATE_CHECK_LIST = 0x40; + + + // background geometry rendering + boolean doInfinite; + Transform3D[] infLocalToVworld; + + // Whether alpha is used in this renderMolecule + boolean useAlpha = false; + + // Support for multiple locales + Locale locale = null; + + // Transform when locale is different from the view's locale + Transform3D[] localeLocalToVworld = null; + + // Vector used for locale translation + Vector3d localeTranslation = null; + + boolean primaryChanged = false; + + boolean isOpaqueOrInOG = true; + boolean inOrderedGroup = false; + + + // closest switch parent + SwitchRetained closestSwitchParent = null; + + // the child index from the closest switch parent + int closestSwitchIndex = -1; + + + RenderMolecule(GeometryAtom ga, + PolygonAttributesRetained polygonAttributes, + LineAttributesRetained lineAttributes, + PointAttributesRetained pointAttributes, + MaterialRetained material, + ColoringAttributesRetained coloringAttributes, + TransparencyAttributesRetained transparency, + RenderingAttributesRetained renderAttrs, + TextureUnitStateRetained[] texUnits, + Transform3D[] transform, int[] transformIndex, + RenderBin rb) { + renderBin = rb; + IndexedUnorderSet.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + + reset(ga, polygonAttributes, lineAttributes, pointAttributes, + material, coloringAttributes, transparency, renderAttrs, + texUnits, transform, + transformIndex); + } + + void reset(GeometryAtom ga, + PolygonAttributesRetained polygonAttributes, + LineAttributesRetained lineAttributes, + PointAttributesRetained pointAttributes, + MaterialRetained material, + ColoringAttributesRetained coloringAttributes, + TransparencyAttributesRetained transparency, + RenderingAttributesRetained renderAttrs, + TextureUnitStateRetained[] texUnits, + Transform3D[] transform, int[] transformIndex) { + primaryMoleculeType = 0; + numRenderAtoms = 0; + numEditingRenderAtoms = 0; + onUpdateList = 0; + dirtyAttrsAcrossRms = ALL_DIRTY_BITS; + primaryRenderMethod = null; + isNonUniformScale = false; + primaryChanged = false; + this.material = material; + this.polygonAttributes = polygonAttributes; + this.lineAttributes = lineAttributes; + this.pointAttributes = pointAttributes; + this.coloringAttributes = coloringAttributes; + this.transparency = transparency; + + closestSwitchParent = ga.source.closestSwitchParent; + closestSwitchIndex = ga.source.closestSwitchIndex; + + int i1; + // Find the first non-null geometey + GeometryRetained geo = null; + int k = 0; + isOpaqueOrInOG = true; + inOrderedGroup = false; + while (geo == null && (k < ga.geometryArray.length)) { + geo = ga.geometryArray[k]; + k++; + } + + if (ga.source.appearance != null) { + soleUser = ((ga.source.appearance.changedFrequent & RM_COMPONENTS) != 0); + } + else { + soleUser = false; + } + // Set the appearance only for soleUser case + if (soleUser) + appHandle = ga.source.appearance; + else + appHandle = (Object)this; + + // If its of type GeometryArrayRetained + if (ga.geoType <= GeometryRetained.GEO_TYPE_GEOMETRYARRAY || + ga.geoType == GeometryRetained.GEO_TYPE_TEXT3D) { + + if (ga.source instanceof OrientedShape3DRetained) { + primaryRenderMethod = + VirtualUniverse.mc.getOrientedShape3DRenderMethod(); + primaryMoleculeType = ORIENTEDSHAPE3D_MOLECULE; + } else if (ga.geoType == GeometryRetained.GEO_TYPE_TEXT3D) { + primaryRenderMethod = + VirtualUniverse.mc.getText3DRenderMethod(); + primaryMoleculeType = TEXT3D_MOLECULE; + } else { + // Make determination of dlist or not during addRenderAtom + secondaryRenderMethod = cachedVertexArrayRenderMethod; + } + } + else { + if (ga.geoType == GeometryRetained.GEO_TYPE_COMPRESSED) { + primaryRenderMethod = + VirtualUniverse.mc.getCompressedGeometryRenderMethod(); + primaryMoleculeType = COMPRESSED_MOLECULE; + } + else if (geo instanceof RasterRetained) { + primaryRenderMethod = + VirtualUniverse.mc.getDefaultRenderMethod(); + primaryMoleculeType = RASTER_MOLECULE; + } + } + + prev = null; + next = null; + prevMap = null; + nextMap = null; + + primaryRenderAtomList = null; + vertexArrayRenderAtomList = null; + + + + switch (ga.geoType) { + case GeometryRetained.GEO_TYPE_POINT_SET: + case GeometryRetained.GEO_TYPE_INDEXED_POINT_SET: + this.geometryType = POINT; + break; + case GeometryRetained.GEO_TYPE_LINE_SET: + case GeometryRetained.GEO_TYPE_LINE_STRIP_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_STRIP_SET: + this.geometryType = LINE; + break; + case GeometryRetained.GEO_TYPE_RASTER: + this.geometryType = RASTER; + break; + case GeometryRetained.GEO_TYPE_COMPRESSED: + this.geometryType = COMPRESSED; + + switch (((CompressedGeometryRetained)geo).getBufferType()) { + case CompressedGeometryHeader.POINT_BUFFER: + this.geometryType |= POINT ; + break ; + case CompressedGeometryHeader.LINE_BUFFER: + this.geometryType |= LINE ; + break ; + default: + case CompressedGeometryHeader.TRIANGLE_BUFFER: + this.geometryType |= SURFACE ; + if (polygonAttributes != null) { + if (polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_POINT) { + this.geometryType |= POINT; + } else if (polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_LINE) { + this.geometryType |= LINE; + } + } + break ; + } + break; + default: + this.geometryType = SURFACE; + if (polygonAttributes != null) { + if (polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_POINT) { + this.geometryType |= POINT; + } else if (polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_LINE) { + this.geometryType |= LINE; + } + } + break; + } + + isQuadGeometryArray = (geo.getClassType() == + GeometryRetained.QUAD_TYPE); + isTriGeometryArray = (geo.getClassType() == + GeometryRetained.TRIANGLE_TYPE); + + this.localToVworld = transform; + this.localToVworldIndex = transformIndex; + doInfinite = ga.source.inBackgroundGroup; + if (doInfinite) { + if (infLocalToVworld == null) { + infLocalToVworld = new Transform3D[2]; + infLocalToVworld[0] = infLocalToVworld[1] = new Transform3D(); + } + localToVworld[0].getRotation(infLocalToVworld[0]); + } + int mask = 0; + if (polygonAttributes != null) { + if (polygonAttributes.changedFrequent != 0) { + definingPolygonAttributes = polygonAttributes; + + mask |= POLYGONATTRS_DIRTY; + } + else { + if (definingPolygonAttributes != null) { + definingPolygonAttributes.set(polygonAttributes); + } + else { + definingPolygonAttributes = (PolygonAttributesRetained)polygonAttributes.clone(); + } + } + polygonMode = definingPolygonAttributes.polygonMode; + } else { + polygonMode = PolygonAttributes.POLYGON_FILL; + definingPolygonAttributes = null; + } + + if (lineAttributes != null) { + if (lineAttributes.changedFrequent != 0) { + definingLineAttributes = lineAttributes; + mask |= LINEATTRS_DIRTY; + } + else { + if (definingLineAttributes != null) { + definingLineAttributes.set(lineAttributes); + } + else { + definingLineAttributes = (LineAttributesRetained)lineAttributes.clone(); + } + } + lineAA = definingLineAttributes.lineAntialiasing; + } else { + lineAA = false; + definingLineAttributes = null; + } + + if (pointAttributes != null) { + if (pointAttributes.changedFrequent != 0) { + definingPointAttributes = pointAttributes; + mask |= POINTATTRS_DIRTY; + + } + else { + if (definingPointAttributes != null) { + definingPointAttributes.set(pointAttributes); + } + else { + definingPointAttributes = (PointAttributesRetained)pointAttributes.clone(); + } + } + pointAA = definingPointAttributes.pointAntialiasing; + } else { + pointAA = false; + definingPointAttributes = null; + } + + normalPresent = true; + if (geo instanceof GeometryArrayRetained) { + GeometryArrayRetained gr = (GeometryArrayRetained)geo; + this.vertexFormat = gr.vertexFormat; + + if (gr.texCoordSetMap != null) { + this.texCoordSetMapLen = gr.texCoordSetMap.length; + } else { + this.texCoordSetMapLen = 0; + } + + // Throw an exception if lighting is enabled, but no normals defined + if ((vertexFormat & GeometryArray.NORMALS) == 0) { + // Force lighting to false + normalPresent = false; + } + + } + else if (geo instanceof CompressedGeometryRetained) { + this.vertexFormat = + ((CompressedGeometryRetained)geo).getVertexFormat(); + // Throw an exception if lighting is enabled, but no normals defined + if ((vertexFormat & GeometryArray.NORMALS) == 0) { + // Force lighting to false + normalPresent = false; + } + + this.texCoordSetMapLen = 0; + + } else { + this.vertexFormat = -1; + this.texCoordSetMapLen = 0; + } + + if (material != null) { + if (material.changedFrequent != 0) { + definingMaterial = material; + mask |= MATERIAL_DIRTY; + } + else { + if (definingMaterial != null) + definingMaterial.set(material); + else { + definingMaterial = (MaterialRetained)material.clone(); + } + } + + } + else { + definingMaterial = null; + } + evalMaterialCachedState(); + if (coloringAttributes != null) { + if (coloringAttributes.changedFrequent != 0) { + definingColoringAttributes = coloringAttributes; + mask |= COLORINGATTRS_DIRTY; + } + else { + if (definingColoringAttributes != null) { + definingColoringAttributes.set(coloringAttributes); + } + else { + definingColoringAttributes = (ColoringAttributesRetained)coloringAttributes.clone(); + } + } + red = coloringAttributes.color.x; + green = coloringAttributes.color.y; + blue = coloringAttributes.color.z; + } else { + red = 1.0f; + green = 1.0f; + blue = 1.0f; + definingColoringAttributes = null; + } + + if (transparency != null) { + + if (transparency.changedFrequent != 0) { + definingTransparency = transparency; + mask |= TRANSPARENCY_DIRTY; + } + else { + if (definingTransparency != null) { + definingTransparency.set(transparency); + } + else { + definingTransparency = + (TransparencyAttributesRetained)transparency.clone(); + } + } + alpha = 1.0f - transparency.transparency; + + } else { + alpha = 1.0f; + definingTransparency = null; + + } + + locale = ga.source.locale; + if (locale != renderBin.locale) { + if (localeLocalToVworld == null) { + localeLocalToVworld = new Transform3D[2]; + } + localeLocalToVworld[0] = VirtualUniverse.mc.getTransform3D(null); + localeLocalToVworld[1] = VirtualUniverse.mc.getTransform3D(null); + localeTranslation = new Vector3d(); + ga.locale.hiRes.difference(renderBin.locale.hiRes, localeTranslation); + translate(); + } + else { + localeLocalToVworld = localToVworld; + } + + if (doInfinite) { + trans = infLocalToVworld; + } + else { + trans = localeLocalToVworld; + } + + evalAlphaUsage(renderAttrs, texUnits); + isOpaqueOrInOG = isOpaque() || (ga.source.orderedPath != null); + inOrderedGroup = (ga.source.orderedPath != null); + // System.out.println("isOpaque = "+isOpaque() +" OrInOG = "+isOpaqueOrInOG); + if (mask != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= mask; + } + } + + + /** + * This tests if the given attributes matches this TextureBin + */ + boolean equals(RenderAtom ra, + PolygonAttributesRetained polygonAttributes, + LineAttributesRetained lineAttributes, + PointAttributesRetained pointAttributes, + MaterialRetained material, + ColoringAttributesRetained coloringAttributes, + TransparencyAttributesRetained transparency, + Transform3D[] transform) { + int geoType = 0; + GeometryAtom ga = ra.geometryAtom; + int eAttrs = 0; + + if (this.localToVworld != transform) { + return (false); + } + + if (locale != ra.geometryAtom.source.locale) { + return (false); + } + + if (ra.geometryAtom.source.closestSwitchParent != closestSwitchParent || + ra.geometryAtom.source.closestSwitchIndex != closestSwitchIndex) { + return (false); + } + + // Find the first non-null geometey + GeometryRetained geo = null; + int k = 0; + while (geo == null && (k < ga.geometryArray.length)) { + geo = ga.geometryArray[k]; + k++; + } + + // TODO: Add tags + switch (ga.geoType) { + case GeometryRetained.GEO_TYPE_POINT_SET: + case GeometryRetained.GEO_TYPE_INDEXED_POINT_SET: + geoType = POINT; + break; + case GeometryRetained.GEO_TYPE_LINE_SET: + case GeometryRetained.GEO_TYPE_LINE_STRIP_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_SET: + case GeometryRetained.GEO_TYPE_INDEXED_LINE_STRIP_SET: + geoType = LINE; + break; + case GeometryRetained.GEO_TYPE_RASTER: + geoType = RASTER; + break; + case GeometryRetained.GEO_TYPE_COMPRESSED: + geoType = COMPRESSED; + switch (((CompressedGeometryRetained)geo).getBufferType()) { + case CompressedGeometryHeader.POINT_BUFFER: + geoType |= POINT ; + break ; + case CompressedGeometryHeader.LINE_BUFFER: + geoType |= LINE ; + break ; + default: + case CompressedGeometryHeader.TRIANGLE_BUFFER: + geoType |= SURFACE ; + break ; + } + break; + default: + geoType = SURFACE; + if (polygonAttributes != null) { + if (polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_POINT) { + geoType |= POINT; + } else if (polygonAttributes.polygonMode == + PolygonAttributes.POLYGON_LINE) { + geoType |= LINE; + } + } + break; + } + + if (this.geometryType != geoType) { + return (false); + } + /* + // TODO : Check this + if (useDisplayList && + (ga.geometry.isEditable || + ga.geometry.refCount > 1 || + ((GroupRetained)ga.source.parent).switchLevel >= 0 || + ga.alphaEditable)) { + return (false); + } + */ + if (ga.geoType == GeometryRetained.GEO_TYPE_TEXT3D && + primaryMoleculeType != 0 && + ((primaryMoleculeType & TEXT3D_MOLECULE) == 0)) { + return (false); + } + + + if(!(ra.geometryAtom.source instanceof OrientedShape3DRetained) + && ((primaryMoleculeType & ORIENTEDSHAPE3D_MOLECULE) != 0)) { + //System.out.println("RA's NOT a OrientedShape3DRetained and RM is a ORIENTEDSHAPE3D_MOLECULE "); + + return (false); + } + + // TODO: Its is necessary to have same vformat for dl, + // Howabout iteration, should we have 2 vformats in rm? + if (geo instanceof GeometryArrayRetained) { + GeometryArrayRetained gr = (GeometryArrayRetained)geo; + if (this.vertexFormat != gr.vertexFormat) { + return (false); + } + + + // we are requiring that texCoordSetMap length to be the same + // so that we can either put all multi-tex ga to a display list, + // or punt all to vertex array. And we don't need to worry + // about some of the ga can be in display list for this canvas, + // and some other can be in display list for the other canvas. + if (((gr.texCoordSetMap != null) && + (this.texCoordSetMapLen != gr.texCoordSetMap.length)) || + ((gr.texCoordSetMap == null) && (this.texCoordSetMapLen != 0))) { + return (false); + } + + if (VirtualUniverse.mc.isD3D() && + (((geo.getClassType() == GeometryRetained.QUAD_TYPE) + && !isQuadGeometryArray) || + ((geo.getClassType() == GeometryRetained.TRIANGLE_TYPE) + && !isTriGeometryArray))) { + return false; + } + + } else if (geo instanceof CompressedGeometryRetained) { + if (this.vertexFormat != + ((CompressedGeometryRetained)geo).getVertexFormat()) { + return (false); + } + } else { + //TODO: compare isEditable + if (this.vertexFormat != -1) { + return (false); + } + } + + // If the any reference to the appearance components that is cached renderMolecule + // can change frequently, make a separate bin + if (soleUser || (ra.geometryAtom.source.appearance != null && + ((ra.geometryAtom.source.appearance.changedFrequent & RM_COMPONENTS) != 0))) { + if (appHandle == (Object)ra.geometryAtom.source.appearance) { + + // if this RenderMolecule is currently on a zombie state, + // we'll need to put it on the update list to reevaluate + // the state, because while it is on a zombie state, + // state could have been changed. Example, + // application could have detached an appearance, + // made changes to the appearance state, and then + // reattached the appearance. In this case, the + // changes would not have reflected to the RenderMolecule + + if (numEditingRenderAtoms == 0) { + + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= ALL_DIRTY_BITS; + } + return true; + } + else { + return false; + } + + } + // Assign the cloned value as the original value + + // Either a changedFrequent or a null case + // and the incoming one is not equal or null + // then return; + // This check also handles null == null case + if (definingPolygonAttributes != null) { + if ((this.definingPolygonAttributes.changedFrequent != 0) || + (polygonAttributes !=null && polygonAttributes.changedFrequent != 0)) + if (definingPolygonAttributes == polygonAttributes) { + if (definingPolygonAttributes.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= POLYGONATTRS_DIRTY; + } + } + else { + return false; + } + else if (!definingPolygonAttributes.equivalent(polygonAttributes)) { + return false; + } + } + else if (polygonAttributes != null) { + return false; + } + + if (definingLineAttributes != null) { + if ((this.definingLineAttributes.changedFrequent != 0) || + (lineAttributes !=null && lineAttributes.changedFrequent != 0)) + if (definingLineAttributes == lineAttributes) { + if (definingLineAttributes.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= LINEATTRS_DIRTY; + } + } + else { + return false; + } + else if (!definingLineAttributes.equivalent(lineAttributes)) { + return false; + } + } + else if (lineAttributes != null) { + return false; + } + + + if (definingPointAttributes != null) { + if ((this.definingPointAttributes.changedFrequent != 0) || + (pointAttributes !=null && pointAttributes.changedFrequent != 0)) + if (definingPointAttributes == pointAttributes) { + if (definingPointAttributes.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= POINTATTRS_DIRTY; + } + } + else { + return false; + } + else if (!definingPointAttributes.equivalent(pointAttributes)) { + return false; + } + } + else if (pointAttributes != null) { + return false; + } + + + + + if (definingMaterial != null) { + if ((this.definingMaterial.changedFrequent != 0) || + (material !=null && material.changedFrequent != 0)) + if (definingMaterial == material) { + if (definingMaterial.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= MATERIAL_DIRTY; + } + } + else { + return false; + } + else if (!definingMaterial.equivalent(material)) { + return false; + } + } + else if (material != null) { + return false; + } + + + + if (definingColoringAttributes != null) { + if ((this.definingColoringAttributes.changedFrequent != 0) || + (coloringAttributes !=null && coloringAttributes.changedFrequent != 0)) + if (definingColoringAttributes == coloringAttributes) { + if (definingColoringAttributes.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= COLORINGATTRS_DIRTY; + } + } + else { + return false; + } + else if (!definingColoringAttributes.equivalent(coloringAttributes)) { + return false; + } + } + else if (coloringAttributes != null) { + return false; + } + + // if the definingTransparency is a non cloned values and the incoming + // one is equivalent, then check if the component is dirty + // this happens when all the RAs from this RM have been removed + // but new ones are not added yet (rbin visibility) not run yet + // and when there is a change in nc based on the new RA, we wil; + // miss the change, doing this check will catch the change durin + // new RAs insertRenderAtom + if (definingTransparency != null) { + if ((this.definingTransparency.changedFrequent != 0) || + (transparency !=null && transparency.changedFrequent != 0)) + if (definingTransparency == transparency) { + if (definingTransparency.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= TRANSPARENCY_DIRTY; + } + } + else { + return false; + } + else if (!definingTransparency.equivalent(transparency)) { + return false; + } + } + else if (transparency != null) { + return false; + } + + return (true); + } + + public void updateRemoveRenderAtoms() { + int i; + RenderAtom r; + RenderAtomListInfo rinfo; + + // Check if this renderMolecule was created and destroyed this frame. + // so, no display list was created + if (numRenderAtoms == 0 && removeRAs == null && addRAs == null) { + textureBin.removeRenderMolecule(this); + return; + } + + while (removeRAs != null) { + r = (RenderAtom)removeRAs; + r.removed = null; + numRenderAtoms--; + + // Loop thru all geometries in the renderAtom, they could + // potentially be in different buckets in the rendermoleulce + for (int index = 0; index < r.rListInfo.length; index++) { + rinfo = r.rListInfo[index]; + // Don't remove null geo + if (rinfo.geometry() == null) + continue; + + if ((rinfo.groupType & RenderAtom.PRIMARY) != 0) { + primaryChanged = true; + if (rinfo.prev == null) { // At the head of the list + primaryRenderAtomList = rinfo.next; + if (rinfo.next != null) { + rinfo.next.prev = null; + } + } else { // In the middle or at the end. + rinfo.prev.next = rinfo.next; + if (rinfo.next != null) { + rinfo.next.prev = rinfo.prev; + } + } + // If this renderAtom has localTransform, + // return transform to freelist + if (primaryMoleculeType == RenderMolecule.TEXT3D_MOLECULE) { + if (!rinfo.renderAtom.inRenderBin()) { + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, rinfo.localToVworld); + + } + } + // If the molecule type is Raster, then add it to the lock list + else if (primaryMoleculeType == RASTER) { + RasterRetained geo = (RasterRetained)rinfo.geometry(); + renderBin.removeGeometryFromLockList(geo); + if (geo.image != null) + renderBin.removeNodeComponent(geo.image); + + } + else if ((rinfo.groupType & RenderAtom.SEPARATE_DLIST_PER_RINFO) != 0) { + if (!rinfo.renderAtom.inRenderBin()) { + renderBin.removeDlistPerRinfo.add(rinfo); + } + } + } + else if ((rinfo.groupType & RenderAtom.SEPARATE_DLIST_PER_GEO) != 0) { + if (rinfo.prev == null) { // At the head of the list + separateDlistRenderAtomList = rinfo.next; + if (rinfo.next != null) { + rinfo.next.prev = null; + } + } else { // In the middle or at the end. + rinfo.prev.next = rinfo.next; + if (rinfo.next != null) { + rinfo.next.prev = rinfo.prev; + } + } + renderBin.removeGeometryDlist(rinfo); + + } + else { + if (rinfo.prev == null) { // At the head of the list + vertexArrayRenderAtomList = rinfo.next; + if (rinfo.next != null) { + rinfo.next.prev = null; + } + } else { // In the middle or at the end. + rinfo.prev.next = rinfo.next; + if (rinfo.next != null) { + rinfo.next.prev = rinfo.prev; + } + } + // For indexed geometry there is no need to lock since + // the mirror is changed only when the renderer is not + // running + // For indexed geometry, if use_coord is set, then either we + // are using the index geometry as is or we will be unindexifying + // on the fly, so its better to lock + GeometryArrayRetained geo = (GeometryArrayRetained)rinfo.geometry(); + if (!(geo instanceof IndexedGeometryArrayRetained) || + ((geo.vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0)) { + renderBin.removeGeometryFromLockList(geo); + } + } + rinfo.prev = null; + rinfo.next = null; + } + removeRAs = removeRAs.nextRemove; + r.nextRemove = null; + r.prevRemove = null; + if (r.isOriented()) { + renderBin.orientedRAs.remove(renderBin.orientedRAs.indexOf(r)); + } + + if ((textureBin.attributeBin.environmentSet.lightBin.geometryBackground == null) && + !isOpaqueOrInOG && renderBin.transpSortMode == View.TRANSPARENCY_SORT_GEOMETRY) { + renderBin.removeTransparentObject(r); + } + } + // If this renderMolecule will not be touched for adding new RenderAtoms + // then .. + if (addRAs == null) { + // If there are no more renderAtoms and there will be no more + // renderatoms added to this renderMolecule , then remove + if (numRenderAtoms == 0) { + // If both lists are empty remove this renderMolecule + if ((primaryMoleculeType &DLIST_MOLECULE) != 0) { + renderBin.addDisplayListResourceFreeList(this); + vwcBounds.set(null); + displayListId = 0; + displayListIdObj = null; + } + // If the locale is different, return xform to freelist + if (locale != renderBin.locale) { + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + localeLocalToVworld[0]); + localeLocalToVworld = null; + } + textureBin.removeRenderMolecule(this); + } else { + if ((primaryMoleculeType &DLIST_MOLECULE) != 0 && primaryChanged) { + + // If a renderAtom is added to the display list + // structure then add this to the dirty list of rm + // for which the display list needs to be recreated + renderBin.addDirtyRenderMolecule(this); + vwcBounds.set(null); + rinfo = primaryRenderAtomList; + while (rinfo != null) { + vwcBounds.combine(rinfo.renderAtom.localeVwcBounds); + rinfo = rinfo.next; + } + primaryChanged = false; + } + } + } + numEditingRenderAtoms = numRenderAtoms; + } + + public void updateObject() { + int i; + RenderAtom renderAtom; + RenderAtomListInfo r; + if (textureBin == null) { + return; + } + + if (addRAs != null) { + while (addRAs != null) { + + numRenderAtoms++; + renderAtom = (RenderAtom)addRAs; + renderAtom.renderMolecule = this; + renderAtom.added = null; + for (int j = 0; j < renderAtom.rListInfo.length; j++) { + r = (RenderAtomListInfo)renderAtom.rListInfo[j]; + // Don't add null geo + if (r.geometry() == null) + continue; + r.groupType = evalRinfoGroupType(r); + if ((r.groupType & RenderAtom.PRIMARY) != 0) { + if ((r.groupType & RenderAtom.DLIST) != 0 && primaryRenderMethod == null) { + primaryMoleculeType = DLIST_MOLECULE; + renderBin.renderMoleculeList.add(this); + + if (vwcBounds == null) + vwcBounds = new BoundingBox((BoundingBox)null); + primaryRenderMethod = + VirtualUniverse.mc.getDisplayListRenderMethod(); + // Assign a displayListId for this renderMolecule + if (displayListId == 0) { + displayListIdObj = VirtualUniverse.mc.getDisplayListId(); + displayListId = displayListIdObj.intValue(); + } + } + else if ((r.groupType & RenderAtom.SEPARATE_DLIST_PER_RINFO) != 0 && + primaryRenderMethod == null) { + primaryMoleculeType = SEPARATE_DLIST_PER_RINFO_MOLECULE; + renderBin.renderMoleculeList.add(this); + primaryRenderMethod = + VirtualUniverse.mc.getDisplayListRenderMethod(); + + } + primaryChanged = true; + if (primaryRenderAtomList == null) { + primaryRenderAtomList = r; + } + else { + r.next = primaryRenderAtomList; + primaryRenderAtomList.prev = r; + primaryRenderAtomList = r; + } + if (primaryMoleculeType == SEPARATE_DLIST_PER_RINFO_MOLECULE) { + if (r.renderAtom.dlistIds == null) { + r.renderAtom.dlistIds = new int[r.renderAtom.rListInfo.length]; + + for (int k = 0; k < r.renderAtom.dlistIds.length; k++) { + r.renderAtom.dlistIds[k] = -1; + } + } + if (r.renderAtom.dlistIds[r.index] == -1) { + r.renderAtom.dlistIds[r.index] = VirtualUniverse.mc.getDisplayListId().intValue(); + renderBin.addDlistPerRinfo.add(r); + } + } + + // If the molecule type is Raster, then add it to the lock list + if (primaryMoleculeType == RASTER) { + RasterRetained geo = (RasterRetained)r.geometry(); + renderBin.addGeometryToLockList(geo); + if (geo.image != null) + renderBin.addNodeComponent(geo.image); + } + } + else if ((r.groupType & RenderAtom.SEPARATE_DLIST_PER_GEO) != 0) { + if (separateDlistRenderAtomList == null) { + separateDlistRenderAtomList = r; + } + else { + r.next = separateDlistRenderAtomList; + separateDlistRenderAtomList.prev = r; + separateDlistRenderAtomList = r; + } + ((GeometryArrayRetained)r.geometry()).assignDlistId(); + renderBin.addGeometryDlist(r); + } + else { + if (secondaryRenderMethod == null) + secondaryRenderMethod = cachedVertexArrayRenderMethod; + if (vertexArrayRenderAtomList == null) { + vertexArrayRenderAtomList = r; + } + else { + r.next = vertexArrayRenderAtomList; + vertexArrayRenderAtomList.prev = r; + vertexArrayRenderAtomList = r; + } + // For indexed geometry there is no need to lock since + // the mirror is changed only when the renderer is not + // running + // For indexed geometry, if use_coord is set, then either we + // are using the index geometry as is or we will be unindexifying + // on the fly, so its better to loc + GeometryArrayRetained geo = (GeometryArrayRetained)r.geometry(); + if (!(geo instanceof IndexedGeometryArrayRetained) || + ((geo.vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0)) { + renderBin.addGeometryToLockList(geo); + // Add the geometry to the dirty list only if the geometry is by + // refernce and there is color and we need to use alpha and its + // not multiScreen + if ((( geo.vertexFormat & GeometryArray.BY_REFERENCE)!=0) && + (geo.c4fAllocated == 0) && + ((geo.vertexFormat & GeometryArray.COLOR) != 0) && + useAlpha && + !renderBin.multiScreen) { + renderBin.addDirtyReferenceGeometry(geo); + } + } + } + + } + addRAs = addRAs.nextAdd; + renderAtom.nextAdd = null; + renderAtom.prevAdd = null; + if (renderAtom.isOriented()) { + renderBin.orientedRAs.add(renderAtom); + + } + // If transparent and not in bg geometry and is depth sorted transparency + if (!isOpaqueOrInOG && (textureBin.attributeBin.environmentSet.lightBin.geometryBackground == null)&& + (renderBin.transpSortMode == View.TRANSPARENCY_SORT_GEOMETRY)) { + GeometryRetained geo = null; + int k = 0; + while (geo == null && k < renderAtom.rListInfo.length) { + geo = renderAtom.rListInfo[k].geometry(); + k++; + } + if (geo != null) { + if (renderAtom.parentTInfo != null && renderAtom.parentTInfo[k-1] != null) { + renderBin.updateTransparentInfo(renderAtom); + } + // Newly added renderAtom + else { + renderBin.addTransparentObject(renderAtom); + } + } + // Moving within the renderBin + + } + } + + if ((primaryMoleculeType &DLIST_MOLECULE) != 0 && primaryChanged) { + + // If a renderAtom is added to the display list + // structure then add this to the dirty list of rm + // for which the display list needs to be recreated + renderBin.addDirtyRenderMolecule(this); + vwcBounds.set(null); + r = primaryRenderAtomList; + while (r != null) { + vwcBounds.combine(r.renderAtom.localeVwcBounds); + r = r.next; + } + primaryChanged = false; + } + + + if ((onUpdateList & LOCALE_CHANGED) != 0) { + handleLocaleChange(); + } + + if (locale != renderBin.locale) { + translate(); + } + } + else { + // The flag LOCALE_CHANGED only gets sets when there is a new additon + // There are cases when RM updateObject() get called (due to addition + // in renderBin - see processTransformChanged()), we need to + // evaluate locale change for this case as well + if (renderBin.localeChanged) { + handleLocaleChange(); + } + + if (locale != renderBin.locale) { + translate(); + } + + if ((onUpdateList & UPDATE_BACKGROUND_TRANSFORM) != 0) { + i = localToVworldIndex[NodeRetained.LAST_LOCAL_TO_VWORLD]; + localeLocalToVworld[i].getRotation(infLocalToVworld[i]); + } + + // No new renderAtoms were added, but need to + // recompute vwcBounds in response to xform change + if ((onUpdateList & BOUNDS_RECOMPUTE_UPDATE) != 0) { + vwcBounds.set(null); + r = primaryRenderAtomList; + while (r != null) { + vwcBounds.combine(r.renderAtom.localeVwcBounds); + r = r.next; + } + } + } + + // Clear all bits except the IN_DIRTY_LIST + onUpdateList &= IN_DIRTY_RENDERMOLECULE_LIST; + + numEditingRenderAtoms = numRenderAtoms; + } + + boolean canBeInDisplayList(GeometryRetained geo, GeometryAtom ga) { + boolean inDL = false; + inDL = geo.canBeInDisplayList(ga.alphaEditable); + // If can not in DL, then check if all the attrs affecting + // it are infrequently changing, if yes then put it + // Exclude Morph and indexed-by-ref-use-index-coord only for now + // in displayList if OptimizeForSpace if false + + // System.out.println("inDL = "+inDL); + // System.out.println("geo.cachedChangedFrequent = "+geo.cachedChangedFrequent); + // System.out.println("inDL = "+inDL); + // System.out.println("COLOR = "+((((GeometryArrayRetained)geo).vertexFormat& + // GeometryArray.COLOR) != 0)); + // System.out.println("Before: inDL = "+inDL); + // System.out.println("VirtualUniverse.mc.buildDisplayListIfPossible = "+VirtualUniverse.mc.buildDisplayListIfPossible); + if (VirtualUniverse.mc.buildDisplayListIfPossible && + !inDL && + !(ga.source.sourceNode instanceof MorphRetained || + (geo instanceof GeometryArrayRetained && + ((((GeometryArrayRetained)geo).vertexFormat & (GeometryArray.USE_NIO_BUFFER|GeometryArray.INTERLEAVED)) ==(GeometryArray.USE_NIO_BUFFER|GeometryArray.INTERLEAVED))) || + (geo instanceof IndexedGeometryArrayRetained && + ((((IndexedGeometryArrayRetained)geo).vertexFormat & (GeometryArray.BY_REFERENCE|GeometryArray.USE_COORD_INDEX_ONLY)) == (GeometryArray.BY_REFERENCE|GeometryArray.USE_COORD_INDEX_ONLY))))) { + // Check if geometry is frequentlyEditable + boolean alphaFreqEditable = ga.source.isAlphaFrequentlyEditable(geo); + inDL = !((geo.cachedChangedFrequent != 0) || + ((!(geo instanceof GeometryArrayRetained) && alphaFreqEditable)|| + (alphaFreqEditable && ((((GeometryArrayRetained)geo).vertexFormat& + GeometryArray.COLOR) != 0)))); + } + // System.out.println("After: inDL = "+inDL); + return inDL; + } + + // If dlist will be altered due to alpha or ignoreVertexColors, then don't + // put in a separate dlist that can be shared ... + final boolean geoNotAltered(GeometryArrayRetained geo) { + return !(((geo.vertexFormat & GeometryArray.COLOR) != 0) && + (textureBin.attributeBin.ignoreVertexColors || useAlpha)); + } + + int evalRinfoGroupType(RenderAtomListInfo r) { + int groupType = 0; + + GeometryRetained geo = r.geometry(); + if (geo == null) + return groupType; + + if ((primaryMoleculeType & (COMPRESSED_MOLECULE | + RASTER_MOLECULE | + TEXT3D_MOLECULE | + ORIENTEDSHAPE3D_MOLECULE)) != 0) { + groupType = RenderAtom.OTHER; + } + else if (canBeInDisplayList(geo, r.renderAtom.geometryAtom)) { + // if geometry is under share group we immediate set the + // dlistID to something other than -1 + if ( !((GeometryArrayRetained)geo).isShared || + // if we do a compiled and push the transform down to + // Geometry, we can't share the displayList + (r.renderAtom.geometryAtom.source.staticTransform != null)) { + // If the molecule is already defined to be SEPARATE_DLIST_PER_RINFO_MOLECULE + // continue adding in that mode even if it was switched back to + // no depth sorted mode + // System.out.println("isOpaqueOrInOG ="+isOpaqueOrInOG+" primaryMoleculeType ="+primaryMoleculeType+" renderBin.transpSortMode ="+renderBin.transpSortMode); + if (primaryMoleculeType == SEPARATE_DLIST_PER_RINFO_MOLECULE) { + groupType = RenderAtom.SEPARATE_DLIST_PER_RINFO; + } + else { + if (isOpaqueOrInOG || + renderBin.transpSortMode == View.TRANSPARENCY_SORT_NONE) { + groupType = RenderAtom.DLIST; + } + else { + groupType = RenderAtom.SEPARATE_DLIST_PER_RINFO; + } + } + + } else if (geoNotAltered((GeometryArrayRetained)r.geometry()) ) { + groupType = RenderAtom.SEPARATE_DLIST_PER_GEO; + } + else { + groupType = RenderAtom.VARRAY; + } + } + else { + groupType = RenderAtom.VARRAY; + } + return groupType; + } + + /** + * Adds the given RenderAtom to this RenderMolecule. + */ + void addRenderAtom(RenderAtom renderAtom, RenderBin rb) { + int i, n; + RenderAtomListInfo r; + int index; + + renderAtom.envSet = textureBin.attributeBin.environmentSet; + renderAtom.renderMolecule = this; + renderAtom.dirtyMask &= ~RenderAtom.NEED_SEPARATE_LOCALE_VWC_BOUNDS; + + AppearanceRetained raApp = renderAtom.geometryAtom.source.appearance; + + MaterialRetained mat = (raApp == null)? null : raApp.material; + if (!soleUser && material != mat) { + // no longer sole user + material = definingMaterial; + } + + if ((geometryType & SURFACE) != 0) { + PolygonAttributesRetained pgAttrs = + (raApp == null)? null : raApp.polygonAttributes; + if (!soleUser && polygonAttributes != pgAttrs) { + // no longer sole user + polygonAttributes = definingPolygonAttributes; + } + + } + if ((geometryType & LINE) != 0) { + LineAttributesRetained lnAttrs = + (raApp == null)? null : raApp.lineAttributes; + if (!soleUser && lineAttributes != lnAttrs) { + // no longer sole user + lineAttributes = definingLineAttributes; + } + + } + if ((geometryType & POINT) != 0) { + PointAttributesRetained pnAttrs = + (raApp == null)? null : raApp.pointAttributes; + if (!soleUser && pointAttributes != pnAttrs) { + // no longer sole user + pointAttributes = definingPointAttributes; + } + } + + ColoringAttributesRetained coAttrs = + (raApp == null)? null : raApp.coloringAttributes; + if (!soleUser && coloringAttributes != coAttrs) { + // no longer sole user + coloringAttributes = definingColoringAttributes; + } + + TransparencyAttributesRetained trAttrs = + (raApp == null)? null : raApp.transparencyAttributes; + if (!soleUser && transparency != trAttrs) { + // no longer sole user + transparency = definingTransparency; + } + + + + // If the renderAtom is being inserted first time, then evaluate + // the groupType to determine if need separate localeVwcBounds + if (!renderAtom.inRenderBin()) { + for (i = 0; i < renderAtom.rListInfo.length; i++) { + if (renderAtom.rListInfo[i].geometry() == null) + continue; + int groupType = evalRinfoGroupType(renderAtom.rListInfo[i]); + if (groupType != RenderAtom.DLIST) { + renderAtom.dirtyMask |= RenderAtom.NEED_SEPARATE_LOCALE_VWC_BOUNDS; + } + } + } + if (renderAtom.removed == this) { + // Remove the renderAtom from the list of removeRAs + // If this is at the head of the list + if (renderAtom == removeRAs) { + removeRAs = renderAtom.nextRemove; + if (removeRAs != null) + removeRAs.prevRemove = null; + renderAtom.nextRemove = null; + renderAtom.prevRemove = null; + } + // Somewhere in the middle + else { + renderAtom.prevRemove.nextRemove = renderAtom.nextRemove; + if (renderAtom.nextRemove != null) + renderAtom.nextRemove.prevRemove = renderAtom.prevRemove; + renderAtom.nextRemove = null; + renderAtom.prevRemove = null; + } + + renderAtom.removed = null; + // Redo any dlist etc, because it has been added + for ( i = 0; i < renderAtom.rListInfo.length; i++) { + if (renderAtom.rListInfo[i].geometry() == null) + continue; + if ((renderAtom.rListInfo[i].groupType & RenderAtom.DLIST) != 0) + renderBin.addDirtyRenderMolecule(this); + else if ((renderAtom.rListInfo[i].groupType & RenderAtom.SEPARATE_DLIST_PER_RINFO) != 0) { + renderBin.addDlistPerRinfo.add(renderAtom.rListInfo[i]); + } + else if ((renderAtom.rListInfo[i].groupType & RenderAtom.SEPARATE_DLIST_PER_GEO) != 0) + renderBin.addGeometryDlist(renderAtom.rListInfo[i]); + + } + if (removeRAs == null) + rb.removeRenderAtomInRMList.remove(this); + } + else { + // Add this renderAtom to the addList + if (addRAs == null) { + addRAs = renderAtom; + renderAtom.nextAdd = null; + renderAtom.prevAdd = null; + } + else { + renderAtom.nextAdd = addRAs; + renderAtom.prevAdd = null; + addRAs.prevAdd = renderAtom; + addRAs = renderAtom; + } + renderAtom.added = this; + if (onUpdateList == 0) + rb.objUpdateList.add(this); + onUpdateList |= NEW_RENDERATOMS_UPDATE; + + } + if (renderBin.localeChanged && !doInfinite) { + if (onUpdateList == 0) + rb.objUpdateList.add(this); + onUpdateList |= LOCALE_CHANGED; + } + + // inform the texture bin that this render molecule is no longer + // in zombie state + + if (numEditingRenderAtoms == 0) { + textureBin.incrActiveRenderMolecule(); + } + numEditingRenderAtoms++; + } + + /** + * Removes the given RenderAtom from this RenderMolecule. + */ + void removeRenderAtom(RenderAtom r) { + int index; + + r.renderMolecule = null; + if (r.added == this) { + //Remove this renderAtom from the addRAs list + + // If this is at the head of the list + if (r == addRAs) { + addRAs = r.nextAdd; + if (addRAs != null) + addRAs.prevAdd = null; + r.nextAdd = null; + r.prevAdd = null; + } + // Somewhere in the middle + else { + r.prevAdd.nextAdd = r.nextAdd; + if (r.nextAdd != null) + r.nextAdd.prevAdd = r.prevAdd; + r.nextAdd = null; + r.prevAdd = null; + } + + r.added = null; + r.envSet = null; + // If the number of renderAtoms is zero, and it is on the + // update list for adding new renderatroms only (not for + // bounds update), then remove this rm from the update list + + // Might be expensive to remove this entry from the renderBin + // objUpdateList, just let it call the renderMolecule + /* + if (addRAs == null) { + if (onUpdateList == NEW_RENDERATOMS_UPDATE){ + renderBin.objUpdateList.remove(renderBin.objUpdateList.indexOf(this)); + } + onUpdateList &= ~NEW_RENDERATOMS_UPDATE; + } + */ + + } + else { + // Add this renderAtom to the remove list + if (removeRAs == null) { + removeRAs = r; + r.nextRemove = null; + r.prevRemove = null; + } + else { + r.nextRemove = removeRAs; + r.prevRemove = null; + removeRAs.prevRemove = r; + removeRAs = r; + } + r.removed = this; + } + + // Add it to the removeRenderAtom List , in case the renderMolecule + // needs to be removed + if (!renderBin.removeRenderAtomInRMList.contains(this)) { + renderBin.removeRenderAtomInRMList.add(this); + } + + // decrement the number of editing render atoms in this render molecule + numEditingRenderAtoms--; + + // if there is no more editing render atoms, inform the texture bin + // that this render molecule is going to zombie state + + if (numEditingRenderAtoms == 0) { + textureBin.decrActiveRenderMolecule(); + } + } + + /** + * Recalculates the vwcBounds for a RenderMolecule + */ + void recalcBounds() { + RenderAtomListInfo ra; + + if (primaryRenderMethod == + VirtualUniverse.mc.getDisplayListRenderMethod()) { + vwcBounds.set(null); + ra = primaryRenderAtomList; + while (ra != null) { + vwcBounds.combine(ra.renderAtom.localeVwcBounds); + ra = ra.next; + } + } + } + + void evalAlphaUsage(RenderingAttributesRetained renderAttrs, + TextureUnitStateRetained[] texUnits) { + RenderingAttributesRetained ra; + TextureAttributesRetained ta; + boolean alphaBlend, alphaTest, textureBlend = false; + + alphaBlend = + definingTransparency != null && + definingTransparency.transparencyMode != + TransparencyAttributes.NONE && + (VirtualUniverse.mc.isD3D() || + !VirtualUniverse.mc.isD3D() && + definingTransparency.transparencyMode != + TransparencyAttributes.SCREEN_DOOR); + + + if (texUnits != null) { + for (int i = 0; + textureBlend == false && i < texUnits.length; + i++) { + if (texUnits[i] != null && + texUnits[i].texAttrs != null) { + textureBlend = textureBlend || + (texUnits[i].texAttrs.textureMode == + TextureAttributes.BLEND); + } + } + } + + alphaTest = + renderAttrs != null && renderAttrs.alphaTestFunction != RenderingAttributes.ALWAYS; + + boolean oldUseAlpha = useAlpha; + useAlpha = alphaBlend || alphaTest || textureBlend; + + if( !oldUseAlpha && useAlpha) { + GeometryArrayRetained geo = null; + + if(vertexArrayRenderAtomList != null) + geo = (GeometryArrayRetained)vertexArrayRenderAtomList.geometry(); + + if(geo != null) { + if (!(geo instanceof IndexedGeometryArrayRetained) || + ((geo.vertexFormat & GeometryArray.USE_COORD_INDEX_ONLY) != 0)) { + renderBin.addGeometryToLockList(geo); + // Add the geometry to the dirty list only if the geometry is by + // refernce and there is color and we need to use alpha and its + // not multiScreen + if ((( geo.vertexFormat & GeometryArray.BY_REFERENCE)!=0) && + (geo.c4fAllocated == 0) && + ((geo.vertexFormat & GeometryArray.COLOR) != 0) && + useAlpha && + !renderBin.multiScreen) { + renderBin.addDirtyReferenceGeometry(geo); + } + } + } + } + } + + final boolean isSwitchOn() { + // The switchOn status of the entire RM can be determined + // by the switchOn status of any renderAtoms below. + // This is possible because renderAtoms generated from a common + // switch branch are placed in the same renderMolecule + if (primaryRenderAtomList != null) { + return primaryRenderAtomList.renderAtom.geometryAtom. + source.switchState.lastSwitchOn; + + } + + if (vertexArrayRenderAtomList != null) { + return vertexArrayRenderAtomList.renderAtom.geometryAtom. + source.switchState.lastSwitchOn; + + } + + if (separateDlistRenderAtomList != null) { + return separateDlistRenderAtomList.renderAtom.geometryAtom. + source.switchState.lastSwitchOn; + } + return false; + } + + /** + * Renders this RenderMolecule + */ + boolean render(Canvas3D cv, int pass, int dirtyBits) { + + boolean isVisible = isSwitchOn(); + + if (!isVisible) { + return false; + } + + isVisible = false; + + // include this LightBin to the to-be-updated list in Canvas + cv.setStateToUpdate(Canvas3D.RENDERMOLECULE_BIT, this); + + boolean modeSupportDL = true; + isNonUniformScale = !trans[localToVworldIndex[NodeRetained.LAST_LOCAL_TO_VWORLD]].isCongruent(); + // We have to dynamically switch between using displaymode + // mode or not instead of decide in canBeInDisplayList(), + // since polygonAttribute can be change by editable Appearance + // or editable polygonAttribute which mode we can't take + // advantage of display list mode in many cases just because + // there are three special cases to handle. + + // Another case for punting to vertex array is if pass specifies + // something other than -1. That means, we are in the + // multi-texturing multi-pass case. Then we'll use vertex array + // instead. Or the length of the texCoordSetMap is greater than + // the number of texture units supported by the Canvas, then + // we'll have to punt to vertex array as well. + + if ((pass != TextureBin.USE_DISPLAYLIST) || + (texCoordSetMapLen > cv.numTexCoordSupported) || + (VirtualUniverse.mc.isD3D() && + (((definingPolygonAttributes != null) && + ((isQuadGeometryArray && + (definingPolygonAttributes.polygonMode == + PolygonAttributes.POLYGON_LINE))|| + (isTriGeometryArray && + (definingPolygonAttributes.polygonMode == + PolygonAttributes.POLYGON_POINT)))) || + cv.texLinearMode))) { + modeSupportDL = false; + } + + /* + System.out.println("texCoord " + texCoordSetMapLen + " " + + cv.numTexCoordSupported + " " + modeSupportDL); + + System.out.println("primaryMoleculeType = "+primaryMoleculeType+" primaryRenderAtomList ="+primaryRenderAtomList+" separateDlistRenderAtomList ="+separateDlistRenderAtomList+" vertexArrayRenderAtomList ="+vertexArrayRenderAtomList); + */ + // Send down the model view only once, if its not of type text + if ((primaryMoleculeType & (TEXT3D_MOLECULE| ORIENTEDSHAPE3D_MOLECULE)) == 0) { + if (primaryRenderAtomList != null) { + if ((primaryRenderMethod != VirtualUniverse.mc.getDisplayListRenderMethod()) || + modeSupportDL) { + if (primaryMoleculeType != SEPARATE_DLIST_PER_RINFO_MOLECULE) { + + if ((primaryRenderMethod != VirtualUniverse.mc.getDisplayListRenderMethod()) && + (pass == TextureBin.USE_DISPLAYLIST)) { + pass = TextureBin.USE_VERTEXARRAY; + } + if (primaryRenderMethod.render(this, cv, pass, primaryRenderAtomList,dirtyBits)) + isVisible = true; + } + else { + if (renderBin.dlistRenderMethod.renderSeparateDlistPerRinfo(this, cv, pass,primaryRenderAtomList,dirtyBits)) + isVisible = true; + + } + } else { + if (pass == TextureBin.USE_DISPLAYLIST) { + pass = TextureBin.USE_VERTEXARRAY; + } + if(cachedVertexArrayRenderMethod.render(this, cv, pass, + primaryRenderAtomList, + dirtyBits)) { + isVisible = true; + } + } + } + } + else { // TEXT3D or ORIENTEDSHAPE3D + if (primaryRenderAtomList != null) { + if (pass == TextureBin.USE_DISPLAYLIST) { + pass = TextureBin.USE_VERTEXARRAY; + } + if(primaryRenderMethod.render(this, cv, pass, primaryRenderAtomList, + dirtyBits)) { + isVisible = true; + } + } + } + + if (separateDlistRenderAtomList != null) { + if (modeSupportDL) { + if(renderBin.dlistRenderMethod. + renderSeparateDlists(this, cv, pass, + separateDlistRenderAtomList, + dirtyBits)) { + isVisible = true; + } + + } else { + if (pass == TextureBin.USE_DISPLAYLIST) { + pass = TextureBin.USE_VERTEXARRAY; + } + if(cachedVertexArrayRenderMethod.render(this, cv, pass, + separateDlistRenderAtomList, + dirtyBits)) { + isVisible = true; + } + } + + } + + // TODO: In the case of independent primitives such as quads, + // it would still be better to call multi draw arrays + if (vertexArrayRenderAtomList != null) { + if (pass == TextureBin.USE_DISPLAYLIST) { + pass = TextureBin.USE_VERTEXARRAY; + } + if(cachedVertexArrayRenderMethod.render(this, cv, pass, + vertexArrayRenderAtomList, + dirtyBits)) { + isVisible = true; + } + } + return isVisible; + } + + void updateAttributes(Canvas3D cv, int dirtyBits) { + + + boolean setTransparency = false; + + // If this is a beginning of a frame OR diff. geometryType + // then reload everything for the first rendermolecule + // System.out.println("updateAttributes"); + int bitMask = geometryType | Canvas3D.MATERIAL_DIRTY| + Canvas3D.COLORINGATTRS_DIRTY| + Canvas3D.TRANSPARENCYATTRS_DIRTY; + + /* + System.out.println("RM : updateAttributes " + this + + " dirtyBits is " + dirtyBits); + */ + + // If beginning of a frame then reload all the attributes + if ((cv.canvasDirty & bitMask) != 0) { + if ((geometryType & SURFACE) != 0) { + if (definingPolygonAttributes == null) { + cv.resetPolygonAttributes(cv.ctx); + } else { + definingPolygonAttributes.updateNative(cv.ctx); + } + cv.polygonAttributes = polygonAttributes; + } + if ((geometryType & LINE) != 0) { + if (definingLineAttributes == null) { + cv.resetLineAttributes(cv.ctx); + } else { + definingLineAttributes.updateNative(cv.ctx); + } + cv.lineAttributes = lineAttributes; + } + if ((geometryType & POINT) != 0) { + if (definingPointAttributes == null) { + cv.resetPointAttributes(cv.ctx); + } else { + definingPointAttributes.updateNative(cv.ctx); + } + cv.pointAttributes = pointAttributes; + } + + if (definingTransparency == null) { + cv.resetTransparency(cv.ctx, geometryType, + polygonMode, lineAA, pointAA); + } else { + definingTransparency.updateNative(cv.ctx, + alpha, geometryType, + polygonMode, lineAA, + pointAA); + } + cv.transparency = transparency; + + if (definingMaterial == null) { + cv.updateMaterial(cv.ctx, red, green, blue, alpha); + } else { + definingMaterial.updateNative(cv.ctx, + red, green, blue, alpha, + enableLighting); + } + cv.material = material; + cv.enableLighting = enableLighting; + + if (definingColoringAttributes == null) { + cv.resetColoringAttributes(cv.ctx, red, green, blue, + alpha, enableLighting); + } else { + definingColoringAttributes.updateNative(cv.ctx, + dRed, + dBlue, + dGreen,alpha, + enableLighting); + } + cv.coloringAttributes = coloringAttributes; + + // Use Object instead of AppearanceRetained class for + // state caching optimation for memory performance + cv.appHandle = appHandle; + } + + // assuming neighbor dirty bits ORing is implemented + // note that we need to set it to ALL_DIRTY at the + // begining of textureBin first and only do the ORing + // whenever encounter a non-visible rm + + else if (cv.renderMolecule != this && (dirtyBits != 0)) { + + // no need to download states if appHandle is the same + if (cv.appHandle != appHandle) { + + // Check if the attribute bundle in the canvas is the same + // as the attribute bundle in this renderMolecule + + if (cv.transparency != transparency && + (dirtyBits & TRANSPARENCY_DIRTY) != 0) { + setTransparency = true; + if (definingTransparency == null) { + + cv.resetTransparency(cv.ctx, geometryType, + polygonMode, lineAA, pointAA); + } else { + definingTransparency.updateNative(cv.ctx, alpha, + geometryType, polygonMode, + lineAA, pointAA); + } + cv.transparency = transparency; + } + + if (setTransparency || ((cv.enableLighting != enableLighting) || + (cv.material != material) && + (dirtyBits & MATERIAL_DIRTY) != 0)){ + if (definingMaterial == null) { + cv.updateMaterial(cv.ctx, red, green, blue, alpha); + } else { + definingMaterial.updateNative(cv.ctx, red, green, + blue, alpha, + enableLighting); + } + cv.material = material; + cv.enableLighting = enableLighting; + } + + if (((geometryType & SURFACE) != 0) && + cv.polygonAttributes != polygonAttributes && + (dirtyBits & POLYGONATTRS_DIRTY) != 0) { + + if (definingPolygonAttributes == null) { + cv.resetPolygonAttributes(cv.ctx); + } else { + definingPolygonAttributes.updateNative(cv.ctx); + } + cv.polygonAttributes = polygonAttributes; + } + + if (((geometryType & LINE) != 0) && + cv.lineAttributes != lineAttributes && + (dirtyBits & LINEATTRS_DIRTY) != 0) { + + if (definingLineAttributes == null) { + cv.resetLineAttributes(cv.ctx); + } else { + definingLineAttributes.updateNative(cv.ctx); + } + cv.lineAttributes = lineAttributes; + } + + if (((geometryType & POINT) != 0) && + cv.pointAttributes != pointAttributes && + (dirtyBits & POINTATTRS_DIRTY) != 0) { + + if (definingPointAttributes == null) { + cv.resetPointAttributes(cv.ctx); + } else { + definingPointAttributes.updateNative(cv.ctx); + } + cv.pointAttributes = pointAttributes; + } + + // Use Object instead of AppearanceRetained class for + // state caching optimation for memory performance + cv.appHandle = appHandle; + } + // no state caching for color attrs, which can also be + // changed by primitive with colors + if(setTransparency || ((dirtyBits & COLORINGATTRS_DIRTY) != 0)) { + + if (definingColoringAttributes == null) { + cv.resetColoringAttributes(cv.ctx, + red, green, blue, alpha, + enableLighting); + } else { + definingColoringAttributes.updateNative(cv.ctx, + dRed, + dBlue, + dGreen,alpha, + enableLighting); + + } + cv.coloringAttributes = coloringAttributes; + } + + } + + if ((primaryMoleculeType & (TEXT3D_MOLECULE| ORIENTEDSHAPE3D_MOLECULE)) == 0) { + Transform3D modelMatrix = + trans[localToVworldIndex[NodeRetained.LAST_LOCAL_TO_VWORLD]]; + + if (cv.modelMatrix != modelMatrix) { + cv.setModelViewMatrix(cv.ctx, cv.vworldToEc.mat, + modelMatrix); + } + } + + cv.canvasDirty &= ~bitMask; + cv.renderMolecule = this; + } + + void transparentSortRender(Canvas3D cv, int pass, TransparentRenderingInfo tinfo) { + + Transform3D modelMatrix = + trans[localToVworldIndex[NodeRetained.LAST_LOCAL_TO_VWORLD]]; + + // include this LightBin to the to-be-updated list in Canvas + cv.setStateToUpdate(Canvas3D.RENDERMOLECULE_BIT, this); + + + boolean modeSupportDL = true; + + // We have to dynamically switch between using displaymode + // mode or not instead of decide in canBeInDisplayList(), + // since polygonAttribute can be change by editable Appearance + // or editable polygonAttribute which mode we can't take + // advantage of display list mode in many cases just because + // there are three special cases to handle. + + // Another case for punting to vertex array is if pass specifies + // something other than -1. That means, we are in the + // multi-texturing multi-pass case. Then we'll use vertex array + // instead. + + if ((pass != TextureBin.USE_DISPLAYLIST) || + (texCoordSetMapLen > cv.numTexCoordSupported) || + (VirtualUniverse.mc.isD3D() && + (((definingPolygonAttributes != null) && + ((isQuadGeometryArray && + (definingPolygonAttributes.polygonMode == + PolygonAttributes.POLYGON_LINE)) || + (isTriGeometryArray && + (definingPolygonAttributes.polygonMode == + PolygonAttributes.POLYGON_POINT)))) + || + cv.texLinearMode))) { + modeSupportDL = false; + } + + // System.out.println("r.isOpaque = "+isOpaque+" rinfo = "+tinfo.rInfo+" groupType = "+tinfo.rInfo.groupType); + // Only support individual dlist or varray + // If this rInfo is a part of a bigger dlist, render as VA + // TODO: What to do with Text3D, Raster, CG? + if ((tinfo.rInfo.groupType & RenderAtom.SEPARATE_DLIST_PER_RINFO) != 0) { + RenderAtomListInfo save= tinfo.rInfo.next; + // Render only one geometry + tinfo.rInfo.next = null; + // System.out.println("cachedVertexArrayRenderMethod = "+cachedVertexArrayRenderMethod); + // System.out.println("tinfo.rInfo = "+tinfo.rInfo); + if (modeSupportDL) { + renderBin.dlistRenderMethod.renderSeparateDlistPerRinfo(this, cv, pass, + tinfo.rInfo, + ALL_DIRTY_BITS); + } + else { + cachedVertexArrayRenderMethod.render(this, cv, pass, tinfo.rInfo,ALL_DIRTY_BITS); + } + tinfo.rInfo.next = save; + } + else if ((tinfo.rInfo.groupType & (RenderAtom.VARRAY| RenderAtom.DLIST)) != 0) { + RenderAtomListInfo save= tinfo.rInfo.next; + // Render only one geometry + tinfo.rInfo.next = null; + // System.out.println("cachedVertexArrayRenderMethod = "+cachedVertexArrayRenderMethod); + // System.out.println("tinfo.rInfo = "+tinfo.rInfo); + cachedVertexArrayRenderMethod.render(this, cv, pass, tinfo.rInfo, + ALL_DIRTY_BITS); + tinfo.rInfo.next = save; + } + + // Only support individual dlist or varray + else if ((tinfo.rInfo.groupType & RenderAtom.SEPARATE_DLIST_PER_GEO) != 0) { + RenderAtomListInfo save= tinfo.rInfo.next; + tinfo.rInfo.next = null; + if (modeSupportDL) { + renderBin.dlistRenderMethod.renderSeparateDlists(this, cv, pass, + tinfo.rInfo, + ALL_DIRTY_BITS); + } + else { + cachedVertexArrayRenderMethod.render(this, cv, pass, tinfo.rInfo, + ALL_DIRTY_BITS); + } + tinfo.rInfo.next = save; + } + else { + RenderAtomListInfo save= tinfo.rInfo.next; + primaryRenderMethod.render(this, cv, pass, primaryRenderAtomList, + ALL_DIRTY_BITS); + tinfo.rInfo.next = save; + } + + } + + + /** + * This render method is used to render the transparency attributes. + * It is used in the multi-texture multi-pass case to reset the + * transparency attributes to what it was + */ + void updateTransparencyAttributes(Canvas3D cv) { + if (definingTransparency == null) { + cv.resetTransparency(cv.ctx, geometryType, polygonMode, + lineAA, pointAA); + } else { + definingTransparency.updateNative(cv.ctx, alpha, geometryType, + polygonMode, lineAA, pointAA); + } + } + + void updateDisplayList(Canvas3D cv) { + // This function only gets called when primaryRenderAtomsList are + if (primaryRenderAtomList != null) { + ((DisplayListRenderMethod)primaryRenderMethod).buildDisplayList(this, cv); + } + } + + void releaseAllPrimaryDisplayListID() { + + if (primaryRenderAtomList != null) { + if (primaryMoleculeType == SEPARATE_DLIST_PER_RINFO_MOLECULE) { + RenderAtomListInfo ra = primaryRenderAtomList; + int id; + + while (ra != null) { + id = ra.renderAtom.dlistIds[ra.index]; + + if (id > 0) { + VirtualUniverse.mc.freeDisplayListId(new Integer(id)); + ra.renderAtom.dlistIds[ra.index] = -1; + } + ra = ra.next; + } + } + else if (primaryMoleculeType == DLIST_MOLECULE) { + if (displayListIdObj != null) { + VirtualUniverse.mc.freeDisplayListId(displayListIdObj); + displayListIdObj = null; + displayListId = -1; + } + } + } + + } + + void releaseAllPrimaryDisplayListResources(Canvas3D cv) { + if (primaryRenderAtomList != null) { + if (primaryMoleculeType == SEPARATE_DLIST_PER_RINFO_MOLECULE) { + RenderAtomListInfo ra = primaryRenderAtomList; + int id; + while (ra != null) { + id = ra.renderAtom.dlistIds[ra.index]; + if (id > 0) { + cv.freeDisplayList(cv.ctx, id); + } + ra = ra.next; + } + } + else if (primaryMoleculeType == DLIST_MOLECULE) { + if (displayListId > 0) { + cv.freeDisplayList(cv.ctx, displayListId); + } + } + } + } + + void updateAllPrimaryDisplayLists(Canvas3D cv) { + // This function only gets called when primaryRenderAtomsList are + if (primaryRenderAtomList != null) { + if (primaryMoleculeType == SEPARATE_DLIST_PER_RINFO_MOLECULE) { + RenderAtomListInfo ra = primaryRenderAtomList; + while (ra != null) { + renderBin.dlistRenderMethod.buildDlistPerRinfo(ra, this, cv); + ra = ra.next; + } + } + else if(primaryMoleculeType == DLIST_MOLECULE) { + ((DisplayListRenderMethod)primaryRenderMethod).buildDisplayList(this, cv); + } + } + } + + void checkEquivalenceWithBothNeighbors(int dirtyBits) { + RenderMolecule leftRm = prev; + RenderMolecule rightRm = next; + dirtyAttrsAcrossRms = ALL_DIRTY_BITS; + boolean reload_color = true; + + if (prev != null) { + checkEquivalenceWithLeftNeighbor(prev, dirtyBits); + } + if (next != null) { + next.checkEquivalenceWithLeftNeighbor(this, dirtyBits); + } + } + + boolean reloadColor(RenderMolecule rm) { + if (((rm.vertexFormat & GeometryArray.COLOR) == 0) || + (((rm.vertexFormat & GeometryArray.COLOR) != 0) && + (vertexFormat & GeometryArray.COLOR) != 0)) { + return false; + } + return true; + } + + void checkEquivalenceWithLeftNeighbor(RenderMolecule rm, int dirtyBits) { + boolean reload_color = reloadColor(rm); + // TODO : For now ignore the dirtyBits being sent in + dirtyAttrsAcrossRms = ALL_DIRTY_BITS ; + + + + // There is some interdepenency between the different components + // in the way it is sent down to the native code + // Material is affected by transparency and coloring attrs + // Transparency is affected by poly/line/pointAA + // ColoringAttrs is affected by material and transaparency + int materialColoringDirty = (MATERIAL_DIRTY | + TRANSPARENCY_DIRTY | + COLORINGATTRS_DIRTY); + + int transparencyDirty = (TRANSPARENCY_DIRTY| + POLYGONATTRS_DIRTY | + LINEATTRS_DIRTY | + POINTATTRS_DIRTY); + + if ((dirtyAttrsAcrossRms & POLYGONATTRS_DIRTY) != 0) { + if (rm.geometryType == geometryType && + (rm.polygonAttributes == polygonAttributes || + ((rm.definingPolygonAttributes != null) && + (rm.definingPolygonAttributes.equivalent(definingPolygonAttributes))))) + dirtyAttrsAcrossRms &= ~POLYGONATTRS_DIRTY; + + } + + if ((dirtyAttrsAcrossRms & POINTATTRS_DIRTY) != 0) { + if (rm.geometryType == geometryType && + ((rm.pointAttributes == pointAttributes) || + ((rm.definingPointAttributes != null) && + (rm.definingPointAttributes.equivalent(definingPointAttributes))))) + dirtyAttrsAcrossRms &= ~POINTATTRS_DIRTY; + + } + + if ((dirtyAttrsAcrossRms & LINEATTRS_DIRTY) != 0) { + if (rm.geometryType == geometryType && + ((rm.lineAttributes == lineAttributes) || + ((rm.definingLineAttributes != null) && + (rm.definingLineAttributes.equivalent(definingLineAttributes))))) + dirtyAttrsAcrossRms &= ~LINEATTRS_DIRTY; + } + + if ((dirtyAttrsAcrossRms & materialColoringDirty) != 0) { + if (materialEquivalent(rm, reload_color)) { + dirtyAttrsAcrossRms &= ~MATERIAL_DIRTY; + } + else { + dirtyAttrsAcrossRms |= MATERIAL_DIRTY; + } + } + + + + + if ((dirtyAttrsAcrossRms & materialColoringDirty) != 0) { + if (coloringEquivalent(rm, reload_color)) { + dirtyAttrsAcrossRms &= ~COLORINGATTRS_DIRTY; + } + else { + dirtyAttrsAcrossRms |= COLORINGATTRS_DIRTY; + } + } + + if ((dirtyAttrsAcrossRms & transparencyDirty) != 0) { + if (transparencyEquivalent(rm)) { + dirtyAttrsAcrossRms &= ~TRANSPARENCY_DIRTY; + } + else { + dirtyAttrsAcrossRms |= TRANSPARENCY_DIRTY; + } + } + } + void translate() { + // System.out.println("onUpdateList = "+onUpdateList+" renderBin.localeChanged = "+renderBin.localeChanged+" rm = "+this); + int i = localToVworldIndex[NodeRetained.LAST_LOCAL_TO_VWORLD]; + + localeLocalToVworld[i].mat[0] = localToVworld[i].mat[0]; + localeLocalToVworld[i].mat[1] = localToVworld[i].mat[1]; + localeLocalToVworld[i].mat[2] = localToVworld[i].mat[2]; + localeLocalToVworld[i].mat[3] = localToVworld[i].mat[3] + localeTranslation.x ; + localeLocalToVworld[i].mat[4] = localToVworld[i].mat[4]; + localeLocalToVworld[i].mat[5] = localToVworld[i].mat[5]; + localeLocalToVworld[i].mat[6] = localToVworld[i].mat[6]; + localeLocalToVworld[i].mat[7] = localToVworld[i].mat[7]+ localeTranslation.y; + localeLocalToVworld[i].mat[8] = localToVworld[i].mat[8]; + localeLocalToVworld[i].mat[9] = localToVworld[i].mat[9]; + localeLocalToVworld[i].mat[10] = localToVworld[i].mat[10]; + localeLocalToVworld[i].mat[11] = localToVworld[i].mat[11]+ localeTranslation.z; + localeLocalToVworld[i].mat[12] = localToVworld[i].mat[12]; + localeLocalToVworld[i].mat[13] = localToVworld[i].mat[13]; + localeLocalToVworld[i].mat[14] = localToVworld[i].mat[14]; + localeLocalToVworld[i].mat[15] = localToVworld[i].mat[15]; + // System.out.println("rm = "+this+" localTovworld = "+localeLocalToVworld[i]+" localeTranslation = "+localeTranslation); + } + + + boolean isOpaque() { + if (!VirtualUniverse.mc.isD3D()) { + // D3D doesn't support line/point antialiasing + if ((geometryType & SURFACE) != 0) { + if (definingPolygonAttributes != null) { + if ((definingPolygonAttributes.polygonMode == + PolygonAttributes.POLYGON_POINT) && + (definingPointAttributes != null) && + definingPointAttributes.pointAntialiasing) { + return false; + } else if ((definingPolygonAttributes.polygonMode == + PolygonAttributes.POLYGON_LINE) && + (definingLineAttributes != null) && + definingLineAttributes.lineAntialiasing) { + return false; + } + } + } else if ((geometryType & POINT) != 0) { + if ((definingPointAttributes != null) && + definingPointAttributes.pointAntialiasing) { + return false; + } + } else if ((geometryType & LINE) != 0) { + if ((definingLineAttributes != null) && + definingLineAttributes.lineAntialiasing) { + return false; + } + } + return ((definingTransparency == null) || + (definingTransparency.transparencyMode == + TransparencyAttributes.NONE) || + (definingTransparency.transparencyMode == + TransparencyAttributes.SCREEN_DOOR)); + } else { + return ((definingTransparency == null) || + (definingTransparency.transparencyMode == + TransparencyAttributes.NONE)); + } + } + + + boolean updateNodeComponent() { + // System.out.println("soleUser = "+soleUser+" rm = "+this); + if ((soleUserCompDirty & MATERIAL_DIRTY) != 0) { + // Note: this RM is a soleUser(only then this function is called) + // and if definingMaterial == material, then the material is freq + // changed and therefore is not cloned, only other time it can be + // same is when an equivalent material is added in and this can + // never be true when a bin is a soleUser of a appearance + + // Evaluate before replacing the old Value + if (soleUser) { + boolean cloned = definingMaterial != null && definingMaterial != material; + // System.out.println("===>Rm = "+this); + + // System.out.println("===> updating node component, cloned = "+cloned+" material.changedFrequent = "+material.changedFrequent); + // System.out.println("===> definingMaterial ="+definingMaterial+" material = "+material); + + material = ((AppearanceRetained)appHandle).material; + if (material == null) + definingMaterial = null; + else { + if (material.changedFrequent != 0) { + definingMaterial = material; + } + else { + // If the one replaced is a cloned copy, then .. + if (cloned) { + definingMaterial.set(material); + } + else { + definingMaterial = (MaterialRetained)material.clone(); + } + } + } + } + evalMaterialCachedState(); + } + if ((soleUserCompDirty & LINEATTRS_DIRTY) != 0) { + if (soleUser) { + // Evaluate before replacing the old Value + boolean cloned = definingLineAttributes != null && definingLineAttributes != lineAttributes; + + lineAttributes = ((AppearanceRetained)appHandle).lineAttributes; + if (lineAttributes == null) { + lineAA = false; + definingLineAttributes = null; + } else { + if (lineAttributes.changedFrequent != 0) { + definingLineAttributes = lineAttributes; + } + else { + // If the one replaced is a cloned copy, then .. + if (cloned) { + definingLineAttributes.set(lineAttributes); + } + else { + definingLineAttributes = (LineAttributesRetained)lineAttributes.clone(); + } + } + lineAA = definingLineAttributes.lineAntialiasing; + } + } + else { + lineAA = definingLineAttributes.lineAntialiasing; + } + } + if ((soleUserCompDirty & POINTATTRS_DIRTY) != 0) { + if (soleUser) { + // Evaluate before replacing the old Value + boolean cloned = definingPointAttributes != null && definingPointAttributes != pointAttributes; + + pointAttributes = ((AppearanceRetained)appHandle).pointAttributes; + if (pointAttributes == null) { + pointAA = false; + definingPointAttributes = null; + } else { + if (pointAttributes.changedFrequent != 0) { + definingPointAttributes = pointAttributes; + } + else { + // If the one replaced is a cloned copy, then .. + if (cloned) { + definingPointAttributes.set(pointAttributes); + } + else { + definingPointAttributes = (PointAttributesRetained)pointAttributes.clone(); + } + } + pointAA = definingPointAttributes.pointAntialiasing; + } + } + else { + pointAA = definingPointAttributes.pointAntialiasing; + } + + } + if ((soleUserCompDirty & POLYGONATTRS_DIRTY) != 0) { + if (soleUser) { + // Evaluate before replacing the old Value + boolean cloned = definingPolygonAttributes != null && definingPolygonAttributes != polygonAttributes; + + + polygonAttributes = ((AppearanceRetained)appHandle).polygonAttributes; + + if (polygonAttributes == null) { + polygonMode = PolygonAttributes.POLYGON_FILL; + definingPolygonAttributes = null; + } else { + if (polygonAttributes.changedFrequent != 0) { + definingPolygonAttributes = polygonAttributes; + } + else { + // If the one replaced is a cloned copy, then .. + if (cloned) { + definingPolygonAttributes.set(polygonAttributes); + } + else { + definingPolygonAttributes = (PolygonAttributesRetained)polygonAttributes.clone(); + } + } + + polygonMode = definingPolygonAttributes.polygonMode; + } + } + else { + polygonMode = definingPolygonAttributes.polygonMode; + } + + if (polygonMode == PolygonAttributes.POLYGON_LINE) { + geometryType |= LINE; + } else if (polygonMode == PolygonAttributes.POLYGON_POINT) { + geometryType |= POINT; + } + } + + if ((soleUserCompDirty & TRANSPARENCY_DIRTY) != 0) { + if (soleUser) { + // Evaluate before replacing the old Value + boolean cloned = definingTransparency != null && definingTransparency != transparency; + transparency = ((AppearanceRetained)appHandle).transparencyAttributes; + + if (transparency == null) { + alpha = 1.0f ; + definingTransparency = null; + } else { + if (transparency.changedFrequent != 0) { + definingTransparency = transparency; + } + else { + // If the one replaced is a cloned copy, then .. + if (cloned) { + definingTransparency.set(transparency); + } + else { + definingTransparency = (TransparencyAttributesRetained)transparency.clone(); + } + } + + alpha = 1.0f - definingTransparency.transparency; + } + } + else { + alpha = 1.0f - definingTransparency.transparency; + } + } + + if ((soleUserCompDirty & COLORINGATTRS_DIRTY) != 0) { + if (soleUser) { + // Evaluate before replacing the old Value + boolean cloned = definingColoringAttributes != null && definingColoringAttributes != coloringAttributes; + + coloringAttributes = ((AppearanceRetained)appHandle).coloringAttributes; + // System.out.println("coloringAttributes and soleUser"); + // System.out.println("coloringAttributes ="+coloringAttributes); + if (coloringAttributes == null) { + definingColoringAttributes = null; + red = 1.0f; + green = 1.0f; + blue = 1.0f; + } else { + // System.out.println("coloringAttributes.changedFrequent = "+coloringAttributes.changedFrequent ); + if (coloringAttributes.changedFrequent != 0) { + definingColoringAttributes = coloringAttributes; + } + else { + // If the one replaced is a cloned copy, then .. + if (cloned) { + definingColoringAttributes.set(coloringAttributes); + } + else { + definingColoringAttributes = (ColoringAttributesRetained)coloringAttributes.clone(); + } + } + red = definingColoringAttributes.color.x; + green = definingColoringAttributes.color.y; + blue = definingColoringAttributes.color.z; + } + } + else { + red = definingColoringAttributes.color.x; + green = definingColoringAttributes.color.y; + blue = definingColoringAttributes.color.z; + } + } + // System.out.println("rm = "+this+"red = "+red+" green = "+green+" blue = "+blue); + boolean newVal = isOpaque() || inOrderedGroup; + return (isOpaqueOrInOG != newVal); + + } + + void evalMaterialCachedState() { + if (definingMaterial == null) { + enableLighting = false;; + definingMaterial = null; + dRed = 1.0f; + dGreen = 1.0f; + dBlue = 1.0f; + } + else { + if ((geometryType & RASTER) != 0) { + enableLighting = false; + dRed = 1.0f; + dGreen = 1.0f; + dBlue = 1.0f; + } else { + if (normalPresent) + enableLighting = definingMaterial.lightingEnable; + else + enableLighting = false; + dRed = definingMaterial.diffuseColor.x; + dGreen = definingMaterial.diffuseColor.y; + dBlue = definingMaterial.diffuseColor.z; + } + } + } + + + void markBitsAsDirty(int leftBits, int rightBits) { + if (prev != null) { + checkEquivalenceWithLeftNeighbor(prev, leftBits); + prev.soleUserCompDirty &= ~ALL_DIRTY_BITS; + } + else if (prevMap != null) { + checkEquivalenceWithLeftNeighbor(prevMap, leftBits); + prevMap.soleUserCompDirty &= ~ALL_DIRTY_BITS; + } + if (next != null) { + if ((next.soleUserCompDirty & ALL_DIRTY_BITS) == 0) { + next.checkEquivalenceWithLeftNeighbor(this, rightBits); + } else { + next.soleUserCompDirty = rightBits; + } + } + else if (nextMap != null) { + if ((nextMap.soleUserCompDirty & ALL_DIRTY_BITS) == 0) { + nextMap.checkEquivalenceWithLeftNeighbor(this, rightBits); + } else { + nextMap.soleUserCompDirty = rightBits; + } + } + + } + + void handleMaterialEquivalence() { + // Check if it has equivalent material to any of the "non-dirty" + // renderMolecules before this one + RenderMolecule curPrevRm = null; + RenderMolecule curNextRm = null; + boolean found = false; + int leftBits = ALL_DIRTY_BITS; + int rightBits = ALL_DIRTY_BITS; + if (prev != null) { + curPrevRm = prev.prev; + if (materialEquivalent(prev, reloadColor(prev))) { + found = true; + leftBits = (((soleUserCompDirty | prev.soleUserCompDirty) &ALL_DIRTY_BITS) & ~MATERIAL_DIRTY); + rightBits = (soleUserCompDirty & ALL_DIRTY_BITS); + markBitsAsDirty(leftBits, rightBits); + } + } + else if (!found && next != null) { + curNextRm = next.next; + + if (materialEquivalent(next, reloadColor(next))) { + found = true; + int bits = 0; + if (prev != null) + bits = prev.soleUserCompDirty; + else if (prevMap != null) + bits = prevMap.soleUserCompDirty; + + leftBits = ((soleUserCompDirty |bits) &ALL_DIRTY_BITS); + rightBits = ((soleUserCompDirty & ALL_DIRTY_BITS) & ~MATERIAL_DIRTY); + markBitsAsDirty(leftBits, rightBits); + + } + } + // try place it next to a equivalent material on the left + while (!found && curPrevRm != null) { + if (materialEquivalent(curPrevRm, reloadColor(curPrevRm))) { + found = true; + // Remove the renderMolecule from it place + prev.next = next; + prev.nextMap = nextMap; + if (next != null) { + next.prev = prev; + if ((next.soleUserCompDirty & ALL_DIRTY_BITS) == 0) { + next.checkEquivalenceWithLeftNeighbor(prev, ALL_DIRTY_BITS); + } + else { + next.soleUserCompDirty = ALL_DIRTY_BITS; + } + } + else if (nextMap != null) { + nextMap.prevMap = prev; + if ((nextMap.soleUserCompDirty & ALL_DIRTY_BITS) == 0) { + nextMap.checkEquivalenceWithLeftNeighbor(prev,ALL_DIRTY_BITS); + } + else { + nextMap.soleUserCompDirty |= ALL_DIRTY_BITS; + } + } + + // Insert it after the equivalent RM + next = curPrevRm.next; + nextMap = curPrevRm.nextMap; + curPrevRm.nextMap = null; + if (next != null) { + next.prev = this; + } + else if (nextMap != null) { + nextMap.prevMap = this; + } + prev = curPrevRm; + curPrevRm.next = this; + leftBits = (ALL_DIRTY_BITS & ~MATERIAL_DIRTY); + markBitsAsDirty(leftBits, ALL_DIRTY_BITS); + } + curPrevRm = curPrevRm.prev; + } + + // Check if it has equivalent material to any of the renderMolecules after + // this one + while (!found && curNextRm != null) { + if (materialEquivalent(curNextRm, reloadColor(curNextRm))) { + found = true; + // switch the pointers + next.prev = prev; + next.prevMap = prevMap; + if (prev != null) { + prev.next = next; + if ((next.soleUserCompDirty & ALL_DIRTY_BITS) == 0) { + next.checkEquivalenceWithLeftNeighbor(prev, ALL_DIRTY_BITS); + } + else { + next.soleUserCompDirty = ALL_DIRTY_BITS; + } + } + else if (prevMap != null) { + prevMap.nextMap = next; + if ((next.soleUserCompDirty & ALL_DIRTY_BITS) == 0) { + next.checkEquivalenceWithLeftNeighbor(prevMap, ALL_DIRTY_BITS); + } + else { + next.soleUserCompDirty = ALL_DIRTY_BITS; + } + } + + // Insert it before the equivalent RM + prev = curNextRm.prev; + prevMap = curNextRm.prevMap; + curNextRm.prevMap = null; + if (curNextRm.prev != null) { + curNextRm.prev.next = this; + } + else if (prevMap != null) { + prevMap.nextMap = this; + } + next = curNextRm; + curNextRm.prev = this; + rightBits = (ALL_DIRTY_BITS & ~MATERIAL_DIRTY); + markBitsAsDirty(ALL_DIRTY_BITS, rightBits); + } + curNextRm = curNextRm.next; + } + // If there are no equivalent ones, evaluate the dirty bits in the current place + if (!found) { + if (prev != null) { + leftBits = ((soleUserCompDirty|prev.soleUserCompDirty) & ALL_DIRTY_BITS); + } + else if (prevMap != null) { + leftBits = ((soleUserCompDirty|prevMap.soleUserCompDirty) & ALL_DIRTY_BITS); + } + if (next != null) { + rightBits = ((soleUserCompDirty|next.soleUserCompDirty) & ALL_DIRTY_BITS); + } + else if (nextMap != null) { + rightBits = ((soleUserCompDirty|nextMap.soleUserCompDirty) & ALL_DIRTY_BITS); + } + markBitsAsDirty(leftBits, rightBits); + } + + } + + void reEvaluateEquivalence () { + // If Material changed, reInsert next to a equivalent material under + // the same transform group + // to prevent unnecessary material download + // This RM may have been evaluated due to an other RM is the same list + // If not, ... + if ((soleUserCompDirty & ALL_DIRTY_BITS) != 0) { + if ((soleUserCompDirty & MATERIAL_DIRTY) != 0) { + handleMaterialEquivalence(); + } + else { + int dirtyBits = (soleUserCompDirty & ALL_DIRTY_BITS); + if (prev != null) { + checkEquivalenceWithLeftNeighbor(prev, ((dirtyBits|prev.soleUserCompDirty) & ALL_DIRTY_BITS)); + prev.soleUserCompDirty = 0; + } else if (prevMap != null) { + checkEquivalenceWithLeftNeighbor(prevMap, ((dirtyBits|prevMap.soleUserCompDirty) & ALL_DIRTY_BITS)); + prevMap.soleUserCompDirty = 0; + } + if (next != null) { + next.checkEquivalenceWithLeftNeighbor(this,((next.soleUserCompDirty|soleUserCompDirty) & ALL_DIRTY_BITS)); + } else if (nextMap != null) { + nextMap.checkEquivalenceWithLeftNeighbor(this,((nextMap.soleUserCompDirty | soleUserCompDirty) & ALL_DIRTY_BITS)); + } + } + } + soleUserCompDirty &= ~ALL_DIRTY_BITS; + } + + + boolean materialEquivalent(RenderMolecule rm, boolean reloadColor) { + if (!reloadColor) { + if (((this.material == rm.material) || + ((rm.definingMaterial != null) && + (rm.definingMaterial.equivalent(definingMaterial)))) && + rm.alpha == alpha && + enableLighting == rm.enableLighting && + (enableLighting || + (!enableLighting && + rm.red ==red && + rm.green == green && + rm.blue == blue))) { + return true; + } + } + return false; + } + + boolean coloringEquivalent(RenderMolecule rm, boolean reload_color) { + if (!reload_color) { + if (((rm.coloringAttributes == coloringAttributes) || + ((rm.definingColoringAttributes != null) && + (rm.definingColoringAttributes.equivalent(definingColoringAttributes)))) && + (!enableLighting || (enableLighting && (dRed == rm.dRed && dBlue == rm.dBlue && dGreen == rm.dGreen)))) { + return true; + } + } + return false; + } + + boolean transparencyEquivalent(RenderMolecule rm) { + if (((rm.transparency == transparency) || + ((rm.definingTransparency != null) && + (rm.definingTransparency.equivalent(definingTransparency))) && + (rm.definingTransparency.transparencyMode < TransparencyAttributes.SCREEN_DOOR && + blendOn() == rm.blendOn()))) { + return true; + } + return false; + } + + boolean blendOn() { + if (lineAA && ((((geometryType & LINE) != 0) || + polygonMode == PolygonAttributes.POLYGON_LINE))) { + return true; + } + if (pointAA && ((((geometryType & POINT) != 0) || + polygonMode == PolygonAttributes.POLYGON_POINT))) { + return true; + } + return false; + } + + VirtualUniverse getVirtualUniverse() { + return null; + } + + + void handleLocaleChange() { + if (locale == renderBin.locale) { + if (localToVworld != localeLocalToVworld) { + if (localeTranslation != null) { + // return to the freelist; + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + localeLocalToVworld[0]); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + localeLocalToVworld[1]); + } + localeLocalToVworld = localToVworld; + localeTranslation = null; + } + } + else { + // Using the localToVworl then, go back to making a new copy + if (localeTranslation == null) { + localeLocalToVworld = new Transform3D[2]; + /* + localeLocalToVworld[0] = VirtualUniverse.mc.getTransform3D(null); + localeLocalToVworld[1] = VirtualUniverse.mc.getTransform3D(null); + */ + + localeLocalToVworld[0] = new Transform3D(); + localeLocalToVworld[1] = new Transform3D(); + + localeTranslation = new Vector3d(); + locale.hiRes.difference(renderBin.locale.hiRes, localeTranslation); + translate(); + int i = localToVworldIndex[NodeRetained.CURRENT_LOCAL_TO_VWORLD]; + + localeLocalToVworld[i].mat[0] = localToVworld[i].mat[0]; + localeLocalToVworld[i].mat[1] = localToVworld[i].mat[1]; + localeLocalToVworld[i].mat[2] = localToVworld[i].mat[2]; + localeLocalToVworld[i].mat[3] = localToVworld[i].mat[3] + localeTranslation.x ; + localeLocalToVworld[i].mat[4] = localToVworld[i].mat[4]; + localeLocalToVworld[i].mat[5] = localToVworld[i].mat[5]; + localeLocalToVworld[i].mat[6] = localToVworld[i].mat[6]; + localeLocalToVworld[i].mat[7] = localToVworld[i].mat[7]+ localeTranslation.y; + localeLocalToVworld[i].mat[8] = localToVworld[i].mat[8]; + localeLocalToVworld[i].mat[9] = localToVworld[i].mat[9]; + localeLocalToVworld[i].mat[10] = localToVworld[i].mat[10]; + localeLocalToVworld[i].mat[11] = localToVworld[i].mat[11]+ localeTranslation.z; + localeLocalToVworld[i].mat[12] = localToVworld[i].mat[12]; + localeLocalToVworld[i].mat[13] = localToVworld[i].mat[13]; + localeLocalToVworld[i].mat[14] = localToVworld[i].mat[14]; + localeLocalToVworld[i].mat[15] = localToVworld[i].mat[15]; + } + } + + trans = localeLocalToVworld; + } + + + /** + * updateNodeComponentCheck is called for each soleUser RenderMolecule + * into which new renderAtom has been added. This method is called before + * updateNodeComponent() to allow RenderMolecule to catch any node + * component changes that have been missed because the changes + * come when there is no active renderAtom associated with the + * TextureBin. See bug# 4503926 for details. + */ + public void updateNodeComponentCheck() { + + // If the renderMolecule has been removed, do nothing .. + if ((onUpdateList &ON_UPDATE_CHECK_LIST ) == 0) + return; + + onUpdateList &= ~ON_UPDATE_CHECK_LIST; + NodeComponentRetained nc = (NodeComponentRetained)appHandle; + if ((nc.compChanged & RM_COMPONENTS) != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= (nc.compChanged & RM_COMPONENTS); + } + if (definingPolygonAttributes != null && + definingPolygonAttributes == polygonAttributes) { + if (definingPolygonAttributes.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= POLYGONATTRS_DIRTY; + } + } + if (definingLineAttributes != null && + definingLineAttributes == lineAttributes) { + if (definingLineAttributes.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= LINEATTRS_DIRTY; + } + } + if (definingPointAttributes != null && + definingPointAttributes.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= POINTATTRS_DIRTY; + } + + if (definingMaterial != null && + definingMaterial == material) { + if (definingMaterial.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= MATERIAL_DIRTY; + } + } + + if (definingColoringAttributes != null && + definingColoringAttributes == coloringAttributes) { + if (definingColoringAttributes.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= COLORINGATTRS_DIRTY; + } + } + + if (definingTransparency != null && + definingTransparency == transparency) { + if (definingTransparency.compChanged != 0) { + if ((soleUserCompDirty& ALL_DIRTY_BITS) == 0 ) { + renderBin.rmUpdateList.add(this); + } + soleUserCompDirty |= TRANSPARENCY_DIRTY; + } + } + } +} + + + + diff --git a/src/classes/share/javax/media/j3d/Renderer.java b/src/classes/share/javax/media/j3d/Renderer.java new file mode 100644 index 0000000..eabfa6e --- /dev/null +++ b/src/classes/share/javax/media/j3d/Renderer.java @@ -0,0 +1,1688 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +/* + * Portions of this code were derived from work done by the Blackdown + * group (www.blackdown.org), who did the initial Linux implementation + * of the Java 3D API. + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.awt.*; +import java.awt.image.*; +import java.util.*; + + +class Renderer extends J3dThread { + int objectId = -1; + // This action causes this thread to wait + static final int WAIT = 0; + + // This action causes this thread to notify the view, and then wait. + static final int NOTIFY_AND_WAIT = 1; + + // This action causes this thread to be notified + static final int NOTIFY = 2; + + // The following are DecalGroup rendering states + static final int DECAL_NONE = 0; + static final int DECAL_1ST_CHILD = 1; + static final int DECAL_NTH_CHILD = 2; + + // stuff for scene antialiasing + static final int NUM_ACCUMULATION_SAMPLES = 8; + + static final float ACCUM_SAMPLES_X[] = + { -0.54818f, 0.56438f, 0.39462f, -0.54498f, + -0.83790f, -0.39263f, 0.32254f, 0.84216f}; + + static final float ACCUM_SAMPLES_Y[] = + { 0.55331f, -0.53495f, 0.41540f, -0.52829f, + 0.82102f, -0.27383f, 0.09133f, -0.84399f}; + + static final float accumValue = 1.0f / NUM_ACCUMULATION_SAMPLES; + + // The following are Render arguments + static final int RENDER = 0; + static final int SWAP = 1; + static final int REQUESTRENDER = 2; + static final int REQUESTCLEANUP = 3; + + // Renderer Structure used for the messaging to the renderer + RendererStructure rendererStructure = new RendererStructure(); + + + // vworldtoVpc matrix for background geometry + Transform3D bgVworldToVpc = new Transform3D(); + + long lasttime; + long currtime; + float numframes = 0.0f; + static final boolean doTiming = false; + + private static int numInstances = 0; + private int instanceNum = -1; + + // Local copy of sharedStereZBuffer flag + boolean sharedStereoZBuffer; + + // This is the id for the underlying sharable graphics context + long sharedCtx = 0; + + // since the sharedCtx id can be the same as the previous one, + // we need to keep a time stamp to differentiate the contexts with the + // same id + long sharedCtxTimeStamp = 0; + + // display id - to free shared context + long display; + int window; + + /** + * This is the id of the current rendering context + */ + long currentCtx = -1; + + // an unique bit to identify this renderer + int rendererBit = 0; + + // List of renderMolecules that are dirty due to additions + // or removal of renderAtoms from their display list set + // of renderAtoms + ArrayList dirtyRenderMoleculeList = new ArrayList(); + + // List of individual dlists that need to be rebuilt + ArrayList dirtyRenderAtomList = new ArrayList(); + + // List of (Rm, rInfo) pair of individual dlists that need to be rebuilt + ArrayList dirtyDlistPerRinfoList = new ArrayList(); + + + // Texture and display list that should be freed + ArrayList textureIdResourceFreeList = new ArrayList(); + ArrayList displayListResourceFreeList = new ArrayList(); + + // Texture that should be reload + ArrayList textureReloadList = new ArrayList(); + + + // This is a local copy of canvas view cache. It is used as a data storage for the + // renderer. Note: This isn't the "real" canvasViewCache references by the Canvas. + CanvasViewCache copyOfCvCache = new CanvasViewCache(null, null, null); + + J3dMessage[] renderMessage; + + // The screen for this Renderer. Note that this renderer may share + // by both on screen and off screen. When view unregister, we need + // to set both reference to null. + Screen3D onScreen; + Screen3D offScreen; + + // full screen anti-aliasing projection matrices + double accumLeftProjMat[] = new double[16]; + double accumRightProjMat[] = new double[16]; + double accumInfLeftProjMat[] = new double[16]; + double accumInfRightProjMat[] = new double[16]; + + // rendering messages + J3dMessage m[]; + int nmesg = 0; + + // List of contexts created + ArrayList listOfCtxs = new ArrayList(); + + // Parallel list of canvases + ArrayList listOfCanvases = new ArrayList(); + + + boolean needToRebuildDisplayList = false; + boolean needToResendTextureDown = false; + + // True when either one of dirtyRenderMoleculeList, + // dirtyDlistPerRinfoList, dirtyRenderAtomList size > 0 + boolean dirtyDisplayList = false; + + // Remember OGL context resources to free + // before context is destroy. + // It is used when sharedCtx = true; + ArrayList textureIDResourceTable = new ArrayList(5); + + native void D3DCleanUp(); + + private synchronized int newInstanceNum() { + return (++numInstances); + } + + int getInstanceNum() { + if (instanceNum == -1) + instanceNum = newInstanceNum(); + return instanceNum; + } + + /** + * Constructs a new Renderer + */ + Renderer(ThreadGroup t) { + super(t); + setName("J3D-Renderer-" + getInstanceNum()); + + type = J3dThread.RENDER_THREAD; + rendererBit = VirtualUniverse.mc.getRendererBit(); + renderMessage = new J3dMessage[1]; + } + + + /** + * The main loop for the renderer. + */ + void doWork(long referenceTime) { + RenderAtom ra; + RenderBin renderBin = null; + Canvas3D cv, canvas=null; + Object firstArg; + View view = null; + Color3f col; + int stereo_mode; + int num_stereo_passes, num_render_passes, num_accum_passes = 1; + int pass, apass, i, j, k; + boolean doAccum = false; + double accumDx = 0.0f, accumDy = 0.0f; + double accumDxFactor = 1.0f, accumDyFactor = 1.0f; + + double accumLeftX = 0.0, accumLeftY = 0.0, + accumRightX = 0.0, accumRightY = 0.0, + accumInfLeftX = 0.0, accumInfLeftY = 0.0, + accumInfRightX = 0.0, accumInfRightY = 0.0; + int opArg, status; + boolean done = false; + Transform3D t3d = null; + + opArg = ((Integer)args[0]).intValue(); + + try { + if (opArg == SWAP) { + + Object [] swapArray = (Object[])args[2]; + + view = (View)args[3]; + + for (i=0; i<swapArray.length; i++) { + cv = (Canvas3D) swapArray[i]; + if (!cv.isRunning) { + continue; + } + + doneSwap: try { + + if (!cv.validCanvas) { + continue; + } + + if (cv.active && (cv.ctx != 0) && + (cv.view != null) && (cv.imageReady)) { + if (cv.useDoubleBuffer) { + synchronized (cv.drawingSurfaceObject) { + if (cv.validCtx) { + if (VirtualUniverse.mc.doDsiRenderLock) { + // Set doDsiLock flag for rendering based on system + // property, If we force DSI lock for swap + // buffer, we lose most of the parallelism that having + // multiple renderers gives us. + + if (!cv.drawingSurfaceObject.renderLock()) { + break doneSwap; + } + cv.makeCtxCurrent(); + cv.syncRender(cv.ctx, true); + status = cv.swapBuffers(cv.ctx, + cv.screen.display, + cv.window); + if (status != Canvas3D.NOCHANGE) { + cv.resetRendering(status); + } + cv.drawingSurfaceObject.unLock(); + } else { + cv.makeCtxCurrent(); + + cv.syncRender(cv.ctx, true); + status = cv.swapBuffers(cv.ctx, + cv.screen.display, + cv.window); + if (status != Canvas3D.NOCHANGE) { + cv.resetRendering(status); + } + + } + } + } + } + cv.view.inCanvasCallback = true; + try { + cv.postSwap(); + } catch (RuntimeException e) { + System.err.println("Exception occurred during Canvas3D callback:"); + e.printStackTrace(); + } + // reset flag + cv.imageReady = false; + cv.view.inCanvasCallback = false; + // Clear canvasDirty bit ONLY when postSwap() success + + // Set all dirty bits except environment set and lightbin + // they are only set dirty if the last used light bin or + // environment set values for this canvas change between + // one frame and other + + if (!cv.ctxChanged) { + cv.canvasDirty = (0xffff & ~(Canvas3D.LIGHTBIN_DIRTY | + Canvas3D.LIGHTENABLES_DIRTY | + Canvas3D.AMBIENTLIGHT_DIRTY | + Canvas3D.MODELCLIP_DIRTY | + Canvas3D.VWORLD_SCALE_DIRTY | + Canvas3D.FOG_DIRTY)); + // Force reload of transform next frame + cv.modelMatrix = null; + + // Force the cached renderAtom to null + cv.ra = null; + } else { + cv.ctxChanged = false; + } + } + } catch (NullPointerException ne) { + //ne.printStackTrace(); + if (VirtualUniverse.mc.doDsiRenderLock) { + cv.drawingSurfaceObject.unLock(); + } + } + } + + if (view != null) { // STOP_TIMER + // incElapsedFrames() is delay until MC:updateMirroObject + if (view.viewCache.getDoHeadTracking()) { + VirtualUniverse.mc.sendRunMessage(view, + J3dThread.RENDER_THREAD); + } + } + + } else if (opArg == REQUESTCLEANUP) { + Integer mtype = (Integer) args[2]; + + if (mtype == MasterControl.REMOVEALLCTXS_CLEANUP) { + // from MasterControl when View is last views + removeAllCtxs(); + } else if (mtype == MasterControl.FREECONTEXT_CLEANUP) { + // from MasterControl freeContext(View v) + cv = (Canvas3D) args[1]; + removeCtx(cv, cv.screen.display, cv.window, cv.ctx, + true, true); + } else if (mtype == MasterControl.RESETCANVAS_CLEANUP) { + // from MasterControl RESET_CANVAS postRequest + cv = (Canvas3D) args[1]; + if (cv.ctx != 0) { + cv.makeCtxCurrent(); + } + cv.freeContextResources(cv.screen.renderer, true, cv.ctx); + } else if (mtype == MasterControl.REMOVECTX_CLEANUP) { + // from Canvas3D removeCtx() postRequest + Object[] obj = (Object []) args[1]; + Canvas3D c = (Canvas3D) obj[0]; + removeCtx(c, + ((Long) obj[1]).longValue(), + ((Integer) obj[2]).intValue(), + ((Long) obj[3]).longValue(), + false, !c.offScreen); + } + return; + } else { // RENDER || REQUESTRENDER + int renderType; + nmesg = 0; + int totalMessages = 0; + if (opArg == RENDER) { + m = renderMessage; + m[0] = VirtualUniverse.mc.getMessage(); + m[0].type = J3dMessage.RENDER_RETAINED; + m[0].incRefcount(); + m[0].args[0] = args[1]; + totalMessages = 1; + } else { // REQUESTRENDER + m = rendererStructure.getMessages(); + totalMessages = rendererStructure.getNumMessage(); + if (totalMessages <= 0) { + return; + } + } + + + doneRender: while (nmesg < totalMessages) { + + firstArg = m[nmesg].args[0]; + + if (firstArg == null) { + Object secondArg = m[nmesg].args[1]; + if (secondArg instanceof Canvas3D) { + // message from Canvas3Ds to destroy Context + Integer reqType = (Integer) m[nmesg].args[2]; + Canvas3D c = (Canvas3D) secondArg; + if (reqType == MasterControl.SET_GRAPHICSCONFIG_FEATURES) { + GraphicsConfiguration gc = c.graphicsConfiguration; + NativeConfigTemplate3D nct = + GraphicsConfigTemplate3D.nativeTemplate; + if (c.offScreen) { + // offScreen canvas neither supports + // double buffering nor stereo + c.doubleBufferAvailable = false; + c.stereoAvailable = false; + } else { + c.doubleBufferAvailable = nct.hasDoubleBuffer(gc); + c.stereoAvailable = nct.hasStereo(gc); + } + c.sceneAntialiasingMultiSamplesAvailable = + nct.hasSceneAntialiasingMultiSamples(gc); + if (c.sceneAntialiasingMultiSamplesAvailable) { + c.sceneAntialiasingAvailable = true; + } else { + c.sceneAntialiasingAvailable = + nct.hasSceneAntialiasingAccum(gc); + } + GraphicsConfigTemplate3D.runMonitor(J3dThread.NOTIFY); + } else if (reqType == MasterControl.SET_QUERYPROPERTIES){ + c.createQueryContext(); + // currentCtx change after we create a new context + GraphicsConfigTemplate3D.runMonitor(J3dThread.NOTIFY); + currentCtx = -1; + } + } else if (secondArg instanceof Integer) { + // message from TextureRetained finalize() method + // to free texture id + freeTextureID(((Integer) secondArg).intValue(), (String)m[nmesg].args[2]); + } else if (secondArg instanceof GeometryArrayRetained) { + // message from GeometryArrayRetained + // clearLive() to free D3D array + ((GeometryArrayRetained) secondArg).freeD3DArray(false); + } else if (secondArg instanceof GraphicsConfigTemplate3D) { + GraphicsConfigTemplate3D gct = + (GraphicsConfigTemplate3D) secondArg; + Integer reqType = (Integer) m[nmesg].args[2]; + if (reqType == MasterControl.GETBESTCONFIG) { + gct.testCfg = + gct.nativeTemplate.getBestConfiguration(gct, + (GraphicsConfiguration []) gct.testCfg); + } else if (reqType == MasterControl.ISCONFIGSUPPORT) { + if (gct.nativeTemplate.isGraphicsConfigSupported(gct, + (GraphicsConfiguration) gct.testCfg)) { + gct.testCfg = Boolean.TRUE; + } else { + gct.testCfg = Boolean.FALSE; + } + } + gct.runMonitor(J3dThread.NOTIFY); + } + + m[nmesg++].decRefcount(); + continue; + } + + canvas = (Canvas3D) firstArg; + + renderType = m[nmesg].type; + + if ((canvas.view == null) || !canvas.firstPaintCalled) { + // This happen when the canvas just remove from the View + if (renderType == J3dMessage.RENDER_OFFSCREEN) { + canvas.offScreenRendering = false; + } + m[nmesg++].decRefcount(); + continue; + } + + if (!canvas.validCanvas && + (renderType != J3dMessage.RENDER_OFFSCREEN)) { + m[nmesg++].decRefcount(); + continue; + } + + if (renderType == J3dMessage.RESIZE_CANVAS) { + canvas.d3dResize(); + // render the image again after resize + VirtualUniverse.mc.sendRunMessage(canvas.view, J3dThread.RENDER_THREAD); + m[nmesg++].decRefcount(); + } else if (renderType == J3dMessage.TOGGLE_CANVAS) { + canvas.d3dToggle(); + VirtualUniverse.mc.sendRunMessage(canvas.view, J3dThread.RENDER_THREAD); + m[nmesg++].decRefcount(); + } else if (renderType == J3dMessage.RENDER_IMMEDIATE) { + int command = ((Integer)m[nmesg].args[1]).intValue(); + //System.out.println("command= " + command); + if (needToResendTextureDown) { + VirtualUniverse.mc.resendTexTimestamp++; + needToResendTextureDown = false; + } + + if (canvas.ctx != 0) { + // ctx may not construct until doClear(); + canvas.beginScene(); + } + + switch (command) { + case GraphicsContext3D.CLEAR: + canvas.graphicsContext3D.doClear(); + break; + case GraphicsContext3D.DRAW: + canvas.graphicsContext3D.doDraw( + (Geometry)m[nmesg].args[2]); + break; + case GraphicsContext3D.SWAP: + canvas.doSwap(); + break; + case GraphicsContext3D.READ_RASTER: + canvas.graphicsContext3D.doReadRaster( + (Raster)m[nmesg].args[2]); + break; + case GraphicsContext3D.SET_APPEARANCE: + canvas.graphicsContext3D.doSetAppearance( + (Appearance)m[nmesg].args[2]); + break; + case GraphicsContext3D.SET_BACKGROUND: + canvas.graphicsContext3D.doSetBackground( + (Background)m[nmesg].args[2]); + break; + case GraphicsContext3D.SET_FOG: + canvas.graphicsContext3D.doSetFog( + (Fog)m[nmesg].args[2]); + break; + case GraphicsContext3D.SET_LIGHT: + canvas.graphicsContext3D.doSetLight( + (Light)m[nmesg].args[2], + ((Integer)m[nmesg].args[3]).intValue()); + break; + case GraphicsContext3D.INSERT_LIGHT: + canvas.graphicsContext3D.doInsertLight( + (Light)m[nmesg].args[2], + ((Integer)m[nmesg].args[3]).intValue()); + break; + case GraphicsContext3D.REMOVE_LIGHT: + canvas.graphicsContext3D.doRemoveLight( + ((Integer)m[nmesg].args[2]).intValue()); + break; + case GraphicsContext3D.ADD_LIGHT: + canvas.graphicsContext3D.doAddLight( + (Light)m[nmesg].args[2]); + break; + case GraphicsContext3D.SET_HI_RES: + canvas.graphicsContext3D.doSetHiRes( + (HiResCoord)m[nmesg].args[2]); + break; + case GraphicsContext3D.SET_MODEL_TRANSFORM: + t3d = (Transform3D)m[nmesg].args[2]; + canvas.graphicsContext3D.doSetModelTransform(t3d); + // return t3d to freelist. t3d was gotten from GraphicsContext3D + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + t3d); + break; + case GraphicsContext3D.MULTIPLY_MODEL_TRANSFORM: + t3d = (Transform3D)m[nmesg].args[2]; + canvas.graphicsContext3D.doMultiplyModelTransform(t3d); + // return t3d to freelist. t3d was gotten from GraphicsContext3D + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + t3d); + break; + case GraphicsContext3D.SET_SOUND: + canvas.graphicsContext3D.doSetSound( + (Sound)m[nmesg].args[2], + ((Integer)m[nmesg].args[3]).intValue()); + break; + case GraphicsContext3D.INSERT_SOUND: + canvas.graphicsContext3D.doInsertSound( + (Sound)m[nmesg].args[2], + ((Integer)m[nmesg].args[3]).intValue()); + break; + case GraphicsContext3D.REMOVE_SOUND: + canvas.graphicsContext3D.doRemoveSound( + ((Integer)m[nmesg].args[2]).intValue()); + break; + case GraphicsContext3D.ADD_SOUND: + canvas.graphicsContext3D.doAddSound( + (Sound)m[nmesg].args[2]); + break; + case GraphicsContext3D.SET_AURAL_ATTRIBUTES: + canvas.graphicsContext3D.doSetAuralAttributes( + (AuralAttributes)m[nmesg].args[2]); + break; + case GraphicsContext3D.SET_BUFFER_OVERRIDE: + canvas.graphicsContext3D.doSetBufferOverride( + ((Boolean)m[nmesg].args[2]).booleanValue()); + break; + case GraphicsContext3D.SET_FRONT_BUFFER_RENDERING: + canvas.graphicsContext3D.doSetFrontBufferRendering( + ((Boolean)m[nmesg].args[2]).booleanValue()); + break; + case GraphicsContext3D.SET_STEREO_MODE: + canvas.graphicsContext3D.doSetStereoMode( + ((Integer)m[nmesg].args[2]).intValue()); + break; + case GraphicsContext3D.FLUSH: + canvas.graphicsContext3D.doFlush( + ((Boolean)m[nmesg].args[2]).booleanValue()); + break; + case GraphicsContext3D.FLUSH2D: + canvas.graphics2D.doFlush(); + break; + case GraphicsContext3D.DRAWANDFLUSH2D: + Object ar[] = m[nmesg].args; + canvas.graphics2D.doDrawAndFlushImage( + (BufferedImage) ar[2], + ((Point) ar[3]).x, + ((Point) ar[3]).y, + (ImageObserver) ar[4]); + break; + case GraphicsContext3D.SET_MODELCLIP: + canvas.graphicsContext3D.doSetModelClip( + (ModelClip)m[nmesg].args[2]); + break; + default: + break; + } + + if (canvas.ctx != 0) { + canvas.endScene(); + } + m[nmesg++].decRefcount(); + } else { // retained mode rendering + m[nmesg++].decRefcount(); + + ImageComponent2DRetained offBufRetained = null; + + if (renderType == J3dMessage.RENDER_OFFSCREEN) { + if (canvas.window == 0 || !canvas.active) { + canvas.offScreenRendering = false; + continue; + } else { + offBufRetained = (ImageComponent2DRetained) + canvas.offScreenBuffer.retained; + + if (offBufRetained.isByReference()) { + offBufRetained.geomLock.getLock(); + offBufRetained.evaluateExtensions( + canvas.extensionsSupported); + } + } + } else if (!canvas.active) { + continue; + } + + boolean background_image_update = false; + + renderBin = canvas.view.renderBin; + + // setup rendering context + + // We need to catch NullPointerException when the dsi + // gets yanked from us during a remove. + + if (canvas.useSharedCtx) { + if (sharedCtx == 0) { + display = canvas.screen.display; + + // Always lock for context create + if (!canvas.drawingSurfaceObject.renderLock()) { + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + break doneRender; + } + + synchronized (VirtualUniverse.mc.contextCreationLock) { + sharedCtx = + canvas.createContext(canvas.screen.display, + canvas.window, + canvas.vid, + canvas.visInfo, + 0, true, + canvas.offScreen); + if (sharedCtx == 0) { + canvas.drawingSurfaceObject.unLock(); + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + break doneRender; + } + sharedCtxTimeStamp = + VirtualUniverse.mc.getContextTimeStamp(); + + needToRebuildDisplayList = true; + } + + canvas.drawingSurfaceObject.unLock(); + } + } + if (canvas.ctx == 0) { + + display = canvas.screen.display; + + // Always lock for context create + if (!canvas.drawingSurfaceObject.renderLock()) { + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + break doneRender; + } + + synchronized (VirtualUniverse.mc.contextCreationLock) { + canvas.ctx = + canvas.createContext(canvas.screen.display, + canvas.window, canvas.vid, + canvas.visInfo, sharedCtx, + false, canvas.offScreen); + + + + if (canvas.ctx == 0) { + canvas.drawingSurfaceObject.unLock(); + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + break doneRender; + } + + if (canvas.graphics2D != null) { + canvas.graphics2D.init(); + } + + canvas.ctxTimeStamp = + VirtualUniverse.mc.getContextTimeStamp(); + listOfCtxs.add(new Long(canvas.ctx)); + listOfCanvases.add(canvas); + + if (renderBin.nodeComponentList.size() > 0) { + for (i = 0; i < renderBin.nodeComponentList.size(); i++) { + NodeComponentRetained nc = (NodeComponentRetained)renderBin.nodeComponentList.get(i); + nc.evaluateExtensions(canvas.extensionsSupported); + } + } + + + // query for the number of texture units supported + if (canvas.multiTexAccelerated) { + canvas.numTexUnitSupported = + canvas.getTextureUnitCount(canvas.ctx); + if (VirtualUniverse.mc.textureUnitMax < canvas.numTexUnitSupported) { + canvas.numTexUnitSupported = VirtualUniverse.mc.textureUnitMax; + } + } + + // enable separate specular color + canvas.enableSeparateSpecularColor(); + + } + + + // create the cache texture state in canvas + // for state download checking purpose + + if (canvas.texUnitState == null) { + canvas.texUnitState = + new TextureUnitStateRetained[ + canvas.numTexCoordSupported]; + for (int t = 0; t < canvas.numTexCoordSupported; + t++) { + canvas.texUnitState[t] = + new TextureUnitStateRetained(); + canvas.texUnitState[t].texture = null; + canvas.texUnitState[t].mirror = null; + } + } + + + // also create the texture unit state map + // which is a mapping from texture unit state to + // the actual underlying texture unit + + if (canvas.texUnitStateMap == null) { + canvas.texUnitStateMap = + new int[canvas.numTexCoordSupported]; + } + + canvas.resetImmediateRendering(Canvas3D.NOCHANGE); + canvas.drawingSurfaceObject.contextValidated(); + + if (!canvas.useSharedCtx) { + canvas.needToRebuildDisplayList = true; + } + canvas.drawingSurfaceObject.unLock(); + } else { + if (canvas.isRunning) { + canvas.makeCtxCurrent(); + } + } + + + if (renderBin != null) { + if ((VirtualUniverse.mc.doDsiRenderLock) && + (!canvas.drawingSurfaceObject.renderLock())) { + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + break doneRender; + } + + if (needToResendTextureDown) { + VirtualUniverse.mc.resendTexTimestamp++; + needToResendTextureDown = false; + } + // handle free resource + if (canvas.useSharedCtx) { + freeResourcesInFreeList(canvas); + } else { + canvas.freeResourcesInFreeList(canvas.ctx); + } + + // save the BACKGROUND_IMAGE_DIRTY before canvas.updateViewCache + // clean it + background_image_update = + ((canvas.cvDirtyMask & Canvas3D.BACKGROUND_IMAGE_DIRTY) != 0); + + // copyOfcvCache is a copy of canvas view + // cache. It is used as a data storage for the + // renderer. Note: This isn't the "real" + // canvasViewCache references by the Canvas. + // + // Note : For performance reason, copyOfcvCache + // doesn't contain are valid canvasViewCache info., + // only data needed by the renderer are stored. + // + // The valid data are : useStereo, canvasWidth, + // canvasHeight, leftProjection, rightProjection, + // leftVpcToEc, rightVpcToEc, leftFrustumPlanes, + // rightFrustumPlanes, vpcToVworld and vworldToVpc. + + if (VirtualUniverse.mc.doDsiRenderLock) { + canvas.drawingSurfaceObject.unLock(); + } + + // Deadlock if we include updateViewCache in + // drawingSurfaceObject sync. + canvas.updateViewCache(false, copyOfCvCache, null, + renderBin.geometryBackground != null); + + if ((VirtualUniverse.mc.doDsiRenderLock) && + (!canvas.drawingSurfaceObject.renderLock())) { + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + break doneRender; + } + + // setup viewport + canvas.setViewport(canvas.ctx, 0, 0, + copyOfCvCache.getCanvasWidth(), + copyOfCvCache.getCanvasHeight()); + + + + // rebuild the display list of all dirty renderMolecules. + if (canvas.useSharedCtx) { + if (needToRebuildDisplayList) { + renderBin.updateAllRenderMolecule( + this, canvas); + needToRebuildDisplayList = false; + } + + if (dirtyDisplayList) { + renderBin.updateDirtyDisplayLists(canvas, + dirtyRenderMoleculeList, + dirtyDlistPerRinfoList, + dirtyRenderAtomList,true); + dirtyDisplayList = false; + } + + // for shared context, download textures upfront + // to minimize the context switching overhead + int sz = textureReloadList.size(); + + if (sz > 0) { + for (j = sz-1; j>=0; j--) { + ((TextureRetained)textureReloadList.get(j)). + reloadTextureSharedContext(canvas); + } + textureReloadList.clear(); + } + + } else { + // update each canvas + if (canvas.needToRebuildDisplayList) { + renderBin.updateAllRenderMolecule(canvas); + canvas.needToRebuildDisplayList = false; + } + if (canvas.dirtyDisplayList) { + renderBin.updateDirtyDisplayLists(canvas, + canvas.dirtyRenderMoleculeList, + canvas.dirtyDlistPerRinfoList, + canvas.dirtyRenderAtomList, false); + canvas.dirtyDisplayList = false; + } + } + + // lighting setup + if (canvas.view.localEyeLightingEnable != + canvas.ctxEyeLightingEnable) { + canvas.ctxUpdateEyeLightingEnable(canvas.ctx, + canvas.view.localEyeLightingEnable); + canvas.ctxEyeLightingEnable = + canvas.view.localEyeLightingEnable; + } + + + // stereo setup + boolean useStereo = copyOfCvCache.getUseStereo(); + if (useStereo) { + num_stereo_passes = 2; + stereo_mode = Canvas3D.FIELD_LEFT; + + sharedStereoZBuffer = + VirtualUniverse.mc.sharedStereoZBuffer; + } else { + num_stereo_passes = 1; + stereo_mode = Canvas3D.FIELD_ALL; + + // just in case user set flag - + // disable since we are not in stereo + sharedStereoZBuffer = false; + } + + // full screen anti-aliasing setup + if (canvas.view.getSceneAntialiasingEnable() && + canvas.sceneAntialiasingAvailable) { + if (!VirtualUniverse.mc.isD3D() && + ((canvas.extensionsSupported & Canvas3D.ARB_MULTISAMPLE) == 0) || + !canvas.sceneAntialiasingMultiSamplesAvailable) { + doAccum = true; + num_accum_passes = NUM_ACCUMULATION_SAMPLES; + + System.arraycopy( + copyOfCvCache.getLeftProjection().mat, + 0, accumLeftProjMat, 0, 16); + + + accumDxFactor = ( + canvas.canvasViewCache.getPhysicalWindowWidth() / + canvas.canvasViewCache.getCanvasWidth())*canvas.view.fieldOfView; + + accumDyFactor = ( + canvas.canvasViewCache.getPhysicalWindowHeight() / + canvas.canvasViewCache.getCanvasHeight())*canvas.view.fieldOfView; + + + accumLeftX = accumLeftProjMat[3]; + accumLeftY = accumLeftProjMat[7]; + + if (useStereo) { + System.arraycopy( + copyOfCvCache.getRightProjection().mat, + 0, accumRightProjMat, 0, 16); + accumRightX = accumRightProjMat[3]; + accumRightY = accumRightProjMat[7]; + } + + if (renderBin.geometryBackground != null) { + System.arraycopy( + copyOfCvCache.getInfLeftProjection().mat, + 0, accumInfLeftProjMat, 0, 16); + accumInfLeftX = accumInfLeftProjMat[3]; + accumInfLeftY = accumInfLeftProjMat[7]; + if (useStereo) { + System.arraycopy( + copyOfCvCache.getInfRightProjection().mat, + 0, accumInfRightProjMat, 0, 16); + accumInfRightX = accumInfRightProjMat[3]; + accumInfRightY = accumInfRightProjMat[7]; + } + } + } else { + if (!canvas.antialiasingSet) { + canvas.setFullSceneAntialiasing(canvas.ctx, true); + canvas.antialiasingSet = true; + } + } + } else { + if (canvas.antialiasingSet) { + canvas.setFullSceneAntialiasing(canvas.ctx, false); + canvas.antialiasingSet = false; + } + } + + // background geometry setup + if (renderBin.geometryBackground != null) { + renderBin.updateInfVworldToVpc(); + } + + // setup default render mode - render to both eyes + canvas.setRenderMode(canvas.ctx, + Canvas3D.FIELD_ALL, + canvas.useDoubleBuffer); + + // Support DVR + /* + System.out.println("canvas.supportVideoResize() is " + + canvas.supportVideoResize()); + */ + if(canvas.supportVideoResize()) { + if(canvas.view.dvrResizeCompensation != + canvas.cachedDvrResizeCompensation) { + /* + System.out.println("Renderer : dvrResizeComp " + + canvas.view.dvrResizeCompensation); + */ + canvas.videoResizeCompensation(canvas.ctx, + canvas.view.dvrResizeCompensation); + canvas.cachedDvrResizeCompensation = + canvas.view.dvrResizeCompensation; + + } + if(canvas.view.dvrFactor != canvas.cachedDvrFactor) { + /* + System.out.println("Renderer : dvrFactor is " + + canvas.view.dvrFactor); + */ + canvas.videoResize(canvas.ctx, + canvas.screen.display, + canvas.window, + canvas.view.dvrFactor); + canvas.cachedDvrFactor = canvas.view.dvrFactor; + + } + + } + + canvas.beginScene(); + + // this is if the background image resizes with the canvas + int winWidth = copyOfCvCache.getCanvasWidth(); + int winHeight = copyOfCvCache.getCanvasHeight(); + + + // clear background if not full screen antialiasing + // and not in stereo mode + if (!doAccum && !sharedStereoZBuffer) { + BackgroundRetained bg = renderBin.background; + if (!VirtualUniverse.mc.isBackgroundTexture) { + canvas.clear(canvas.ctx, + bg.color.x, + bg.color.y, + bg.color.z, + winWidth, + winHeight, + bg.image, + bg.imageScaleMode, + (bg.image != null? + bg.image.imageYdown[0]:null)); + } else { + if ((bg.texImage != null) && + (objectId == -1)) { + objectId = VirtualUniverse.mc. + getTexture2DId(); + } + canvas.textureclear(canvas.ctx, + bg.xmax, + bg.ymax, + bg.color.x, + bg.color.y, + bg.color.z, + winWidth, + winHeight, + objectId, + bg.imageScaleMode, + bg.texImage, + background_image_update); + } +// canvas.clear(canvas.ctx, +// bg.color.x, +// bg.color.y, +// bg.color.z, +// bg.image); + } + + // handle preRender callback + if (VirtualUniverse.mc.doDsiRenderLock) { + canvas.drawingSurfaceObject.unLock(); + } + canvas.view.inCanvasCallback = true; + + try { + canvas.preRender(); + } catch (RuntimeException e) { + System.err.println("Exception occurred " + + "during Canvas3D callback:"); + e.printStackTrace(); + } + canvas.view.inCanvasCallback = false; + + if ((VirtualUniverse.mc.doDsiRenderLock) && + (!canvas.drawingSurfaceObject.renderLock())) { + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + break doneRender; + } + + // render loop + for (pass = 0; pass < num_stereo_passes; pass++) { + if (doAccum) { + canvas.clearAccum(canvas.ctx); + } + canvas.setRenderMode(canvas.ctx, stereo_mode, + canvas.useDoubleBuffer); + + + + for (apass = 0; apass < num_accum_passes; apass++) { + + // jitter projection matrix and clear background + // for full screen anti-aliasing rendering + if (doAccum) { + accumDx = ACCUM_SAMPLES_X[apass] * + accumDxFactor; + accumDy = ACCUM_SAMPLES_Y[apass] * + accumDyFactor; + + accumLeftProjMat[3] = accumLeftX + + accumLeftProjMat[0] * accumDx + + accumLeftProjMat[1] * accumDy; + + accumLeftProjMat[7] = accumLeftY + + accumLeftProjMat[4] * accumDx + + accumLeftProjMat[5] * accumDy; + + if (useStereo) { + accumRightProjMat[3] = accumRightX + + accumRightProjMat[0] * accumDx + + accumRightProjMat[1] * accumDy; + + accumRightProjMat[7] = accumRightY + + accumRightProjMat[4] * accumDx + + accumRightProjMat[5] * accumDy; + } + + if (renderBin.geometryBackground != null) { + accumInfLeftProjMat[3] = accumInfLeftX + + accumInfLeftProjMat[0] * accumDx + + accumInfLeftProjMat[1] * accumDy; + + accumInfLeftProjMat[7] = accumInfLeftY + + accumInfLeftProjMat[4] * accumDx + + accumInfLeftProjMat[5] * accumDy; + + if (useStereo) { + accumInfRightProjMat[3] = + accumInfRightX + + accumInfRightProjMat[0] * accumDx + + accumInfRightProjMat[1] * accumDy; + + accumInfRightProjMat[7] = + accumInfRightY + + accumInfRightProjMat[4] * accumDx + + accumInfRightProjMat[5] * accumDy; + } + } + } + + // clear background for stereo and + // accumulation buffer cases + if (doAccum || sharedStereoZBuffer) { + BackgroundRetained bg = renderBin.background; + if (!VirtualUniverse.mc.isBackgroundTexture) { + canvas.clear(canvas.ctx, + bg.color.x, + bg.color.y, + bg.color.z, + winWidth, + winHeight, + bg.image, + bg.imageScaleMode, + (bg.image != null?bg.image.imageYdown[0]:null)); + } + else { + if ((bg.texImage != null) && + (objectId == -1)) { + objectId = VirtualUniverse.mc. + getTexture2DId(); + } + + canvas.textureclear(canvas.ctx, + bg.xmax, + bg.ymax, + bg.color.x, + bg.color.y, + bg.color.z, + winWidth, + winHeight, + objectId, + bg.imageScaleMode, + bg.texImage, + background_image_update); + } + } + + // render background geometry + if (renderBin.geometryBackground != null) { + + // setup rendering matrices + if (pass == 0) { + canvas.vpcToEc = + copyOfCvCache.getInfLeftVpcToEc(); + if (doAccum) { + canvas.setProjectionMatrix( + canvas.ctx, + accumInfLeftProjMat); + } else { + canvas.setProjectionMatrix( + canvas.ctx, + copyOfCvCache.getInfLeftProjection().mat); + } + } else { + canvas.vpcToEc = + copyOfCvCache.getInfRightVpcToEc(); + if (doAccum) { + canvas.setProjectionMatrix( + canvas.ctx, + accumInfRightProjMat); + } else { + canvas.setProjectionMatrix( + canvas.ctx, + copyOfCvCache.getInfRightProjection().mat); + } + } + canvas.vworldToEc.mul(canvas.vpcToEc, + copyOfCvCache.getInfVworldToVpc()); + + // render background geometry + renderBin.renderBackground(canvas); + } + + // setup rendering matrices + if (pass == 0) { + canvas.vpcToEc = copyOfCvCache.getLeftVpcToEc(); + if (doAccum) { + canvas.setProjectionMatrix( + canvas.ctx, accumLeftProjMat); + } else { + canvas.setProjectionMatrix(canvas.ctx, + copyOfCvCache.getLeftProjection().mat); + } + } else { + canvas.vpcToEc = copyOfCvCache.getRightVpcToEc(); + if (doAccum) { + canvas.setProjectionMatrix( + canvas.ctx, accumRightProjMat); + } else { + canvas.setProjectionMatrix(canvas.ctx, + copyOfCvCache.getRightProjection().mat); + } + } + canvas.vworldToEc.mul(canvas.vpcToEc, + copyOfCvCache.getVworldToVpc()); + + + synchronized (copyOfCvCache) { + if (pass == 0) { + canvas.setFrustumPlanes(copyOfCvCache.getLeftFrustumPlanesInVworld()); + } else { + canvas.setFrustumPlanes(copyOfCvCache.getRightFrustumPlanesInVworld()); + } + } + + // render opaque geometry + renderBin.renderOpaque(canvas); + + // render ordered geometry + renderBin.renderOrdered(canvas); + + // handle renderField callback + if (VirtualUniverse.mc.doDsiRenderLock) { + canvas.drawingSurfaceObject.unLock(); + } + canvas.view.inCanvasCallback = true; + try { + canvas.renderField(stereo_mode); + } catch (RuntimeException e) { + System.err.println("Exception occurred during " + + "Canvas3D callback:"); + e.printStackTrace(); + } + canvas.view.inCanvasCallback = false; + if ((VirtualUniverse.mc.doDsiRenderLock) && + (!canvas.drawingSurfaceObject.renderLock())) { + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + break doneRender; + } + + // render transparent geometry + renderBin.renderTransparent(canvas); + + if (doAccum) + canvas.accum(canvas.ctx, accumValue); + } + + if (doAccum) + canvas.accumReturn(canvas.ctx); + if (useStereo) { + stereo_mode = Canvas3D.FIELD_RIGHT; + canvas.rightStereoPass = true; + } + } + canvas.imageReady = true; + canvas.rightStereoPass = false; + + // reset renderMode + canvas.setRenderMode(canvas.ctx, + Canvas3D.FIELD_ALL, + canvas.useDoubleBuffer); + + // handle postRender callback + if (VirtualUniverse.mc.doDsiRenderLock) { + canvas.drawingSurfaceObject.unLock(); + } + canvas.view.inCanvasCallback = true; + + try { + canvas.postRender(); + } catch (RuntimeException e) { + System.err.println("Exception occurred during " + + "Canvas3D callback:"); + e.printStackTrace(); + } + canvas.view.inCanvasCallback = false; + + // end offscreen rendering + if (canvas.offScreenRendering) { + + canvas.syncRender(canvas.ctx, true); + canvas.endOffScreenRendering(); + + // do the postSwap for offscreen here + canvas.view.inCanvasCallback = true; + try { + canvas.postSwap(); + } catch (RuntimeException e) { + System.err.println("Exception occurred during Canvas 3D callback:"); + e.printStackTrace(); + } + + if (offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + + canvas.offScreenRendering = false; + canvas.view.inCanvasCallback = false; + } + + + canvas.endScene(); + + if (doTiming) { + numframes += 1.0f; + if (numframes >= 20.0f) { + currtime = System.currentTimeMillis(); + System.err.println( + numframes/((currtime-lasttime)/1000.0f) + + " frames per second"); + numframes = 0.0f; + lasttime = currtime; + + // For taking memory footprint of the entire scene. + /* + long totalMem, freeMem, usedMem; + for(int ii=0; ii<5;ii++) { + totalMem = Runtime.getRuntime().totalMemory(); + freeMem = Runtime.getRuntime().freeMemory(); + usedMem = totalMem - freeMem; + System.out.print("mem used - before: " + usedMem + "bytes "); + //System.out.print("mem used - before: " + usedMem + " "); + System.runFinalization(); + System.gc(); + System.runFinalization(); + totalMem = Runtime.getRuntime().totalMemory(); + freeMem = Runtime.getRuntime().freeMemory(); + usedMem = totalMem - freeMem; + System.out.println("after: " + usedMem + "bytes "); + //System.out.println("after: " + usedMem + " "); + try { + Thread.sleep(100); + } + catch (InterruptedException e) { } + + } + */ + + } + } + } else { // if (renderBin != null) + if ((offBufRetained != null) && + offBufRetained.isByReference()) { + offBufRetained.geomLock.unLock(); + } + } + } + } + + // clear array to prevent memory leaks + if (opArg == RENDER) { + m[0] = null; + } else { + Arrays.fill(m, 0, totalMessages, null); + } + } + } catch (NullPointerException ne) { + ne.printStackTrace(); + if (canvas != null) { + if (canvas.ctx != 0) { + canvas.endScene(); + } + // drawingSurfaceObject will safely ignore + // this request if this is not lock before + canvas.drawingSurfaceObject.unLock(); + + } + } + } + + // resource clean up + void shutdown() { + removeAllCtxs(); + + if (VirtualUniverse.mc.isD3D()) { + D3DCleanUp(); + } + } + + void cleanup() { + super.cleanup(); + renderMessage = new J3dMessage[1]; + rendererStructure = new RendererStructure(); + bgVworldToVpc = new Transform3D(); + numframes = 0.0f; + sharedCtx = 0; + sharedCtxTimeStamp = 0; + dirtyRenderMoleculeList.clear(); + dirtyRenderAtomList.clear(); + dirtyDlistPerRinfoList.clear(); + textureIdResourceFreeList.clear(); + displayListResourceFreeList.clear(); + copyOfCvCache = new CanvasViewCache(null, null, null); + onScreen = null; + offScreen = null; + m = null; + nmesg = 0; + lasttime = 0; + currtime = 0; + display = 0; + } + + + + // This is only invoked from removeCtx()/removeAllCtxs() + // with drawingSurface already lock + final void makeCtxCurrent(long sharedCtx, long display, int window) { + if (sharedCtx != currentCtx) { + Canvas3D.useCtx(sharedCtx, display, window); + currentCtx = sharedCtx; + } + } + + // No need to free graphics2d and background if it is from + // Canvas3D postRequest() offScreen rendering since the + // user thread will not wait for it. Also we can just + // reuse it as Canvas3D did not destroy. + void removeCtx(Canvas3D cv, long display, int window, long ctx, + boolean resetCtx, boolean freeBackground) { + + synchronized (VirtualUniverse.mc.contextCreationLock) { + if (ctx != 0) { + int idx = listOfCtxs.indexOf(new Long(ctx)); + if (idx >= 0) { + listOfCtxs.remove(idx); + listOfCanvases.remove(idx); + // display is always 0 under windows + if ((MasterControl.isWin32 || (display != 0)) && + (window != 0) && cv.added) { + // cv.ctx may reset to -1 here so we + // always use the ctx pass in. + if (cv.drawingSurfaceObject.renderLock()) { + // if it is the last one, free shared resources + if (sharedCtx != 0) { + if (listOfCtxs.isEmpty()) { + makeCtxCurrent(sharedCtx, display, window); + freeResourcesInFreeList(null); + freeContextResources(); + Canvas3D.destroyContext(display, window, sharedCtx); + currentCtx = -1; + } else { + freeResourcesInFreeList(cv); + } + cv.makeCtxCurrent(ctx, display, window); + } else { + cv.makeCtxCurrent(ctx, display, window); + cv.freeResourcesInFreeList(ctx); + } + cv.freeContextResources(this, freeBackground, ctx); + Canvas3D.destroyContext(display, window, ctx); + currentCtx = -1; + cv.drawingSurfaceObject.unLock(); + } + } + } + + if (resetCtx) { + cv.ctx = 0; + } + + if ((sharedCtx != 0) && listOfCtxs.isEmpty()) { + sharedCtx = 0; + sharedCtxTimeStamp = 0; + } + cv.ctxTimeStamp = 0; + } + } + } + + void removeAllCtxs() { + Canvas3D cv; + + synchronized (VirtualUniverse.mc.contextCreationLock) { + + for (int i=listOfCanvases.size()-1; i >=0; i--) { + cv = (Canvas3D) listOfCanvases.get(i); + + if ((cv.screen != null) && (cv.ctx != 0)) { + if ((MasterControl.isWin32 || (display != 0)) && + (cv.window != 0) && cv.added) { + if (cv.drawingSurfaceObject.renderLock()) { + // We need to free sharedCtx resource + // first before last non-sharedCtx to + // workaround Nvidia driver bug under Linux + // that crash on freeTexture ID:4685156 + if ((i == 0) && (sharedCtx != 0)) { + makeCtxCurrent(sharedCtx, display, window); + freeResourcesInFreeList(null); + freeContextResources(); + Canvas3D.destroyContext(display, window, sharedCtx); + currentCtx = -1; + } + cv.makeCtxCurrent(); + cv.freeResourcesInFreeList(cv.ctx); + cv.freeContextResources(this, true, cv.ctx); + Canvas3D.destroyContext(cv.screen.display, + cv.window, + cv.ctx); + currentCtx = -1; + cv.drawingSurfaceObject.unLock(); + } + } + } + + cv.ctx = 0; + cv.ctxTimeStamp = 0; + } + + if (sharedCtx != 0) { + sharedCtx = 0; + sharedCtxTimeStamp = 0; + } + listOfCanvases.clear(); + listOfCtxs.clear(); + } + } + + void freeTextureID(int texId, String texture) { + Canvas3D currentCanvas = null; + + // get the current canvas + for (int i=listOfCtxs.size()-1; i >= 0; i--) { + Canvas3D c = (Canvas3D) listOfCanvases.get(i); + if (c.ctx == currentCtx) { + currentCanvas = c; + break; + } + } + + if (currentCanvas == null) { + return; + } + + synchronized (VirtualUniverse.mc.contextCreationLock) { + if (sharedCtx != 0) { + currentCanvas.makeCtxCurrent(sharedCtx); + // OGL share context is used + Canvas3D.freeTexture(sharedCtx, texId); + } else { + for (int i=listOfCtxs.size()-1; i >= 0; i--) { + Canvas3D c = (Canvas3D) listOfCanvases.get(i); + c.makeCtxCurrent(); + Canvas3D.freeTexture(c.ctx, texId); + } + } + // restore current context + currentCanvas.makeCtxCurrent(); + } + if (texture.equals("2D")){ + VirtualUniverse.mc.freeTexture2DId(texId); + } + else if(texture.equals("3D")){ + VirtualUniverse.mc.freeTexture3DId(texId); + } + } + + + // handle free resource in the FreeList + void freeResourcesInFreeList(Canvas3D cv) { + Iterator it; + boolean isFreeTex = (textureIdResourceFreeList.size() > 0); + boolean isFreeDL = (displayListResourceFreeList.size() > 0); + ArrayList list; + int i, val; + GeometryArrayRetained geo; + + if (isFreeTex || isFreeDL) { + if (cv != null) { + cv.makeCtxCurrent(sharedCtx); + } + + if (isFreeDL) { + for (it = displayListResourceFreeList.iterator(); it.hasNext();) { + val = ((Integer) it.next()).intValue(); + if (val <= 0) { + continue; + } + Canvas3D.freeDisplayList(sharedCtx, val); + } + displayListResourceFreeList.clear(); + } + if (isFreeTex) { + for (it = textureIdResourceFreeList.iterator(); it.hasNext();) { + val = ((Integer) it.next()).intValue(); + if (val <= 0) { + continue; + } + if (val >= textureIDResourceTable.size()) { + System.out.println("Error in freeResourcesInFreeList : ResourceIDTableSize = " + + textureIDResourceTable.size() + + " val = " + val); + } else { + textureIDResourceTable.set(val, null); + } + Canvas3D.freeTexture(sharedCtx, val); + } + textureIdResourceFreeList.clear(); + } + if (cv != null) { + cv.makeCtxCurrent(cv.ctx); + } + } + } + + final void addTextureResource(int id, Object obj) { + if (textureIDResourceTable.size() <= id) { + for (int i=textureIDResourceTable.size(); + i < id; i++) { + textureIDResourceTable.add(null); + } + textureIDResourceTable.add(obj); + } else { + textureIDResourceTable.set(id, obj); + } + } + + void freeContextResources() { + Object obj; + TextureRetained tex; + DetailTextureImage detailTex; + + for (int id = textureIDResourceTable.size()-1; id > 0; id--) { + obj = textureIDResourceTable.get(id); + if (obj == null) { + continue; + } + Canvas3D.freeTexture(sharedCtx, id); + if (obj instanceof TextureRetained) { + tex = (TextureRetained) obj; + synchronized (tex.resourceLock) { + tex.resourceCreationMask &= ~rendererBit; + if (tex.resourceCreationMask == 0) { + tex.freeTextureId(id); + } + } + } else if (obj instanceof DetailTextureImage) { + detailTex = (DetailTextureImage) obj; + detailTex.freeDetailTextureId(id, rendererBit); + } + + } + textureIDResourceTable.clear(); + + // displayList is free in Canvas.freeContextResources() + } + +} + + diff --git a/src/classes/share/javax/media/j3d/RendererStructure.java b/src/classes/share/javax/media/j3d/RendererStructure.java new file mode 100644 index 0000000..34122a4 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RendererStructure.java @@ -0,0 +1,56 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A renderer structure is an object that organizes messages + * to the renderer thread. + */ +class RendererStructure extends J3dStructure{ + + /** + * This constructor does nothing + */ + RendererStructure() { + super(null, J3dThread.RENDER_THREAD); + } + + /** + * Returns all messages in the queue. + */ + J3dMessage[] getMessages() { + int sz; + + synchronized (messageList) { + if ((sz = messageList.size()) > 0) { + if (msgList.length < sz) { + msgList = new J3dMessage[sz]; + } + messageList.toArrayAndClear(msgList); + } + } + + nMessage = sz; + return msgList; + } + + + void processMessages(long referenceTime) {} + + void removeNodes(J3dMessage m) {} + + void cleanup() {} +} diff --git a/src/classes/share/javax/media/j3d/RenderingAttributes.java b/src/classes/share/javax/media/j3d/RenderingAttributes.java new file mode 100644 index 0000000..488c48d --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderingAttributes.java @@ -0,0 +1,735 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The RenderingAttributes object defines common rendering attributes + * for all primitive types. The rendering attributes are:<p> + * <ul> + * <li>Alpha test function - used to compare the alpha test value with + * each per-pixel alpha value. If the test passes, the pixel is + * written, otherwise the pixel is not written. The alpha test + * function is set with the <code>setAlphaTestFunction</code> + * method. The alpha test + * function is one of the following:</li><p> + * <ul> + * <li>ALWAYS - pixels are always drawn, irrespective of the alpha + * value. This effectively disables alpha testing. This is + * the default setting.</li><p> + * + * <li>NEVER - pixels are never drawn, irrespective of the alpha + * value.</li><p> + * + * <li>EQUAL - pixels are drawn if the pixel alpha value is equal + * to the alpha test value.</li><p> + * + * <li>NOT_EQUAL - pixels are drawn if the pixel alpha value is + * not equal to the alpha test value.</li><p> + * + * <li>LESS - pixels are drawn if the pixel alpha value is less + * than the alpha test value.</li><p> + * + * <li>LESS_OR_EQUAL - pixels are drawn if the pixel alpha value + * is less than or equal to the alpha test value.</li><p> + * + * <li>GREATER - pixels are drawn if the pixel alpha value is greater + * than the alpha test value.</li><p> + * + * <li>GREATER_OR_EQUAL - pixels are drawn if the pixel alpha + * value is greater than or equal to the alpha test value.</li><p> + * </ul> + * <li>Alpha test value - the test value used by the alpha test function. + * This value is compared to the alpha value of each rendered pixel. + * The alpha test value is set with the <code>setAlphaTestValue</code> + * method. The default alpha test value is 0.0.</li><p> + * + * <li>Raster operation - the raster operation function for this + * RenderingAttributes component object. The raster operation is + * set with the <code>setRasterOp</code> method. The raster operation + * is enabled or disabled with the <code>setRasterOpEnable</code> + * method. The raster operation is one of the following:</li><p> + * <ul> + * <li>ROP_COPY - DST = SRC. This is the default operation.</li> + * <li>ROP_XOR - DST = SRC ^ DST.</li><p> + * </ul> + * <li>Vertex colors - vertex colors can be ignored for this + * RenderingAttributes object. This capability is set with the + * <code>setIgnoreVertexColors</code> method. If + * ignoreVertexColors is false, per-vertex colors are used, when + * present in the associated geometry objects, taking + * precedence over the ColoringAttributes color and the + * specified Material color(s). If ignoreVertexColors is true, per-vertex + * colors are ignored. In this case, if lighting is enabled, the + * Material diffuse color will be used as the object color. + * if lighting is disabled, the ColoringAttributes color is + * used. The default value is false.</li><p> + * + * <li>Visibility flag - when set, invisible objects are + * not rendered (subject to the visibility policy for + * the current view), but they can be picked or collided with. + * This flag is set with the <code>setVisible</code> + * method. By default, the visibility flag is true.</li><p> + * + * <li>Depth buffer - can be enabled or disabled for this + * RenderingAttributes component object. The + * <code>setDepthBufferEnable</code> method enables + * or disabled the depth buffer. The + * <code>setDepthBufferWriteEnable</code> method enables or disables + * writing the depth buffer for this object. During the transparent + * rendering pass, this attribute can be overridden by the + * depthBufferFreezeTransparent attribute in the View + * object. Transparent objects include BLENDED transparent and + * antialiased lines and points. Transparent objects do not + * include opaque objects or primitives rendered with + * SCREEN_DOOR transparency. By default, the depth buffer + * is enabled and the depth buffer write is enabled.</li><p> + * </ul> + * + * @see Appearance + * + */ +public class RenderingAttributes extends NodeComponent { + /** + * Specifies that this RenderingAttributes object + * allows reading its alpha test value component information. + */ + public static final int + ALLOW_ALPHA_TEST_VALUE_READ = CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_ALPHA_TEST_VALUE_READ; + + /** + * Specifies that this RenderingAttributes object + * allows writing its alpha test value component information. + */ + public static final int + ALLOW_ALPHA_TEST_VALUE_WRITE = CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_ALPHA_TEST_VALUE_WRITE; + + /** + * Specifies that this RenderingAttributes object + * allows reading its alpha test function component information. + */ + public static final int + ALLOW_ALPHA_TEST_FUNCTION_READ = CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_ALPHA_TEST_FUNCTION_READ; + + /** + * Specifies that this RenderingAttributes object + * allows writing its alpha test function component information. + */ + public static final int + ALLOW_ALPHA_TEST_FUNCTION_WRITE = CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_ALPHA_TEST_FUNCTION_WRITE; + + /** + * Specifies that this RenderingAttributes object + * allows reading its depth buffer enable and depth buffer write enable + * component information. + */ + public static final int + ALLOW_DEPTH_ENABLE_READ = CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_DEPTH_ENABLE_READ; + + /** + * Specifies that this RenderingAttributes object + * allows writing its depth buffer enable and depth buffer write enable + * component information. + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_DEPTH_ENABLE_WRITE = + CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_DEPTH_ENABLE_WRITE; + + /** + * Specifies that this RenderingAttributes object + * allows reading its visibility information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_VISIBLE_READ = + CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_VISIBLE_READ; + + /** + * Specifies that this RenderingAttributes object + * allows writing its visibility information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_VISIBLE_WRITE = + CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_VISIBLE_WRITE; + + /** + * Specifies that this RenderingAttributes object + * allows reading its ignore vertex colors information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_IGNORE_VERTEX_COLORS_READ = + CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_IGNORE_VERTEX_COLORS_READ; + + /** + * Specifies that this RenderingAttributes object + * allows writing its ignore vertex colors information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_IGNORE_VERTEX_COLORS_WRITE = + CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_IGNORE_VERTEX_COLORS_WRITE; + + /** + * Specifies that this RenderingAttributes object + * allows reading its raster operation information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_RASTER_OP_READ = + CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_RASTER_OP_READ; + + /** + * Specifies that this RenderingAttributes object + * allows writing its raster operation information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_RASTER_OP_WRITE = + CapabilityBits.RENDERING_ATTRIBUTES_ALLOW_RASTER_OP_WRITE; + + /** + * Indicates pixels are always drawn irrespective of alpha value. + * This effectively disables alpha testing. + */ + public static final int ALWAYS = 0; + + /** + * Indicates pixels are never drawn irrespective of alpha value. + */ + public static final int NEVER = 1; + + /** + * Indicates pixels are drawn if pixel alpha value is equal + * to alpha test value. + */ + public static final int EQUAL = 2; + + /** + * Indicates pixels are drawn if pixel alpha value is not equal + * to alpha test value. + */ + public static final int NOT_EQUAL = 3; + + /** + * Indicates pixels are drawn if pixel alpha value is less + * than alpha test value. + */ + public static final int LESS = 4; + + /** + * Indicates pixels are drawn if pixel alpha value is less + * than or equal to alpha test value. + */ + public static final int LESS_OR_EQUAL = 5; + + /** + * Indicates pixels are drawn if pixel alpha value is greater + * than alpha test value. + */ + public static final int GREATER = 6; + + /** + * Indicates pixels are drawn if pixel alpha value is greater + * than or equal to alpha test value. + */ + public static final int GREATER_OR_EQUAL = 7; + + +// public static final int ROP_CLEAR = 0x0; +// public static final int ROP_AND = 0x1; +// public static final int ROP_AND_REVERSE = 0x2; + + /** + * Raster operation: <code>DST = SRC</code>. + * @see #setRasterOp + * @since Java 3D 1.2 + */ + public static final int ROP_COPY = 0x3; + +// public static final int ROP_AND_INVERTED = 0x4; +// public static final int ROP_NOOP = 0x5; + + /** + * Raster operation: <code>DST = SRC ^ DST</code>. + * @see #setRasterOp + * @since Java 3D 1.2 + */ + public static final int ROP_XOR = 0x6; + +// public static final int ROP_OR = 0x7; +// public static final int ROP_NOR = 0x8; +// public static final int ROP_EQUIV = 0x9; +// public static final int ROP_INVERT = 0xA; +// public static final int ROP_OR_REVERSE = 0xB; +// public static final int ROP_COPY_INVERTED = 0xC; +// public static final int ROP_OR_INVERTED = 0xD; +// public static final int ROP_NAND = 0xE; +// public static final int ROP_SET = 0xF; + + + /** + * Constructs a RenderingAttributes object with default parameters. + * The default values are as follows: + * <ul> + * depth buffer enable : true<br> + * depth buffer write enable : true<br> + * alpha test function : ALWAYS<br> + * alpha test value : 0.0<br> + * visible : true<br> + * ignore vertex colors : false<br> + * raster operation enable : false<br> + * raster operation : ROP_COPY<br> + * </ul> + */ + public RenderingAttributes() { + // Just use default attributes + } + + /** + * Constructs a RenderingAttributes object with specified values. + * @param depthBufferEnable a flag to turn depth buffer on/off + * @param depthBufferWriteEnable a flag to to make depth buffer + * read/write or read only + * @param alphaTestValue the alpha test reference value + * @param alphaTestFunction the function for comparing alpha values + */ + public RenderingAttributes(boolean depthBufferEnable, + boolean depthBufferWriteEnable, + float alphaTestValue, + int alphaTestFunction){ + + this(depthBufferEnable, depthBufferWriteEnable, alphaTestValue, + alphaTestFunction, true, false, false, ROP_COPY); + } + + /** + * Constructs a RenderingAttributes object with specified values + * @param depthBufferEnable a flag to turn depth buffer on/off + * @param depthBufferWriteEnable a flag to make depth buffer + * read/write or read only + * @param alphaTestValue the alpha test reference value + * @param alphaTestFunction the function for comparing alpha values + * @param visible a flag that specifies whether the object is visible + * @param ignoreVertexColors a flag to enable or disable + * the ignoring of per-vertex colors + * @param rasterOpEnable a flag that specifies whether logical + * raster operations are enabled for this RenderingAttributes object. + * This disables all alpha blending operations. + * @param rasterOp the logical raster operation, one of ROP_COPY or + * ROP_XOR. + * + * @since Java 3D 1.2 + */ + public RenderingAttributes(boolean depthBufferEnable, + boolean depthBufferWriteEnable, + float alphaTestValue, + int alphaTestFunction, + boolean visible, + boolean ignoreVertexColors, + boolean rasterOpEnable, + int rasterOp) { + + ((RenderingAttributesRetained)this.retained).initDepthBufferEnable(depthBufferEnable); + ((RenderingAttributesRetained)this.retained).initDepthBufferWriteEnable(depthBufferWriteEnable); + ((RenderingAttributesRetained)this.retained).initAlphaTestValue(alphaTestValue); + ((RenderingAttributesRetained)this.retained).initAlphaTestFunction(alphaTestFunction); + ((RenderingAttributesRetained)this.retained).initVisible(visible); + + + ((RenderingAttributesRetained)this.retained).initIgnoreVertexColors(ignoreVertexColors); + ((RenderingAttributesRetained)this.retained).initRasterOpEnable(rasterOpEnable); + ((RenderingAttributesRetained)this.retained).initRasterOp(rasterOp); + } + + /** + * Enables or disables depth buffer mode for this RenderingAttributes + * component object. + * @param state true or false to enable or disable depth buffer mode + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDepthBufferEnable(boolean state){ + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DEPTH_ENABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes0")); + + if (isLive()) + ((RenderingAttributesRetained)this.retained).setDepthBufferEnable(state); + else + ((RenderingAttributesRetained)this.retained).initDepthBufferEnable(state); + + } + + /** + * Retrieves the state of zBuffer Enable flag + * @return true if depth buffer mode is enabled, false + * if depth buffer mode is disabled + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getDepthBufferEnable(){ + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DEPTH_ENABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes1")); + + return ((RenderingAttributesRetained)this.retained).getDepthBufferEnable(); + } + + /** + * Enables or disables writing the depth buffer for this object. + * During the transparent rendering pass, + * this attribute can be overridden by + * the depthBufferFreezeTransparent attribute in the View object. + * @param state true or false to enable or disable depth buffer Write mode + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @see View#setDepthBufferFreezeTransparent + */ + public void setDepthBufferWriteEnable(boolean state) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DEPTH_ENABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes2")); + + if (isLive()) + ((RenderingAttributesRetained)this.retained).setDepthBufferWriteEnable(state); + else + ((RenderingAttributesRetained)this.retained).initDepthBufferWriteEnable(state); + } + + /** + * Retrieves the state of Depth Buffer Write Enable flag. + * @return true if depth buffer is writable, false + * if depth buffer is read-only + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getDepthBufferWriteEnable(){ + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DEPTH_ENABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes3")); + + return ((RenderingAttributesRetained)this.retained).getDepthBufferWriteEnable(); + } + + /** + * Set alpha test value used by alpha test function. This value is + * compared to the alpha value of each rendered pixel. + * @param value the alpha test value + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAlphaTestValue(float value){ + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ALPHA_TEST_VALUE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes4")); + if (isLive()) + ((RenderingAttributesRetained)this.retained).setAlphaTestValue(value); + else + ((RenderingAttributesRetained)this.retained).initAlphaTestValue(value); + + } + + /** + * Retrieves the alpha test value. + * @return the alpha test value. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getAlphaTestValue(){ + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ALPHA_TEST_VALUE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes5")); + + return ((RenderingAttributesRetained)this.retained).getAlphaTestValue(); + } + + /** + * Set alpha test function. This function is used to compare the + * alpha test value with each per-pixel alpha value. If the test + * passes, the pixel is written otherwise the pixel is not + * written. + * @param function the new alpha test function. One of + * ALWAYS, NEVER, EQUAL, NOT_EQUAL, LESS, LESS_OR_EQUAL, GREATER, + * GREATER_OR_EQUAL + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAlphaTestFunction(int function){ + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ALPHA_TEST_FUNCTION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes6")); + + if (isLive()) + ((RenderingAttributesRetained)this.retained).setAlphaTestFunction(function); + else + ((RenderingAttributesRetained)this.retained).initAlphaTestFunction(function); + + } + + /** + * Retrieves current alpha test function. + * @return the current alpha test function + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getAlphaTestFunction(){ + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_ALPHA_TEST_FUNCTION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes7")); + + return ((RenderingAttributesRetained)this.retained).getAlphaTestFunction(); + } + + /** + * Sets the visibility flag for this RenderingAttributes + * component object. Invisible objects are not rendered (subject to + * the visibility policy for the current view), but they can be picked + * or collided with. The default value is true. + * @param visible true or false to enable or disable visibility + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see View#setVisibilityPolicy + * + * @since Java 3D 1.2 + */ + public void setVisible(boolean visible) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_VISIBLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes8")); + + if (isLive()) + ((RenderingAttributesRetained)this.retained).setVisible(visible); + else + ((RenderingAttributesRetained)this.retained).initVisible(visible); + } + + /** + * Retrieves the visibility flag for this RenderingAttributes object. + * @return true if the object is visible; false + * if the object is invisible. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public boolean getVisible() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_VISIBLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes9")); + + return ((RenderingAttributesRetained)this.retained).getVisible(); + } + + /** + * Sets a flag that indicates whether vertex colors are ignored + * for this RenderingAttributes object. If + * <code>ignoreVertexColors</code> is false, per-vertex + * colors are used, when present in the associated Geometry + * objects, taking precedence over the ColoringAttributes color + * and the specified Material color(s). If <code>ignoreVertexColors</code> + * is true, per-vertex colors are ignored. In this case, if + * lighting is enabled, the Material diffuse color will be + * used as the object color. If lighting is disabled, the + * ColoringAttributes color will be used. The default value is false. + * + * @param ignoreVertexColors true or false to enable or disable + * the ignoring of per-vertex colors + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see ColoringAttributes + * @see Material + * + * @since Java 3D 1.2 + */ + public void setIgnoreVertexColors(boolean ignoreVertexColors) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_IGNORE_VERTEX_COLORS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes12")); + + + if (isLive()) + ((RenderingAttributesRetained)this.retained).setIgnoreVertexColors(ignoreVertexColors); + else + ((RenderingAttributesRetained)this.retained).initIgnoreVertexColors(ignoreVertexColors); + } + + /** + * Retrieves the ignoreVertexColors flag for this + * RenderingAttributes object. + * @return true if per-vertex colors are ignored; false + * if per-vertex colors are used. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public boolean getIgnoreVertexColors() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_IGNORE_VERTEX_COLORS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes13")); + + + return ((RenderingAttributesRetained)this.retained).getIgnoreVertexColors(); + } + + /** + * Sets the rasterOp enable flag for this RenderingAttributes + * component object. When set to true, this enables logical + * raster operations as specified by the setRasterOp method. + * Enabling raster operations effectively disables alpha blending, + * which is used for transparency and antialiasing. Raster + * operations, especially XOR mode, are primarily useful when + * rendering to the front buffer in immediate mode. Most + * applications will not wish to enable this mode. + * + * @param rasterOpEnable true or false to enable or disable + * raster operations + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #setRasterOp + * + * @since Java 3D 1.2 + */ + public void setRasterOpEnable(boolean rasterOpEnable) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_RASTER_OP_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes10")); + + if (isLive()) + ((RenderingAttributesRetained)this.retained).setRasterOpEnable(rasterOpEnable); + else + ((RenderingAttributesRetained)this.retained).initRasterOpEnable(rasterOpEnable); + } + + /** + * Retrieves the rasterOp enable flag for this RenderingAttributes + * object. + * @return true if raster operations are enabled; false + * if raster operations are disabled. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public boolean getRasterOpEnable() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_RASTER_OP_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes11")); + + + return ((RenderingAttributesRetained)this.retained).getRasterOpEnable(); + } + + /** + * Sets the raster operation function for this RenderingAttributes + * component object. + * + * @param rasterOp the logical raster operation, one of ROP_COPY or + * ROP_XOR + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void setRasterOp(int rasterOp) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_RASTER_OP_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes10")); + + if (isLive()) + ((RenderingAttributesRetained)this.retained).setRasterOp(rasterOp); + else + ((RenderingAttributesRetained)this.retained).initRasterOp(rasterOp); + } + + /** + * Retrieves the current raster operation for this RenderingAttributes + * object. + * @return one of ROP_COPY or ROP_XOR. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getRasterOp() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_RASTER_OP_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("RenderingAttributes11")); + + return ((RenderingAttributesRetained)this.retained).getRasterOp(); + } + + /** + * Creates a retained mode RenderingAttributesRetained object that this + * RenderingAttributes component object will point to. + */ + void createRetained() { + this.retained = new RenderingAttributesRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + RenderingAttributes ra = new RenderingAttributes(); + ra.duplicateNodeComponent(this); + return ra; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + RenderingAttributesRetained attr = + (RenderingAttributesRetained) originalNodeComponent.retained; + RenderingAttributesRetained rt = + (RenderingAttributesRetained) retained; + + rt.initDepthBufferEnable(attr.getDepthBufferEnable()); + rt.initDepthBufferWriteEnable(attr.getDepthBufferWriteEnable()); + rt.initAlphaTestValue(attr.getAlphaTestValue()); + rt.initAlphaTestFunction(attr.getAlphaTestFunction()); + rt.initVisible(attr.getVisible()); + rt.initIgnoreVertexColors(attr.getIgnoreVertexColors()); + rt.initRasterOpEnable(attr.getRasterOpEnable()); + rt.initRasterOp(attr.getRasterOp()); + + } + +} diff --git a/src/classes/share/javax/media/j3d/RenderingAttributesRetained.java b/src/classes/share/javax/media/j3d/RenderingAttributesRetained.java new file mode 100644 index 0000000..56ea708 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderingAttributesRetained.java @@ -0,0 +1,486 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * The RenderingAttributes object defines all rendering state that can be set + * as a component object of a Shape3D node. + */ +class RenderingAttributesRetained extends NodeComponentRetained { + // A list of pre-defined bits to indicate which component + // in this RenderingAttributes object changed. + static final int DEPTH_ENABLE = 0x01; + + static final int DEPTH_WRITE_ENABLE = 0x02; + + static final int ALPHA_TEST_VALUE = 0x04; + + static final int ALPHA_TEST_FUNC = 0x08; + + static final int VISIBLE = 0x10; + + static final int IGNORE_VCOLOR = 0x20; + + static final int RASTER_OP_ENABLE = 0x40; + + static final int RASTER_OP_VALUE = 0x80; + + + // depth buffer Enable for hidden surface removal + boolean depthBufferEnable = true; + + boolean depthBufferWriteEnable = true; + + float alphaTestValue = 0.0f; + + int alphaTestFunction = RenderingAttributes.ALWAYS; + + boolean visible = true; + + boolean ignoreVertexColors = false; + + // raster operation + boolean rasterOpEnable = false; + int rasterOp = RenderingAttributes.ROP_COPY; + + // depth buffer comparison function. Used by multi-texturing only + static final int LESS = 0; + static final int LEQUAL = 1; + + /** + * Sets the visibility flag for this RenderingAttributes component object. + * @param visible true or false to enable or disable visibility + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see View#setVisibilityPolicy + * + * @since Java 3D 1.2 + */ + final void initVisible(boolean state){ + visible = state; + } + + /** + * Sets the visibility flag for this RenderingAttributes + * component object. Invisible objects are not rendered (subject to + * the visibility policy for the current view), but they can be picked + * or collided with. + * @param visible true or false to enable or disable visibility + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see View#setVisibilityPolicy + * + * @since Java 3D 1.2 + */ + final void setVisible(boolean state){ + // Optimize : If new state equal to current state, should I simply return ? + // Is it safe ? + initVisible(state); + + // Need to call sendMessage twice. Not an efficient approach, but + // it simplified code logic and speed up the common case; where + // perUniv is false. + + + sendMessage(VISIBLE, (state ? Boolean.TRUE: Boolean.FALSE)); + + } + + /** + * Retrieves the visibility flag for this RenderingAttributes object. + * @return true if the object is visible; false + * if the object is invisible. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + final boolean getVisible() { + return visible; + } + + final void initIgnoreVertexColors(boolean state) { + ignoreVertexColors = state; + } + + final void setIgnoreVertexColors(boolean state) { + initIgnoreVertexColors(state); + sendMessage(IGNORE_VCOLOR, + (state ? Boolean.TRUE: Boolean.FALSE)); + } + + final boolean getIgnoreVertexColors() { + return ignoreVertexColors; + } + + /** + * Enables or disables depth buffer mode for this RenderAttributes + * component object. + * @param state true or false to enable or disable depth buffer mode + */ + final void initDepthBufferEnable(boolean state){ + depthBufferEnable = state; + } + + /** + * Enables or disables depth buffer mode for this RenderAttributes + * component object and sends a + * message notifying the interested structures of the change. + * @param state true or false to enable or disable depth buffer mode + */ + final void setDepthBufferEnable(boolean state){ + initDepthBufferEnable(state); + sendMessage(DEPTH_ENABLE, (state ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Retrieves the state of zBuffer Enable flag + * @return true if depth buffer mode is enabled, false + * if depth buffer mode is disabled + */ + final boolean getDepthBufferEnable(){ + return depthBufferEnable; + } + + /** + * Enables or disables writing the depth buffer for this object. + * During the transparent rendering pass, + * this attribute can be overridden by + * the depthBufferFreezeTransparent attribute in the View object. + * @param state true or false to enable or disable depth buffer Write mode + * @see View#setDepthBufferFreezeTransparent + */ + final void initDepthBufferWriteEnable(boolean state){ + depthBufferWriteEnable = state; + } + + /** + * Enables or disables writing the depth buffer for this object and sends + * a message notifying the interested structures of the change. + * During the transparent rendering pass, + * this attribute can be overridden by + * the depthBufferFreezeTransparent attribute in the View object. + * @param state true or false to enable or disable depth buffer Write mode + * @see View#setDepthBufferFreezeTransparent + */ + final void setDepthBufferWriteEnable(boolean state){ + + initDepthBufferWriteEnable(state); + sendMessage(DEPTH_WRITE_ENABLE, (state ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Retrieves the state of Depth Buffer Write Enable flag + * @return true if depth buffer is writable, false + * if depth buffer is read-only + */ + final boolean getDepthBufferWriteEnable(){ + return depthBufferWriteEnable; + } + + /** + * Set alpha test value used by alpha test function. This value is + * compared to the alpha value of each rendered pixel. + * @param value the alpha value + */ + final void initAlphaTestValue(float value){ + alphaTestValue = value; + } + /** + * Set alpha test value used by alpha test function and sends a + * message notifying the interested structures of the change. + * This value is compared to the alpha value of each rendered pixel. + * @param value the alpha value + */ + final void setAlphaTestValue(float value){ + + initAlphaTestValue(value); + sendMessage(ALPHA_TEST_VALUE, new Float(value)); + } + + /** + * Retrieves the alpha test value. + * @return the alpha test value. + */ + final float getAlphaTestValue(){ + return alphaTestValue; + } + + + /** + * Set alpha test function. This function is used to compare the + * alpha test value with each per-pixel alpha value. If the test + * passes, then the pixel is written otherwise the pixel is not + * written. + * @param function the new alpha test function. One of: + * ALWAYS, NEVER, EQUAL, NOT_EQUAL, LESS, LESS_OR_EQUAL, GREATER, + * GREATER_OR_EQUAL. + */ + final void initAlphaTestFunction(int function){ + alphaTestFunction = function; + } + + + /** + * Set alpha test function and sends a + * message notifying the interested structures of the change. + * This function is used to compare the + * alpha test value with each per-pixel alpha value. If the test + * passes, then the pixel is written otherwise the pixel is not + * written. + * @param function the new alpha test function. One of: + * ALWAYS, NEVER, EQUAL, NOT_EQUAL, LESS, LESS_OR_EQUAL, GREATER, + * GREATER_OR_EQUAL. + */ + final void setAlphaTestFunction(int function){ + + initAlphaTestFunction(function); + sendMessage(ALPHA_TEST_FUNC, new Integer(function)); + } + + /** + * Retrieves current alpha test function. + * @return the current alpha test function + */ + final int getAlphaTestFunction(){ + return alphaTestFunction; + } + + + /** + * Initialize the raster op enable flag + */ + final void initRasterOpEnable(boolean flag) { + rasterOpEnable = flag; + } + + /** + * Set the raster op enable flag + */ + final void setRasterOpEnable(boolean flag) { + initRasterOpEnable(flag); + sendMessage(RASTER_OP_ENABLE, new Boolean(flag)); + } + + /** + * Retrieves the current raster op enable flag. + */ + final boolean getRasterOpEnable() { + return rasterOpEnable; + } + + /** + * Initialize the raster op value + */ + final void initRasterOp(int op) { + rasterOp = op; + } + + /** + * Set the raster op value + */ + final void setRasterOp(int op) { + initRasterOp(op); + sendMessage(RASTER_OP_VALUE, new Integer(op)); + } + + /** + * Retrieves the current raster op value. + */ + final int getRasterOp() { + return rasterOp; + } + + + + /** + * Updates the native context. + */ + native void updateNative(long ctx, + boolean depthBufferWriteEnableOverride, + boolean depthBufferEnableOverride, + boolean depthBufferEnable, + boolean depthBufferWriteEnable, + float alphaTestValue, int alphaTestFunction, + boolean ignoreVertexColors, + boolean rasterOpEnable, int rasterOp); + + /** + * Updates the native context. + */ + void updateNative(long ctx, + boolean depthBufferWriteEnableOverride, + boolean depthBufferEnableOverride) { + updateNative(ctx, + depthBufferWriteEnableOverride, depthBufferEnableOverride, + depthBufferEnable, depthBufferWriteEnable, alphaTestValue, + alphaTestFunction, ignoreVertexColors, + rasterOpEnable, rasterOp); + } + + /** + * Creates and initializes a mirror object, point the mirror object + * to the retained object if the object is not editable + */ + synchronized void createMirrorObject() { + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror = this; + } + else { + RenderingAttributesRetained mirrorRa + = new RenderingAttributesRetained(); + mirrorRa.set(this); + mirrorRa.source = source; + mirror = mirrorRa; + } + } else { + ((RenderingAttributesRetained) mirror).set(this); + } + } + + /** + * Initializes a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + ((RenderingAttributesRetained)mirror).set(this); + } + + /** + * Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + RenderingAttributesRetained mirrorRa = (RenderingAttributesRetained)mirror; + + if ((component & DEPTH_ENABLE) != 0) { + mirrorRa.depthBufferEnable = ((Boolean)value).booleanValue(); + } + else if ((component & DEPTH_WRITE_ENABLE) != 0) { + mirrorRa.depthBufferWriteEnable = ((Boolean)value).booleanValue(); + } + else if ((component & ALPHA_TEST_VALUE) != 0) { + mirrorRa.alphaTestValue = ((Float)value).floatValue(); + } + else if ((component & ALPHA_TEST_FUNC) != 0) { + mirrorRa.alphaTestFunction = ((Integer)value).intValue(); + } + else if ((component & VISIBLE) != 0) { + mirrorRa.visible = (((Boolean)value).booleanValue()); + } + else if ((component & IGNORE_VCOLOR) != 0) { + mirrorRa.ignoreVertexColors = (((Boolean)value).booleanValue()); + } + else if ((component & RASTER_OP_ENABLE) != 0) { + mirrorRa.rasterOpEnable = (((Boolean)value).booleanValue()); + } + else if ((component & RASTER_OP_VALUE) != 0) { + mirrorRa.rasterOp = (((Integer)value).intValue()); + } + } + + boolean equivalent(RenderingAttributesRetained rr) { + return (this == rr) || + ((rr != null) && + (rr.depthBufferEnable == depthBufferEnable) && + (rr.depthBufferWriteEnable == depthBufferWriteEnable) && + (rr.alphaTestValue == alphaTestValue) && + (rr.alphaTestFunction == alphaTestFunction) && + (rr.visible == visible) && + (rr.ignoreVertexColors == ignoreVertexColors) && + (rr.rasterOpEnable == rasterOpEnable) && + (rr.rasterOp == rasterOp)); + } + + protected void set(RenderingAttributesRetained ra) { + super.set(ra); + depthBufferEnable = ra.depthBufferEnable; + depthBufferWriteEnable = ra.depthBufferWriteEnable; + alphaTestValue = ra.alphaTestValue; + alphaTestFunction = ra.alphaTestFunction; + visible = ra.visible; + ignoreVertexColors = ra.ignoreVertexColors; + rasterOpEnable = ra.rasterOpEnable; + rasterOp = ra.rasterOp; + } + + final void sendMessage(int attrMask, Object attr) { + + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.RENDERINGATTRIBUTES_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + // System.out.println("changedFreqent1 = "+changedFrequent); + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + if (attrMask == VISIBLE) + createMessage.threads |= J3dThread.UPDATE_GEOMETRY; + createMessage.type = J3dMessage.RENDERINGATTRIBUTES_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList)gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + + } + + + void handleFrequencyChange(int bit) { + int mask = 0; + + if (bit == RenderingAttributes.ALLOW_ALPHA_TEST_VALUE_WRITE) + mask = ALPHA_TEST_VALUE; + if( bit == RenderingAttributes.ALLOW_ALPHA_TEST_FUNCTION_WRITE) + mask = ALPHA_TEST_FUNC; + if(bit == RenderingAttributes.ALLOW_VISIBLE_WRITE) + mask = VISIBLE; + if (bit == RenderingAttributes.ALLOW_IGNORE_VERTEX_COLORS_WRITE) + mask = IGNORE_VCOLOR; + if(bit == RenderingAttributes.ALLOW_RASTER_OP_WRITE) + mask = RASTER_OP_ENABLE; + if(bit == RenderingAttributes.ALLOW_DEPTH_ENABLE_WRITE) + mask = DEPTH_WRITE_ENABLE; + if (mask != 0) + setFrequencyChangeMask(bit, mask); + // System.out.println("changedFreqent2 = "+changedFrequent); + } + +} diff --git a/src/classes/share/javax/media/j3d/RenderingAttributesStructure.java b/src/classes/share/javax/media/j3d/RenderingAttributesStructure.java new file mode 100644 index 0000000..cd959b6 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderingAttributesStructure.java @@ -0,0 +1,226 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * A rendering attributes structure is an object that handles + * NodeComponent object updates. + */ + +class RenderingAttributesStructure extends J3dStructure implements ObjectUpdate { + // List of textures whose resourceCreation mask should be updated + ArrayList objList = new ArrayList(); + + RenderingAttributesStructure() { + super(null, J3dThread.UPDATE_RENDERING_ATTRIBUTES); + } + + void processMessages(long referenceTime) { + J3dMessage m; + boolean addMirrorObj = false; + + J3dMessage messages[] = getMessages(referenceTime); + int nMsg = getNumMessage(); + + if (nMsg <= 0) { + return; + } + + for (int i=0; i < nMsg; i++) { + m = messages[i]; + switch (m.type) { + // Apperance is always updated immediately, since rBin needs + // the most up-to-date values for restructuring + case J3dMessage.APPEARANCE_CHANGED: + case J3dMessage.TEXTURE_UNIT_STATE_CHANGED: // TODO: Is this correct? + { + int component = ((Integer)m.args[1]).intValue(); + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + nc.mirror.changedFrequent = ((Integer)m.args[3]).intValue(); + updateNodeComponent((Object[])m.args); + if (nc.mirror.changedFrequent != 0) { + objList.add(m); + addMirrorObj = true; + nc.mirror.compChanged |= component; + } + else { + m.decRefcount(); + } + } + break; + case J3dMessage.COLORINGATTRIBUTES_CHANGED: + case J3dMessage.LINEATTRIBUTES_CHANGED: + case J3dMessage.POINTATTRIBUTES_CHANGED: + case J3dMessage.POLYGONATTRIBUTES_CHANGED: + case J3dMessage.RENDERINGATTRIBUTES_CHANGED: + case J3dMessage.TRANSPARENCYATTRIBUTES_CHANGED: + case J3dMessage.MATERIAL_CHANGED: + case J3dMessage.TEXCOORDGENERATION_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + nc.mirror.changedFrequent = ((Integer)m.args[3]).intValue(); + if (nc.mirror.changedFrequent != 0) { + objList.add(m); + addMirrorObj = true; + nc.mirror.compChanged = 1; + } + else { + updateNodeComponent((Object[])m.args); + m.decRefcount(); + } + } + break; + case J3dMessage.IMAGE_COMPONENT_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + int changes = ((Integer)m.args[3]).intValue(); + if(nc.mirror != null) + nc.mirror.changedFrequent = changes; + if (changes != 0) { + objList.add(m); + addMirrorObj = true; + } + else { + updateNodeComponent((Object[])m.args); + m.decRefcount(); + } + } + break; + case J3dMessage.TEXTUREATTRIBUTES_CHANGED: + { + + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + nc.mirror.changedFrequent = ((Integer)m.args[4]).intValue(); + + + + if (nc.mirror.changedFrequent != 0) { + objList.add(m); + addMirrorObj = true; + nc.mirror.compChanged = 1; + } + else { + updateTextureAttributes((Object[])m.args); + m.decRefcount(); + } + } + break; + + case J3dMessage.TEXTURE_CHANGED: + { + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + int attrMask = ((Integer)m.args[1]).intValue(); + nc.mirror.changedFrequent = ((Integer)m.args[3]).intValue(); + + objList.add(m); + nc.mirror.compChanged = 1; + addMirrorObj = true; + } + break; + case J3dMessage.GEOMETRY_CHANGED: + GeometryRetained geo = (GeometryRetained) m.args[1]; + int val; + if (geo instanceof IndexedGeometryArrayRetained) { + objList.add(m); + addMirrorObj = true; + if (m.args[2] == null) { + val = ((Integer)m.args[3]).intValue(); + geo.cachedChangedFrequent = val; + } + } + else { + val = ((Integer)m.args[3]).intValue(); + geo.cachedChangedFrequent = val; + m.decRefcount(); + } + break; + case J3dMessage.MORPH_CHANGED: + objList.add(m); + addMirrorObj = true; + break; + default: + m.decRefcount(); + } + } + if (addMirrorObj) { + VirtualUniverse.mc.addMirrorObject(this); + } + + // clear array to prevent memory leaks + Arrays.fill(messages, 0, nMsg, null); + } + + public void updateObject() { + + int size = objList.size(); + for (int i = 0; i < size; i++) { + J3dMessage m = (J3dMessage)objList.get(i); + // Message Only sent to RenderingAttributesStructure + // when the geometry type is indexed + if (m.type == J3dMessage.GEOMETRY_CHANGED) { + GeometryArrayRetained geo = (GeometryArrayRetained)m.args[1]; + if (m.args[2] == null) { + geo.updateMirrorGeometry(); + } + else { + geo.initMirrorGeometry(); + } + } + else if (m.type == J3dMessage.MORPH_CHANGED) { + MorphRetained morph = (MorphRetained) m.args[0]; + GeometryArrayRetained geo = (GeometryArrayRetained)morph.morphedGeometryArray.retained; + geo.updateMirrorGeometry(); + } + else if (m.type == J3dMessage.TEXTUREATTRIBUTES_CHANGED) { + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + nc.mirror.compChanged = 0; + updateTextureAttributes((Object[])m.args); + } + else if (m.type == J3dMessage.APPEARANCE_CHANGED || + m.type == J3dMessage.TEXTURE_UNIT_STATE_CHANGED){ + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + nc.mirror.compChanged = 0; + } + else { + NodeComponentRetained nc = (NodeComponentRetained)m.args[0]; + if (nc.mirror != null) + nc.mirror.compChanged = 0; + updateNodeComponent(m.args); + } + m.decRefcount(); + } + objList.clear(); + } + + + void updateNodeComponent(Object[] args) { + NodeComponentRetained n = (NodeComponentRetained)args[0]; + n.updateMirrorObject(((Integer)args[1]).intValue(), args[2]); + } + + void updateTextureAttributes(Object[] args) { + TextureAttributesRetained n = (TextureAttributesRetained)args[0]; + n.updateMirrorObject(((Integer)args[1]).intValue(), args[2], args[3]); + } + + void removeNodes(J3dMessage m) {} + + void cleanup() {} + + +} + + diff --git a/src/classes/share/javax/media/j3d/RenderingEnvironmentStructure.java b/src/classes/share/javax/media/j3d/RenderingEnvironmentStructure.java new file mode 100644 index 0000000..f63e705 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RenderingEnvironmentStructure.java @@ -0,0 +1,1754 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; +import javax.vecmath.*; + +/** + * A rendering environment structure is an object that organizes lights, + * fogs, backgrounds, clips, and model clips. + */ + +class RenderingEnvironmentStructure extends J3dStructure implements ObjectUpdate { + /** + * The list of light nodes + */ + ArrayList nonViewScopedLights = new ArrayList(); + HashMap viewScopedLights = new HashMap(); + int numberOfLights = 0; + + /** + * The list of fog nodes + */ + ArrayList nonViewScopedFogs = new ArrayList(); + HashMap viewScopedFogs = new HashMap(); + int numberOfFogs = 0; + + /** + * The list of alternate app nodes + */ + ArrayList nonViewScopedAltAppearances = new ArrayList(); + HashMap viewScopedAltAppearances = new HashMap(); + int numberOfAltApps = 0; + + /** + * The list of model clip nodes + */ + ArrayList nonViewScopedModelClips = new ArrayList(); + HashMap viewScopedModelClips = new HashMap(); + int numberOfModelClips = 0; + + /** + * The list of background nodes + */ + ArrayList nonViewScopedBackgrounds = new ArrayList(); + HashMap viewScopedBackgrounds = new HashMap(); + int numberOfBgs = 0; + + /** + * The list of clip nodes + */ + ArrayList nonViewScopedClips = new ArrayList(); + HashMap viewScopedClips = new HashMap(); + int numberOfClips = 0; + + // For closest Background selection + BackgroundRetained[] intersectedBacks = new BackgroundRetained[1]; + + + // For closest Clip selection + ClipRetained[] intersectedClips = new ClipRetained[1]; + + // For closest Background, Clip, Fog selection + Bounds[] intersectedBounds = new Bounds[1]; + + Transform3D localeXform = new Transform3D(); + Vector3d localeTranslation = new Vector3d(); + Bounds localeBounds = null; + + // For closest Fog selection + FogRetained[] intersectedFogs = new FogRetained[1]; + + // for closest alternate appearance selection + AlternateAppearanceRetained[] intersectedAltApps = new AlternateAppearanceRetained[1]; + + // For closest ModelClip selection + ModelClipRetained[] intersectedModelClips = new ModelClipRetained[1]; + + + // Back clip distance in V world + double backClipDistance; + + // ArrayList of leafRetained object whose mirrorObjects + // should be updated + ArrayList objList = new ArrayList(); + + // ArrayList of leafRetained object whose boundingleaf xform + // should be updated + ArrayList xformChangeList = new ArrayList(); + + + // freelist management of objects + ArrayList objFreeList = new ArrayList(); + + LightRetained[] retlights = new LightRetained[5]; + + // variables used for processing transform messages + boolean transformMsg = false; + UpdateTargets targets = null; + ArrayList blUsers = null; + + Integer ogInsert = new Integer(J3dMessage.ORDERED_GROUP_INSERTED); + Integer ogRemove = new Integer(J3dMessage.ORDERED_GROUP_REMOVED); + + // Used to lock the intersectedBounds {used by fog, mclip etc} + // Can used intersectedBounds itself, since this may be realloced + Object lockObj = new Object(); + + /** + * Constructs a RenderingEnvironmentStructure object in the specified + * virtual universe. + */ + RenderingEnvironmentStructure(VirtualUniverse u) { + super(u, J3dThread.UPDATE_RENDERING_ENVIRONMENT); + } + + + /** + * Returns a object array of length 5 to save the 5 objects in the message list. + */ + Object[] getObjectArray() { + Object[] objs; + int size; + + size = objFreeList.size(); + if (size == 0) { + objs = new Object[5]; + } + else { + objs = (Object[]) objFreeList.get(size - 1); + objFreeList.remove(size -1); + } + return objs; + } + + void addObjArrayToFreeList(Object[] objs) { + int i; + + for (i = 0; i < objs.length; i++) + objs[i] = null; + + objFreeList.add(objs); + } + + + + public void updateObject() { + int i, j; + Object[] args; + LeafRetained leaf; + Boolean masks; + int size; + + size = objList.size(); + for (i = 0; i < size; i++) { + args = (Object[])objList.get(i); + leaf = (LeafRetained)args[0]; + leaf.updateMirrorObject(args); + addObjArrayToFreeList(args); + } + objList.clear(); + + size = xformChangeList.size(); + for (i = 0; i < size; i++) { + leaf = (LeafRetained)xformChangeList.get(i); + leaf.updateTransformChange(); + } + xformChangeList.clear(); + + } + + void processMessages(long referenceTime) { + J3dMessage[] messages = getMessages(referenceTime);; + J3dMessage m; + int nMsg = getNumMessage(); + + if (nMsg <= 0) { + return; + } + + for (int i=0; i < nMsg; i++) { + m = messages[i]; + + switch (m.type) { + case J3dMessage.INSERT_NODES: + insertNodes(m); + break; + case J3dMessage.REMOVE_NODES: + removeNodes(m); + break; + case J3dMessage.LIGHT_CHANGED: + updateLight((Object[])m.args); + break; + case J3dMessage.BOUNDINGLEAF_CHANGED: + updateBoundingLeaf((Object[])m.args); + break; + case J3dMessage.FOG_CHANGED: + updateFog((Object[])m.args); + break; + case J3dMessage.ALTERNATEAPPEARANCE_CHANGED: + updateAltApp((Object[])m.args); + break; + case J3dMessage.SHAPE3D_CHANGED: + updateShape3D((Object[])m.args); + break; + case J3dMessage.ORIENTEDSHAPE3D_CHANGED: + updateOrientedShape3D((Object[])m.args); + break; + case J3dMessage.MORPH_CHANGED: + updateMorph((Object[])m.args); + break; + case J3dMessage.TRANSFORM_CHANGED: + transformMsg = true; + break; + case J3dMessage.SWITCH_CHANGED: + processSwitchChanged(m); + // may need to process dirty switched-on transform + if (universe.transformStructure.getLazyUpdate()) { + transformMsg = true; + } + break; + case J3dMessage.MODELCLIP_CHANGED: + updateModelClip((Object[])m.args); + break; + case J3dMessage.BACKGROUND_CHANGED: + updateBackground((Object[])m.args); + break; + case J3dMessage.CLIP_CHANGED: + updateClip((Object[])m.args); + break; + case J3dMessage.ORDERED_GROUP_INSERTED: + updateOrderedGroupInserted(m); + break; + case J3dMessage.ORDERED_GROUP_REMOVED: + updateOrderedGroupsRemoved(m); + break; + case J3dMessage.VIEWSPECIFICGROUP_CHANGED: + updateViewSpecificGroupChanged(m); + break; + case J3dMessage.VIEWSPECIFICGROUP_INIT: + initViewSpecificInfo(m); + break; + case J3dMessage.VIEWSPECIFICGROUP_CLEAR: + clearViewSpecificInfo(m); + break; + } + m.decRefcount(); + } + + if (transformMsg) { + updateTransformChange(); + transformMsg = false; + } + + VirtualUniverse.mc.addMirrorObject(this); + + Arrays.fill(messages, 0, nMsg, null); + } + + void updateOrderedGroupInserted(J3dMessage m) { + Object[] ogList = (Object[])m.args[0]; + Object[] ogChildIdList = (Object[])m.args[1]; + Object[] ogOrderedIdList = (Object[])m.args[2]; + OrderedGroupRetained og; + int index; + Object[] objs; + + for (int n = 0; n < ogList.length; n++) { + og = (OrderedGroupRetained)ogList[n]; + og.updateChildIdTableInserted(((Integer) ogChildIdList[n]).intValue(), + ((Integer) ogOrderedIdList[n]).intValue()); + og.incrChildCount(); + } + } + + void updateOrderedGroupsRemoved(J3dMessage m) { + Object[] ogList = (Object[])m.args[0]; + Object[] ogChildIdList = (Object[])m.args[1]; + OrderedGroupRetained og; + int index; + Object[] objs; + + for (int n = 0; n < ogList.length; n++) { + og = (OrderedGroupRetained)ogList[n]; + og.updateChildIdTableRemoved(((Integer) ogChildIdList[n]).intValue()); + og.decrChildCount(); + } + + } + /** + * This processes a switch change. + */ + void processSwitchChanged(J3dMessage m) { + int i; + UnorderList arrList; + int size; + Object[] nodes, nodesArr; + LeafRetained leaf; + + UpdateTargets targets = (UpdateTargets)m.args[0]; + + arrList = targets.targetList[Targets.BLN_TARGETS]; + if (arrList != null) { + BoundingLeafRetained mbleaf; + Object[] objArr = (Object[])m.args[1]; + Object[] obj; + size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (int h=0; h<size; h++) { + nodes = (Object[])nodesArr[h]; + obj = (Object[])objArr[h]; + + for (i=0; i<nodes.length; i++) { + + Object[] users = (Object[])obj[i]; + mbleaf = (BoundingLeafRetained)nodes[i]; + + //mbleaf.switchState.updateCurrentSwitchOn(); + for (int j = 0; j < users.length; j++) { + leaf = (LeafRetained)users[j]; + if (leaf instanceof FogRetained || + leaf instanceof LightRetained || + leaf instanceof ModelClipRetained || + leaf instanceof ClipRetained || + leaf instanceof AlternateAppearanceRetained || + leaf instanceof BackgroundRetained) { + leaf.updateBoundingLeaf(); + } + } + } + } + } + } + + void insertNodes(J3dMessage m) { + Object[] nodes = (Object[])m.args[0]; + ArrayList viewScopedNodes = (ArrayList)m.args[3]; + ArrayList scopedNodesViewList = (ArrayList)m.args[4]; + Object n; + int i; + GeometryAtom ga; + int num; + LightRetained lt; + FogRetained fg; + ModelClipRetained mc; + ArrayList list; + + for (i=0; i<nodes.length; i++) { + n = nodes[i]; + if (n instanceof LightRetained) { + lt = (LightRetained)n; + numberOfLights++; + + // If this particulat light is not scoped, added it + // to all the views + if (lt.inBackgroundGroup) + lt.geometryBackground.lights.add(lt); + else + nonViewScopedLights.add(lt); + + } else if (n instanceof FogRetained) { + fg = (FogRetained)n; + numberOfFogs++; + // If the fog is scoped to a view , then .. + + if (fg.inBackgroundGroup) { + fg.geometryBackground.fogs.add(fg); + } else { + nonViewScopedFogs.add(fg); + } + + + } else if (n instanceof AlternateAppearanceRetained) { + AlternateAppearanceRetained altApp = (AlternateAppearanceRetained)n; + + numberOfAltApps++; + + nonViewScopedAltAppearances.add(n); + + } else if (n instanceof BackgroundRetained) { + BackgroundRetained bg = (BackgroundRetained)n; + numberOfBgs++; + + nonViewScopedBackgrounds.add(n); + + } else if (n instanceof ClipRetained) { + ClipRetained cl = (ClipRetained)n; + numberOfClips++; + + nonViewScopedClips.add(n); + + } else if (n instanceof ModelClipRetained) { + mc = (ModelClipRetained)n; + numberOfModelClips++; + + nonViewScopedModelClips.add(n); + + } + + } + + + if (viewScopedNodes != null) { + int size = viewScopedNodes.size(); + int vlsize; + + + for (i = 0; i < size; i++) { + n = (NodeRetained)viewScopedNodes.get(i); + ArrayList vl = (ArrayList) scopedNodesViewList.get(i); + if (n instanceof LightRetained) { + ((LightRetained)n).isViewScoped = true; + numberOfLights++; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + if ((list = (ArrayList)viewScopedLights.get(view)) == null) { + list = new ArrayList(); + viewScopedLights.put(view, list); + } + list.add(n); + } + } else if (n instanceof FogRetained) { + ((FogRetained)n).isViewScoped = true; + numberOfFogs++; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + if ((list = (ArrayList)viewScopedFogs.get(view)) == null) { + list = new ArrayList(); + viewScopedFogs.put(view, list); + } + list.add(n); + } + } else if (n instanceof AlternateAppearanceRetained) { + ((AlternateAppearanceRetained)n).isViewScoped = true; + numberOfAltApps++; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + if ((list = (ArrayList)viewScopedAltAppearances.get(view)) == null) { + list = new ArrayList(); + viewScopedAltAppearances.put(view, list); + } + list.add(n); + } + } else if (n instanceof BackgroundRetained) { + ((BackgroundRetained)n).isViewScoped = true; + numberOfBgs++; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + if ((list = (ArrayList)viewScopedBackgrounds.get(view)) == null) { + list = new ArrayList(); + viewScopedBackgrounds.put(view, list); + } + list.add(n); + } + } else if (n instanceof ClipRetained) { + ((ClipRetained)n).isViewScoped = true; + numberOfClips++; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + if ((list = (ArrayList)viewScopedClips.get(view)) == null) { + list = new ArrayList(); + viewScopedClips.put(view, list); + } + list.add(n); + } + } else if (n instanceof ModelClipRetained) { + ((ModelClipRetained)n).isViewScoped = true; + numberOfModelClips++; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + if ((list = (ArrayList)viewScopedModelClips.get(view)) == null) { + list = new ArrayList(); + viewScopedModelClips.put(view, list); + } + list.add(n); + } + } + } + + } + if (numberOfLights > retlights.length) + retlights = new LightRetained[numberOfLights]; + if (intersectedFogs.length < numberOfFogs) + intersectedFogs = new FogRetained[numberOfFogs]; + if (intersectedAltApps.length < numberOfAltApps) + intersectedAltApps = new AlternateAppearanceRetained[numberOfAltApps]; + if (intersectedBacks.length < numberOfBgs) + intersectedBacks = new BackgroundRetained[numberOfBgs]; + if (intersectedClips.length < numberOfClips) + intersectedClips = new ClipRetained[numberOfClips]; + if (intersectedModelClips.length < numberOfModelClips) + intersectedModelClips = new ModelClipRetained[numberOfModelClips]; + } + + void removeNodes(J3dMessage m) { + Object[] nodes = (Object[])m.args[0]; + ArrayList viewScopedNodes = (ArrayList)m.args[3]; + ArrayList scopedNodesViewList = (ArrayList)m.args[4]; + Object n; + int i; + GeometryAtom ga; + LeafRetained oldsrc = null; + + // System.out.println("RE : removeNodes message " + m); + // System.out.println("RE : removeNodes m.args[0] " + m.args[0]); + + for (i=0; i<nodes.length; i++) { + n = nodes[i]; + if (n instanceof LightRetained) { + LightRetained lt = (LightRetained)n; + if (lt.inBackgroundGroup) { + lt.geometryBackground.lights.remove(lt); + } + else { + nonViewScopedLights.remove(nonViewScopedLights.indexOf(n)); + } + + numberOfLights--; + } else if (n instanceof FogRetained) { + numberOfFogs--; + FogRetained fg = (FogRetained)n; + if (fg.inBackgroundGroup) { + fg.geometryBackground.fogs.remove(fg); + } else { + nonViewScopedFogs.remove(nonViewScopedFogs.indexOf(n)); + } + } else if (n instanceof AlternateAppearanceRetained) { + numberOfAltApps--; + nonViewScopedAltAppearances.remove(nonViewScopedAltAppearances.indexOf(n)); + }else if (n instanceof BackgroundRetained) { + numberOfBgs--; + nonViewScopedBackgrounds.remove(nonViewScopedBackgrounds.indexOf(n)); + } else if (n instanceof ClipRetained) { + numberOfClips--; + nonViewScopedClips.remove(nonViewScopedClips.indexOf(n)); + } else if (n instanceof ModelClipRetained) { + ModelClipRetained mc = (ModelClipRetained)n; + numberOfModelClips--; + nonViewScopedModelClips.remove(nonViewScopedModelClips.indexOf(n)); + + } + else if (n instanceof GeometryAtom) { + ga = (GeometryAtom)n; + // Check that we have not already cleared the mirrorobject + // since mant geometry atoms could be generated for one + // mirror shape + if (ga.source != oldsrc) { + ga.source.clearMirrorShape(); + oldsrc = ga.source; + } + } + else if (n instanceof OrderedGroupRetained) { + // Clear the orderedBins for this orderedGroup + ((OrderedGroupRetained)n).clearDerivedDataStructures(); + } + } + if (viewScopedNodes != null) { + int size = viewScopedNodes.size(); + int vlsize; + ArrayList list; + for (i = 0; i < size; i++) { + n = (NodeRetained)viewScopedNodes.get(i); + ArrayList vl = (ArrayList) scopedNodesViewList.get(i); + if (n instanceof LightRetained) { + ((LightRetained)n).isViewScoped = false; + numberOfLights--; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + list = (ArrayList)viewScopedLights.get(view); + list.remove(n); + if (list.size() == 0) { + viewScopedLights.remove(view); + } + } + } else if (n instanceof FogRetained) { + ((FogRetained)n).isViewScoped = false; + numberOfFogs--; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + list = (ArrayList)viewScopedFogs.get(view); + list.remove(n); + if (list.size() == 0) { + viewScopedFogs.remove(view); + } + } + } else if (n instanceof AlternateAppearanceRetained) { + ((AlternateAppearanceRetained)n).isViewScoped = false; + numberOfAltApps--; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + list = (ArrayList)viewScopedAltAppearances.get(view); + list.remove(n); + if (list.size() == 0) { + viewScopedAltAppearances.remove(view); + } + } + } else if (n instanceof BackgroundRetained) { + ((BackgroundRetained)n).isViewScoped = false; + numberOfBgs--; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + list = (ArrayList)viewScopedBackgrounds.get(view); + list.remove(n); + if (list.size() == 0) { + viewScopedBackgrounds.remove(view); + } + } + } else if (n instanceof ClipRetained) { + ((ClipRetained)n).isViewScoped = false; + numberOfClips--; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + list = (ArrayList)viewScopedClips.get(view); + list.remove(n); + if (list.size() == 0) { + viewScopedClips.remove(view); + } + } + } else if (n instanceof ModelClipRetained) { + ((ModelClipRetained)n).isViewScoped = false; + numberOfModelClips--; + vlsize = vl.size(); + for (int k = 0; k < vlsize; k++) { + View view = (View)vl.get(k); + list = (ArrayList)viewScopedModelClips.get(view); + list.remove(n); + if (list.size() == 0) { + viewScopedModelClips.remove(view); + } + } + } + } + + } + } + + LightRetained[] getInfluencingLights(RenderAtom ra, View view) { + LightRetained[] lightAry = null; + ArrayList globalLights; + int numLights; + int i, j, n; + + // Need to lock retlights, since on a multi-processor + // system with 2 views on a single universe, there might + // be councurrent access + synchronized (retlights) { + numLights = 0; + if (ra.geometryAtom.source.inBackgroundGroup) { + globalLights = ra.geometryAtom.source.geometryBackground.lights; + numLights = processLights(globalLights, ra, numLights); + } else { + if ((globalLights = (ArrayList)viewScopedLights.get(view)) != null) { + numLights = processLights(globalLights, ra, numLights); + } + // now process the common lights + numLights = processLights(nonViewScopedLights, ra, numLights); + } + + boolean newLights = false; + if (ra.lights != null && ra.lights.length == numLights) { + for (i=0; i<ra.lights.length; i++) { + for (j=0; j<numLights; j++) { + if (ra.lights[i] == retlights[j]) { + break; + } + } + if (j==numLights) { + newLights = true; + break; + } + } + } else { + newLights = true; + } + if (newLights) { + lightAry = new LightRetained[numLights]; + for (i = 0; i < numLights; i++) { + lightAry[i] = (LightRetained)retlights[i]; + } + return (lightAry); + } else { + return(ra.lights); + } + } + } + + // Called while holding the retlights lock + int processLights(ArrayList globalLights, RenderAtom ra, int numLights) { + LightRetained[] shapeScopedLt; + Bounds bounds; + int i, j, n; + bounds = ra.localeVwcBounds; + int size = globalLights.size(); + + if (size > 0) { + for (i=0; i<size; i++) { + LightRetained light = (LightRetained)globalLights.get(i); + // System.out.println("vwcBounds = "+bounds); + // System.out.println("light.region = "+light.region); + // System.out.println("Intersected = "+bounds.intersect(light.region)); + // System.out.println(""); + + // if ((light.viewList != null && light.viewList.contains(view)) && + // Treat lights in background geo as having infinite bounds + if (light.lightOn && light.switchState.currentSwitchOn && + (ra.geometryAtom.source.inBackgroundGroup || bounds.intersect(light.region))){ + // Get the mirror Shape3D node + n = ((Shape3DRetained)ra.geometryAtom.source).numlights; + shapeScopedLt = ((Shape3DRetained)ra.geometryAtom.source).lights; + + // System.out.println("numLights per shape= "+n); + // scoped Fog/light is kept in the original + // shape3D node, what happens if this list changes + // while accessing them?. So, Lock. + if (light.isScoped) { + for (j = 0; j < n; j++) { + // then check if the light is scoped to + // this group + if (light == shapeScopedLt[j]) { + retlights[numLights++] = light; + break; + } + } + } + else { + retlights[numLights++] = light; + } + } + } + } + return numLights; + + } + FogRetained getInfluencingFog(RenderAtom ra, View view) { + FogRetained fog = null; + int i, j, k, n, nfogs; + Bounds closestBounds; + ArrayList globalFogs; + int numFogs; + + // Need to lock lockObj, since on a multi-processor + // system with 2 views on a single universe, there might + // be councurrent access + synchronized(lockObj) { + nfogs = 0; + Bounds bounds = ra.localeVwcBounds; + + if (intersectedBounds.length < numberOfFogs) + intersectedBounds = new Bounds[numberOfFogs]; + + if (ra.geometryAtom.source.inBackgroundGroup) { + globalFogs = ra.geometryAtom.source.geometryBackground.fogs; + nfogs = processFogs(globalFogs, ra, nfogs); + // If background, then nfogs > 1, take the first one + if (nfogs >= 1) + fog = intersectedFogs[0]; + + } else { + if ((globalFogs = (ArrayList)viewScopedFogs.get(view)) != null) { + nfogs = processFogs(globalFogs, ra, nfogs); + } + // now process the common fogs + nfogs = processFogs(nonViewScopedFogs, ra, nfogs); + + + if (nfogs == 1) + fog = intersectedFogs[0]; + + else if (nfogs > 1) { + closestBounds = bounds.closestIntersection(intersectedBounds); + for (j= 0; j < nfogs; j++) { + if (intersectedBounds[j] == closestBounds) { + fog = intersectedFogs[j]; + break; + } + } + } + } + return (fog); + } + } + + // Called while holding lockObj lock + int processFogs(ArrayList globalFogs, RenderAtom ra, int numFogs) { + int size = globalFogs.size(); + FogRetained fog; + int i, k, n; + Bounds bounds = ra.localeVwcBounds; + FogRetained[] shapeScopedFog; + + if (globalFogs.size() > 0) { + for (i = 0 ; i < size; i++) { + fog = (FogRetained) globalFogs.get(i); + // Note : There is no enable check for fog + if (fog.region != null && fog.switchState.currentSwitchOn && + (ra.geometryAtom.source.inBackgroundGroup || fog.region.intersect(bounds))) { + n = ((Shape3DRetained)ra.geometryAtom.source).numfogs; + shapeScopedFog = ((Shape3DRetained)ra.geometryAtom.source).fogs; + + if (fog.isScoped) { + for (k = 0; k < n; k++) { + // then check if the Fog is scoped to + // this group + if (fog == shapeScopedFog[k]) { + intersectedBounds[numFogs] = fog.region; + intersectedFogs[numFogs++] = fog; + break; + } + } + } + else { + intersectedBounds[numFogs] = fog.region; + intersectedFogs[numFogs++] = fog; + } + } + } + } + return numFogs; + } + + ModelClipRetained getInfluencingModelClip(RenderAtom ra, View view) { + ModelClipRetained modelClip = null; + int i, j, k, n, nModelClips; + Bounds closestBounds; + ArrayList globalModelClips; + + + + if (ra.geometryAtom.source.inBackgroundGroup) { + return null; + } + // Need to lock lockObj, since on a multi-processor + // system with 2 views on a single universe, there might + // be councurrent access + synchronized(lockObj) { + Bounds bounds = ra.localeVwcBounds; + nModelClips = 0; + if (intersectedBounds.length < numberOfModelClips) + intersectedBounds = new Bounds[numberOfModelClips]; + + if ((globalModelClips = (ArrayList)viewScopedModelClips.get(view)) != null) { + nModelClips = processModelClips(globalModelClips, ra, nModelClips); + } + + // now process the common clips + nModelClips = processModelClips(nonViewScopedModelClips, ra, nModelClips); + + + + + + modelClip = null; + if (nModelClips == 1) + modelClip = intersectedModelClips[0]; + else if (nModelClips > 1) { + closestBounds = bounds.closestIntersection(intersectedBounds); + for (j= 0; j < nModelClips; j++) { + if (intersectedBounds[j] == closestBounds) { + modelClip = intersectedModelClips[j]; + break; + } + } + } + return (modelClip); + } + + } + + int processModelClips(ArrayList globalModelClips, RenderAtom ra, int nModelClips) { + int size = globalModelClips.size(); + int i, k, n; + ModelClipRetained modelClip; + Bounds bounds = ra.localeVwcBounds; + ModelClipRetained[] shapeScopedModelClip; + + if (size > 0) { + for (i = 0; i < size; i++) { + modelClip = (ModelClipRetained) globalModelClips.get(i); + if (modelClip.enableFlag == true && + modelClip.region != null && modelClip.switchState.currentSwitchOn) { + if (modelClip.region.intersect(bounds) == true) { + n = ((Shape3DRetained)ra.geometryAtom.source).numModelClips; + shapeScopedModelClip = ((Shape3DRetained)ra.geometryAtom.source).modelClips; + + if (modelClip.isScoped) { + for (k = 0; k < n; k++) { + // then check if the modelClip is scoped to + // this group + if (shapeScopedModelClip[k] == modelClip) { + + intersectedBounds[nModelClips] = modelClip.region; + intersectedModelClips[nModelClips++] = modelClip; + break; + } + } + } + else { + intersectedBounds[nModelClips] = modelClip.region; + intersectedModelClips[nModelClips++] = modelClip; + } + } + } + } + } + return nModelClips; + } + + BackgroundRetained getApplicationBackground(BoundingSphere bounds, Locale viewLocale, View view) { + BackgroundRetained bg = null; + Bounds closestBounds; + BackgroundRetained back; + int i = 0, j = 0; + int nbacks; + ArrayList globalBgs; + + + + // Need to lock lockObj, since on a multi-processor + // system with 2 views on a single universe, there might + // be councurrent access + synchronized(lockObj) { + nbacks = 0; + if (intersectedBounds.length < numberOfBgs) + intersectedBounds = new Bounds[numberOfBgs]; + + + + if ((globalBgs = (ArrayList)viewScopedBackgrounds.get(view)) != null) { + nbacks = processBgs(globalBgs, bounds, nbacks, viewLocale); + } + nbacks = processBgs(nonViewScopedBackgrounds, bounds, nbacks, viewLocale); + + // If there are no intersections, set to black. + if (nbacks == 1) { + bg = intersectedBacks[0]; + } else if (nbacks > 1) { + closestBounds = + bounds.closestIntersection(intersectedBounds); + for (j=0; j<nbacks; j++) { + if (intersectedBounds[j] == closestBounds) { + bg = intersectedBacks[j]; + //System.out.println("matched " + closestBounds); + break; + } + } + } + return (bg); + } + } + + + // Called while holding lockObj lock + int processBgs(ArrayList globalBgs, BoundingSphere bounds, int nbacks, Locale viewLocale) { + int size = globalBgs.size(); + int i; + BackgroundRetained back; + + for (i=0; i<size; i++) { + back = (BackgroundRetained)globalBgs.get(i); + if (back.transformedRegion != null && back.switchState.currentSwitchOn) { + if (back.cachedLocale != viewLocale) { + localeBounds = (Bounds) back.transformedRegion.clone(); + // Translate the transformed region + back.cachedLocale.hiRes.difference(viewLocale.hiRes, localeTranslation); + localeXform.setIdentity(); + localeXform.setTranslation(localeTranslation); + localeBounds.transform(localeXform); + if (localeBounds.intersect(bounds) == true) { + intersectedBounds[nbacks] = localeBounds; + intersectedBacks[nbacks++] = back; + } + } + else { + if (back.transformedRegion.intersect(bounds) == true) { + intersectedBounds[nbacks] = back.transformedRegion; + intersectedBacks[nbacks++] = back; + } + } + } + } + return nbacks; + } + + double[] backClipDistanceInVworld (BoundingSphere bounds, View view) { + int i,j; + int nclips; + Bounds closestBounds; + ClipRetained clip; + boolean backClipActive; + double[] backClipDistance; + double distance; + ArrayList globalClips; + + // Need to lock intersectedBounds, since on a multi-processor + // system with 2 views on a single universe, there might + // be councurrent access + synchronized(lockObj) { + backClipDistance = null; + backClipActive = false; + nclips = 0; + distance = 0.0; + if (intersectedBounds.length < numberOfClips) + intersectedBounds = new Bounds[numberOfClips]; + + if ((globalClips = (ArrayList)viewScopedClips.get(view)) != null) { + nclips = processClips(globalClips, bounds, nclips); + } + nclips = processClips(nonViewScopedClips, bounds, nclips); + + + + + if (nclips == 1) { + distance = intersectedClips[0].backDistanceInVworld; + backClipActive = true; + } else if (nclips > 1) { + closestBounds = + bounds.closestIntersection(intersectedBounds); + for (j=0; j < nclips; j++) { + if (intersectedBounds[j] == closestBounds) { + distance = intersectedClips[j].backDistanceInVworld; + backClipActive = true; + break; + } + } + } + if (backClipActive) { + backClipDistance = new double[1]; + backClipDistance[0] = distance; + } + return (backClipDistance); + } + } + + int processClips(ArrayList globalClips, BoundingSphere bounds, int nclips) { + int i; + int size = globalClips.size(); + ClipRetained clip; + + for (i=0 ; i<size; i++) { + clip = (ClipRetained)globalClips.get(i); + if (clip.transformedRegion.intersect(bounds) == true + && clip.switchState.currentSwitchOn) { + intersectedBounds[nclips] = clip.transformedRegion; + intersectedClips[nclips++] = clip; + } + } + return nclips; + } + + + void updateLight(Object[] args) { + Object[] objs; + LightRetained light = (LightRetained)args[0]; + int component = ((Integer)args[1]).intValue(); + // Store the value to be updated during object update + // If its an ambient light, then if color changed, update immediately + if ((component & (LightRetained.INIT_MIRROR)) != 0) { + light.initMirrorObject(args); + } + + if (light instanceof AmbientLightRetained && + ((component & LightRetained.COLOR_CHANGED) != 0)) { + light.updateImmediateMirrorObject(args); + } + else if ((component & (LightRetained.COLOR_CHANGED| + LightRetained.INIT_MIRROR | + PointLightRetained.POSITION_CHANGED | + PointLightRetained.ATTENUATION_CHANGED| + DirectionalLightRetained.DIRECTION_CHANGED | + SpotLightRetained.DIRECTION_CHANGED | + SpotLightRetained.CONCENTRATION_CHANGED | + SpotLightRetained.ANGLE_CHANGED)) != 0) { + objs = getObjectArray(); + objs[0] = args[0]; + objs[1] = args[1]; + objs[2] = args[2]; + objs[3] = args[3]; + objs[4] = args[4]; + + objList.add(objs); + } + else if ((component & LightRetained.CLEAR_MIRROR) != 0) { + light.clearMirrorObject(args); + } + else { + light.updateImmediateMirrorObject(args); + } + + + + } + + void updateBackground(Object[] args) { + BackgroundRetained bg = (BackgroundRetained)args[0]; + bg.updateImmediateMirrorObject(args); + } + + void updateFog(Object[] args) { + Object[] objs; + FogRetained fog = (FogRetained)args[0]; + int component = ((Integer)args[1]).intValue(); + if ((component & FogRetained.INIT_MIRROR) != 0) { + fog.initMirrorObject(args); + // Color, distance et all should be updated when renderer + // is not running .. + objs = getObjectArray(); + objs[0] = args[0]; + objs[1] = args[1]; + objs[2] = args[2]; + objs[3] = args[3]; + objs[4] = args[4]; + objList.add(objs); + } + else if ((component & FogRetained.CLEAR_MIRROR) != 0) { + fog.clearMirrorObject(args); + // Store the value to be updated during object update + } else if ((component & (FogRetained.COLOR_CHANGED | + LinearFogRetained.FRONT_DISTANCE_CHANGED| + LinearFogRetained.BACK_DISTANCE_CHANGED | + ExponentialFogRetained.DENSITY_CHANGED)) != 0) { + objs = getObjectArray(); + objs[0] = args[0]; + objs[1] = args[1]; + objs[2] = args[2]; + objs[3] = args[3]; + objs[4] = args[4]; + objList.add(objs); + } + else { + fog.updateImmediateMirrorObject(args); + } + + + } + + void updateAltApp(Object[] args) { + AlternateAppearanceRetained altApp = (AlternateAppearanceRetained)args[0]; + int component = ((Integer)args[1]).intValue(); + if ((component & AlternateAppearanceRetained.INIT_MIRROR) != 0) { + AlternateAppearanceRetained altapp = (AlternateAppearanceRetained)args[0]; + altapp.initMirrorObject(args); + } + else if ((component & AlternateAppearanceRetained.CLEAR_MIRROR) != 0) { + AlternateAppearanceRetained altapp = (AlternateAppearanceRetained)args[0]; + altapp.clearMirrorObject(args); + } + else { + altApp.updateImmediateMirrorObject(args); + } + + + } + + void updateClip(Object[] args) { + ClipRetained clip = (ClipRetained)args[0]; + clip.updateImmediateMirrorObject(args); + } + + void updateModelClip(Object[] args) { + ModelClipRetained modelClip = (ModelClipRetained)args[0]; + Object[] objs; + int component = ((Integer)args[1]).intValue(); + + if ((component & ModelClipRetained.INIT_MIRROR) != 0) { + modelClip.initMirrorObject(args); + } + if ((component & ModelClipRetained.CLEAR_MIRROR) != 0) { + modelClip.clearMirrorObject(args); + } + else if ((component & (ModelClipRetained.PLANES_CHANGED | + ModelClipRetained.INIT_MIRROR | + ModelClipRetained.PLANE_CHANGED)) != 0) { + objs = getObjectArray(); + objs[0] = args[0]; + objs[1] = args[1]; + objs[2] = args[2]; + objs[3] = args[3]; + objs[4] = args[4]; + objList.add(objs); + } + else { + modelClip.updateImmediateMirrorObject(args); + } + + } + + void updateBoundingLeaf(Object[] args) { + BoundingLeafRetained bl = (BoundingLeafRetained)args[0]; + Object[] users = (Object[])(args[3]); + bl.updateImmediateMirrorObject(args); + // Now update all users of this bounding leaf object + for (int i = 0; i < users.length; i++) { + LeafRetained mLeaf = (LeafRetained)users[i]; + mLeaf.updateBoundingLeaf(); + } + } + + void updateShape3D(Object[] args) { + Shape3DRetained shape = (Shape3DRetained)args[0]; + shape.updateImmediateMirrorObject(args); + } + + void updateOrientedShape3D(Object[] args) { + OrientedShape3DRetained shape = (OrientedShape3DRetained)args[4]; + shape.updateImmediateMirrorObject(args); + } + + void updateMorph(Object[] args) { + MorphRetained morph = (MorphRetained)args[0]; + morph.updateImmediateMirrorObject(args); + } + + + void updateTransformChange() { + int i,j; + Object[] nodes, nodesArr; + BoundingLeafRetained bl; + LightRetained ml; + UnorderList arrList; + int size; + + targets = universe.transformStructure.getTargetList(); + blUsers = universe.transformStructure.getBlUsers(); + + // process misc environment nodes + arrList = targets.targetList[Targets.ENV_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (j = 0; j < size; j++) { + nodes = (Object[])nodesArr[j]; + + for (i = 0; i < nodes.length; i++) { + if (nodes[i] instanceof LightRetained) { + ml = (LightRetained)nodes[i]; + ml.updateImmediateTransformChange(); + xformChangeList.add(nodes[i]); + + } else if (nodes[i] instanceof FogRetained) { + FogRetained mfog = (FogRetained) nodes[i]; + mfog.updateImmediateTransformChange(); + xformChangeList.add(nodes[i]); + + } else if (nodes[i] instanceof AlternateAppearanceRetained){ + AlternateAppearanceRetained mAltApp = + (AlternateAppearanceRetained) nodes[i]; + mAltApp.updateImmediateTransformChange(); + xformChangeList.add(nodes[i]); + + } else if (nodes[i] instanceof BackgroundRetained) { + BackgroundRetained bg = (BackgroundRetained) nodes[i]; + bg.updateImmediateTransformChange(); + + } else if (nodes[i] instanceof ModelClipRetained) { + ModelClipRetained mc = (ModelClipRetained) nodes[i]; + mc.updateImmediateTransformChange(); + } + } + } + } + + // process BoundingLeaf nodes + arrList = targets.targetList[Targets.BLN_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + for (j = 0; j < size; j++) { + nodes = (Object[])nodesArr[j]; + for (i = 0; i < nodes.length; i++) { + bl = (BoundingLeafRetained)nodes[i]; + bl.updateImmediateTransformChange(); + } + } + } + + // Now notify the list of all users of bounding leaves + // to update its boundingleaf transformed region + if (blUsers != null) { + for (i = 0; i < blUsers.size(); i++) { + LeafRetained leaf = (LeafRetained) blUsers.get(i); + leaf.updateBoundingLeaf(); + } + } + targets = null; + blUsers = null; + } + + + // The first element is TRUE, if alternate app is in effect + // The second element return the appearance that should be used + // Note , I can't just return null for app, then I don't know + // if the appearance is null or if the alternate app in not + // in effect + Object[] getInfluencingAppearance(RenderAtom ra, View view) { + AlternateAppearanceRetained altApp = null; + int i, j, k, n, nAltApp;; + Bounds closestBounds; + Bounds bounds; + Object[] retVal; + ArrayList globalAltApps; + retVal = new Object[2]; + + if (ra.geometryAtom.source.inBackgroundGroup) { + retVal[0] = Boolean.FALSE; + return retVal; + } + + // Need to lock lockObj, since on a multi-processor + // system with 2 views on a single universe, there might + // be councurrent access + synchronized(lockObj) { + nAltApp = 0; + bounds = ra.localeVwcBounds; + + if (intersectedBounds.length < numberOfAltApps) + intersectedBounds = new Bounds[numberOfAltApps]; + + if ((globalAltApps =(ArrayList)viewScopedAltAppearances.get(view)) != null) { + nAltApp = processAltApps(globalAltApps, ra, nAltApp); + } + nAltApp = processAltApps(nonViewScopedAltAppearances, ra, nAltApp); + + + altApp = null; + if (nAltApp == 1) + altApp = intersectedAltApps[0]; + else if (nAltApp > 1) { + closestBounds = bounds.closestIntersection(intersectedBounds); + for (j= 0; j < nAltApp; j++) { + if (intersectedBounds[j] == closestBounds) { + altApp = intersectedAltApps[j]; + break; + } + } + } + if (altApp == null) { + retVal[0] = Boolean.FALSE; + return retVal; + } else { + retVal[0] = Boolean.TRUE; + retVal[1] = altApp.appearance; + return retVal; + } + } + } + + // Called while holding lockObj lock + int processAltApps(ArrayList globalAltApps, RenderAtom ra, int nAltApp) { + int size = globalAltApps.size(); + AlternateAppearanceRetained altApp; + int i, k, n; + Bounds bounds = ra.localeVwcBounds; + AlternateAppearanceRetained[] shapeScopedAltApp; + + + if (size > 0) { + for (i = 0; i < size; i++) { + altApp = (AlternateAppearanceRetained) globalAltApps.get(i); + // System.out.println("altApp.region = "+altApp.region+" altApp.switchState.currentSwitchOn = "+altApp.switchState.currentSwitchOn+" intersect = "+altApp.region.intersect(ra.geometryAtom.vwcBounds)); + // System.out.println("altApp.isScoped = "+altApp.isScoped); + // Note : There is no enable check for fog + if (altApp.region != null && altApp.switchState.currentSwitchOn) { + if (altApp.region.intersect(bounds) == true) { + n = ((Shape3DRetained)ra.geometryAtom.source).numAltApps; + shapeScopedAltApp = ((Shape3DRetained)ra.geometryAtom.source).altApps; + if (altApp.isScoped) { + for (k = 0; k < n; k++) { + // then check if the light is scoped to + // this group + if (altApp == shapeScopedAltApp[k]) { + + intersectedBounds[nAltApp] = altApp.region; + intersectedAltApps[nAltApp++] = altApp; + break; + } + } + } + else { + intersectedBounds[nAltApp] = altApp.region; + intersectedAltApps[nAltApp++] = altApp; + } + } + } + } + } + return nAltApp; + } + + void initViewSpecificInfo(J3dMessage m) { + int[] keys = (int[])m.args[2]; + ArrayList vlists = (ArrayList)m.args[1]; + ArrayList vsgs = (ArrayList)m.args[0]; + if (vsgs != null) { + // System.out.println("===> non null Vsg"); + int size = vsgs.size(); + for (int i = 0; i < size; i++) { + ViewSpecificGroupRetained v = (ViewSpecificGroupRetained)vsgs.get(i); + ArrayList l = (ArrayList)vlists.get(i); + int index = keys[i]; + // System.out.println("v = "+v+" index = "+index+" l = "+l); + v.cachedViewList.add(index, l); + /* + for (int k = 0; k < v.cachedViewList.size(); k++) { + System.out.println("v = "+v+" k = "+k+" v.cachedViewList.get(k) = "+v.cachedViewList.get(k)); + } + */ + } + } + } + + void clearViewSpecificInfo(J3dMessage m) { + int[] keys = (int[])m.args[1]; + ArrayList vsgs = (ArrayList)m.args[0]; + if (vsgs != null) { + int size = vsgs.size(); + for (int i = 0; i < size; i++) { + ViewSpecificGroupRetained v = (ViewSpecificGroupRetained)vsgs.get(i); + int index = keys[i]; + if (index == -1) { + int csize = v.cachedViewList.size(); + for (int j = 0; j< csize; j++) { + ArrayList l = (ArrayList)v.cachedViewList.get(j); + l.clear(); + } + v.cachedViewList.clear(); + } + else { + ArrayList l = (ArrayList) v.cachedViewList.remove(index); + l.clear(); + } + } + } + } + + void updateViewSpecificGroupChanged(J3dMessage m) { + int component = ((Integer)m.args[0]).intValue(); + Object[] objAry = (Object[])m.args[1]; + + ArrayList ltList = null; + ArrayList fogList = null; + ArrayList mclipList = null; + ArrayList altAppList = null; + ArrayList bgList = null; + ArrayList clipList = null; + int idx; + + if (((component & ViewSpecificGroupRetained.ADD_VIEW) != 0) || + ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { + int i; + Object obj; + View view = (View)objAry[0]; + ArrayList vsgList = (ArrayList)objAry[1]; + ArrayList leafList = (ArrayList)objAry[2]; + int[] keyList = (int[])objAry[3]; + int size = vsgList.size(); + // Don't add null views + + if (view != null) { + for (i = 0; i < size; i++) { + ViewSpecificGroupRetained vsg = (ViewSpecificGroupRetained)vsgList.get(i); + int index = keyList[i]; + vsg.updateCachedInformation(ViewSpecificGroupRetained.ADD_VIEW, view, index); + + } + size = leafList.size(); + // Leaves is non-null only for the top VSG + + if (size > 0) { + // Now process the list of affected leaved + for( i = 0; i < size; i++) { + obj = leafList.get(i); + if (obj instanceof LightRetained) { + ((LightRetained)obj).isViewScoped = true; + numberOfLights++; + if (ltList == null) { + if ((ltList = (ArrayList)viewScopedLights.get(view)) == null) { + ltList = new ArrayList(); + viewScopedLights.put(view, ltList); + } + } + ltList.add(obj); + } + if (obj instanceof FogRetained) { + ((FogRetained)obj).isViewScoped = true; + numberOfFogs++; + if (fogList == null) { + if ((fogList= (ArrayList)viewScopedFogs.get(view)) == null) { + fogList = new ArrayList(); + viewScopedFogs.put(view, fogList); + } + } + fogList.add(obj); + } + if (obj instanceof ModelClipRetained) { + ((ModelClipRetained)obj).isViewScoped = true; + numberOfModelClips++; + if (mclipList == null) { + if ((mclipList= (ArrayList)viewScopedModelClips.get(view)) == null) { + mclipList = new ArrayList(); + viewScopedModelClips.put(view, mclipList); + } + } + mclipList.add(obj); + } + if (obj instanceof AlternateAppearanceRetained) { + ((AlternateAppearanceRetained)obj).isViewScoped = true; + numberOfAltApps++; + if (altAppList == null) { + if ((altAppList= (ArrayList)viewScopedAltAppearances.get(view)) == null) { + altAppList = new ArrayList(); + viewScopedAltAppearances.put(view, altAppList); + } + } + altAppList.add(obj); + } + if (obj instanceof ClipRetained) { + ((ClipRetained)obj).isViewScoped = true; + numberOfClips++; + if (clipList == null) { + if ((clipList= (ArrayList)viewScopedClips.get(view)) == null) { + clipList = new ArrayList(); + viewScopedClips.put(view, clipList); + } + } + clipList.add(obj); + } + if (obj instanceof BackgroundRetained) { + ((BackgroundRetained)obj).isViewScoped = true; + numberOfBgs++; + if (bgList == null) { + if ((bgList= (ArrayList)viewScopedBackgrounds.get(view)) == null) { + bgList = new ArrayList(); + viewScopedBackgrounds.put(view, bgList); + } + } + bgList.add(obj); + } + } + if (numberOfLights > retlights.length) + retlights = new LightRetained[numberOfLights]; + if (intersectedFogs.length < numberOfFogs) + intersectedFogs = new FogRetained[numberOfFogs]; + if (intersectedAltApps.length < numberOfAltApps) + intersectedAltApps = new AlternateAppearanceRetained[numberOfAltApps]; + if (intersectedBacks.length < numberOfBgs) + intersectedBacks = new BackgroundRetained[numberOfBgs]; + if (intersectedClips.length < numberOfClips) + intersectedClips = new ClipRetained[numberOfClips]; + if (intersectedModelClips.length < numberOfModelClips) + intersectedModelClips = new ModelClipRetained[numberOfModelClips]; + } + } + } + if (((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0)|| + ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { + int i; + Object obj; + ArrayList vsgList; + ArrayList leafList; + int[] keyList; + View view; + + if ((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0) { + view = (View)objAry[0]; + vsgList = (ArrayList)objAry[1]; + leafList = (ArrayList)objAry[2]; + keyList = (int[])objAry[3]; + } + else { + view = (View)objAry[4]; + vsgList = (ArrayList)objAry[5]; + leafList = (ArrayList)objAry[6]; + keyList = (int[])objAry[7]; + } + // Don't add null views + if (view != null) { + int size = vsgList.size(); + for (i = 0; i < size; i++) { + ViewSpecificGroupRetained vsg = (ViewSpecificGroupRetained)vsgList.get(i); + int index = keyList[i]; + vsg.updateCachedInformation(ViewSpecificGroupRetained.REMOVE_VIEW, view, index); + + } + size = leafList.size(); + // Leaves is non-null only for the top VSG + if (size > 0) { + // Now process the list of affected leaved + for( i = 0; i < size; i++) { + obj = leafList.get(i); + if (obj instanceof LightRetained) { + ((LightRetained)obj).isViewScoped = false; + numberOfLights--; + if (ltList == null) { + ltList = (ArrayList)viewScopedLights.get(view); + } + ltList.remove(obj); + } + if (obj instanceof FogRetained) { + ((FogRetained)obj).isViewScoped = false; + numberOfFogs--; + if (fogList == null) { + fogList = (ArrayList)viewScopedFogs.get(view); + } + fogList.remove(obj); + } + if (obj instanceof ModelClipRetained) { + ((ModelClipRetained)obj).isViewScoped = false; + numberOfModelClips--; + if (mclipList == null) { + mclipList = (ArrayList)viewScopedModelClips.get(view); + } + mclipList.remove(obj); + } + if (obj instanceof AlternateAppearanceRetained) { + ((AlternateAppearanceRetained)obj).isViewScoped = false; + numberOfAltApps--; + if (altAppList == null) { + altAppList = (ArrayList)viewScopedAltAppearances.get(view); + } + altAppList.remove(obj); + } + if (obj instanceof ClipRetained) { + ((ClipRetained)obj).isViewScoped = false; + numberOfClips--; + if (clipList == null) { + clipList = (ArrayList)viewScopedClips.get(view); + } + clipList.remove(obj); + } + if (obj instanceof BackgroundRetained) { + ((BackgroundRetained)obj).isViewScoped = false; + numberOfBgs++; + if (bgList == null) { + bgList = (ArrayList)viewScopedBackgrounds.get(view); + } + bgList.remove(obj); + } + } + + // If there are no more lights scoped to the view, + // remove the mapping + if (ltList != null && ltList.size() == 0) + viewScopedLights.remove(view); + if (fogList != null && fogList.size() == 0) + viewScopedFogs.remove(view); + if (mclipList != null && mclipList.size() == 0) + viewScopedModelClips.remove(view); + if (altAppList != null && altAppList.size() == 0) + viewScopedAltAppearances.remove(view); + if (clipList != null && clipList.size() == 0) + viewScopedClips.remove(view); + if (bgList != null && bgList.size() == 0) + viewScopedBackgrounds.remove(view); + } + } + } + + } + + boolean isLightScopedToThisView(Object obj, View view) { + LightRetained light = (LightRetained)obj; + if (light.isViewScoped) { + ArrayList l = (ArrayList)viewScopedLights.get(view); + // If this is a scoped lights, but has no views then .. + if (l == null || !l.contains(light)) + return false; + } + return true; + } + + boolean isFogScopedToThisView(Object obj, View view) { + FogRetained fog = (FogRetained)obj; + if (fog.isViewScoped) { + ArrayList l = (ArrayList)viewScopedFogs.get(view); + // If this is a scoped fog, but has no views then .. + if (l ==null || !l.contains(fog)) + return false; + } + return true; + } + + boolean isAltAppScopedToThisView(Object obj, View view) { + AlternateAppearanceRetained altApp = (AlternateAppearanceRetained)obj; + if (altApp.isViewScoped) { + ArrayList l = (ArrayList)viewScopedAltAppearances.get(view); + // If this is a scoped altapp, but has no views then .. + if (l == null || !l.contains(altApp)) + return false; + } + return true; + } + + boolean isBgScopedToThisView(Object obj, View view) { + BackgroundRetained bg = (BackgroundRetained)obj; + if (bg.isViewScoped) { + ArrayList l = (ArrayList)viewScopedBackgrounds.get(view); + // If this is a scoped bg, but has no views then .. + if (l == null || !l.contains(bg)) + return false; + } + return true; + } + + boolean isClipScopedToThisView(Object obj, View view) { + ClipRetained clip = (ClipRetained)obj; + if (clip.isViewScoped) { + ArrayList l = (ArrayList)viewScopedClips.get(view); + // If this is a scoped clip, but has no views then .. + if (l == null || !l.contains(clip)) + return false; + } + return true; + } + + + boolean isMclipScopedToThisView(Object obj, View view) { + ModelClipRetained mclip = (ModelClipRetained)obj; + if (mclip.isViewScoped) { + ArrayList l = (ArrayList)viewScopedModelClips.get(view); + // If this is a scoped mclip, but has no views then .. + if (l == null || !l.contains(mclip)) + return false; + } + return true; + } + + void cleanup() {} +} + + diff --git a/src/classes/share/javax/media/j3d/RestrictedAccessException.java b/src/classes/share/javax/media/j3d/RestrictedAccessException.java new file mode 100644 index 0000000..e52bfec --- /dev/null +++ b/src/classes/share/javax/media/j3d/RestrictedAccessException.java @@ -0,0 +1,37 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates an attempt to access or modify a state variable + * without permission to do so. For example, invoking a set + * method for a state variable that is currently read-only. + */ +public class RestrictedAccessException extends RuntimeException { + +/** + * Create the exception object with default values. + */ + public RestrictedAccessException(){ + } + +/** + * Create the exception object that outputs a message. + * @param str the message string to be output. + */ + public RestrictedAccessException(String str) { + + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/RotPosPathInterpolator.java b/src/classes/share/javax/media/j3d/RotPosPathInterpolator.java new file mode 100644 index 0000000..ebdf064 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RotPosPathInterpolator.java @@ -0,0 +1,378 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Vector3f; +import javax.vecmath.Quat4f; +import javax.vecmath.Matrix4d; +import javax.vecmath.Point3f; + + +/** + * RotPosPathInterpolator behavior. This class defines a behavior that + * modifies the rotational and translational components of its target + * TransformGroup by linearly interpolating among a series of predefined + * knot/positon and knot/orientation pairs (using the value generated + * by the specified Alpha object). The interpolated position and + * orientation are used to generate a transform in the local coordinate + * system of this interpolator. + */ + +public class RotPosPathInterpolator extends PathInterpolator { + private Transform3D rotation = new Transform3D(); + + private Vector3f pos = new Vector3f(); + private Quat4f tQuat = new Quat4f(); + private Matrix4d tMat = new Matrix4d(); + + // Arrays of quaternions and positions at each knot + private Quat4f quats[]; + private Point3f positions[]; + private float prevInterpolationValue = Float.NaN; + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, default constructor used by cloneNode + RotPosPathInterpolator() { + } + + + /** + * Constructs a new interpolator that varies the rotation and translation + * of the target TransformGroup's transform. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node affected by this translator + * @param axisOfTransform the transform that defines the local coordinate + * system in which this interpolator operates + * @param knots an array of knot values that specify interpolation points. + * @param quats an array of quaternion values at the knots. + * @param positions an array of position values at the knots. + * @exception IllegalArgumentException if the lengths of the + * knots, quats, and positions arrays are not all the same. + */ + public RotPosPathInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + float[] knots, + Quat4f[] quats, + Point3f[] positions) { + super(alpha, target, axisOfTransform, knots); + + if (knots.length != positions.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosPathInterpolator0")); + + if (knots.length != quats.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosPathInterpolator0")); + + setPathArrays(quats, positions); + } + + + /** + * Sets the quat at the specified index for this interpolator. + * @param index the index to be changed + * @param quat the new quat value + */ + public void setQuat(int index, Quat4f quat) { + this.quats[index].set(quat); + } + + + /** + * Retrieves the quat value at the specified index. + * @param index the index of the value requested + * @param quat the quat to receive the quat value at the index + */ + public void getQuat(int index, Quat4f quat) { + quat.set(this.quats[index]); + } + + + /** + * Sets the position at the specified index for this + * interpolator. + * @param index the index to be changed + * @param position the new position value + */ + public void setPosition(int index, Point3f position) { + this.positions[index].set(position); + } + + + /** + * Retrieves the position value at the specified index. + * @param index the index of the value requested + * @param position the position to receive the position value at the index + */ + public void getPosition(int index, Point3f position) { + position.set(this.positions[index]); + } + + + /** + * Replaces the existing arrays of knot values, quaternion + * values, and position values with the specified arrays. + * The arrays of knots, quats, and positions are copied + * into this interpolator object. + * @param knots a new array of knot values that specify + * interpolation points. + * @param quats a new array of quaternion values at the knots. + * @param positions a new array of position values at the knots. + * @exception IllegalArgumentException if the lengths of the + * knots, quats, and positions arrays are not all the same. + * + * @since Java 3D 1.2 + */ + public void setPathArrays(float[] knots, + Quat4f[] quats, + Point3f[] positions) { + + if (knots.length != quats.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosPathInterpolator0")); + + if (knots.length != positions.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosPathInterpolator0")); + + setKnots(knots); + setPathArrays(quats, positions); + } + + + // Set the specific arrays for this path interpolator + private void setPathArrays(Quat4f[] quats, + Point3f[] positions) { + + this.quats = new Quat4f[quats.length]; + for(int i = 0; i < quats.length; i++) { + this.quats[i] = new Quat4f(); + this.quats[i].set(quats[i]); + } + + this.positions = new Point3f[positions.length]; + for(int i = 0; i < positions.length; i++) { + this.positions[i] = new Point3f(); + this.positions[i].set(positions[i]); + } + } + + + /** + * Copies the array of quaternion values from this interpolator + * into the specified array. + * The array must be large enough to hold all of the quats. + * The individual array elements must be allocated by the caller. + * @param quats array that will receive the quats. + * + * @since Java 3D 1.2 + */ + public void getQuats(Quat4f[] quats) { + for (int i = 0; i < this.quats.length; i++) { + quats[i].set(this.quats[i]); + } + } + + + /** + * Copies the array of position values from this interpolator + * into the specified array. + * The array must be large enough to hold all of the positions. + * The individual array elements must be allocated by the caller. + * @param positions array that will receive the positions. + * + * @since Java 3D 1.2 + */ + public void getPositions(Point3f[] positions) { + for (int i = 0; i < this.positions.length; i++) { + positions[i].set(this.positions[i]); + } + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.setTransformAxis(Transform3D)</code> + */ + + public void setAxisOfRotPos(Transform3D axisOfRotPos) { + setTransformAxis(axisOfRotPos); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.getTransformAxis()</code> + */ + public Transform3D getAxisOfRotPos() { + return getTransformAxis(); + } + + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + double quatDot; + + computePathInterpolation(alphaValue); + + if (currentKnotIndex == 0 && + currentInterpolationValue == 0f) { + tQuat.x = quats[0].x; + tQuat.y = quats[0].y; + tQuat.z = quats[0].z; + tQuat.w = quats[0].w; + pos.x = positions[0].x; + pos.y = positions[0].y; + pos.z = positions[0].z; + } else { + quatDot = quats[currentKnotIndex].x * + quats[currentKnotIndex+1].x + + quats[currentKnotIndex].y * + quats[currentKnotIndex+1].y + + quats[currentKnotIndex].z * + quats[currentKnotIndex+1].z + + quats[currentKnotIndex].w * + quats[currentKnotIndex+1].w; + if (quatDot < 0) { + tQuat.x = quats[currentKnotIndex].x + + (-quats[currentKnotIndex+1].x - + quats[currentKnotIndex].x)*currentInterpolationValue; + tQuat.y = quats[currentKnotIndex].y + + (-quats[currentKnotIndex+1].y - + quats[currentKnotIndex].y)*currentInterpolationValue; + tQuat.z = quats[currentKnotIndex].z + + (-quats[currentKnotIndex+1].z - + quats[currentKnotIndex].z)*currentInterpolationValue; + tQuat.w = quats[currentKnotIndex].w + + (-quats[currentKnotIndex+1].w - + quats[currentKnotIndex].w)*currentInterpolationValue; + } else { + tQuat.x = quats[currentKnotIndex].x + + (quats[currentKnotIndex+1].x - + quats[currentKnotIndex].x)*currentInterpolationValue; + tQuat.y = quats[currentKnotIndex].y + + (quats[currentKnotIndex+1].y - + quats[currentKnotIndex].y)*currentInterpolationValue; + tQuat.z = quats[currentKnotIndex].z + + (quats[currentKnotIndex+1].z - + quats[currentKnotIndex].z)*currentInterpolationValue; + tQuat.w = quats[currentKnotIndex].w + + (quats[currentKnotIndex+1].w - + quats[currentKnotIndex].w)*currentInterpolationValue; + } + pos.x = positions[currentKnotIndex].x + + (positions[currentKnotIndex+1].x - + positions[currentKnotIndex].x) * currentInterpolationValue; + pos.y = positions[currentKnotIndex].y + + (positions[currentKnotIndex+1].y - + positions[currentKnotIndex].y) * currentInterpolationValue; + pos.z = positions[currentKnotIndex].z + + (positions[currentKnotIndex+1].z - + positions[currentKnotIndex].z) * currentInterpolationValue; + } + tQuat.normalize(); + + // Set the rotation components + tMat.set(tQuat); + + // Set the translation components. + tMat.m03 = pos.x; + tMat.m13 = pos.y; + tMat.m23 = pos.z; + rotation.set(tMat); + + // construct a Transform3D from: axis * rotation * axisInverse + transform.mul(axis, rotation); + transform.mul(transform, axisInverse); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + RotPosPathInterpolator rppi = new RotPosPathInterpolator(); + rppi.duplicateNode(this, forceDuplicate); + return rppi; + } + + + + /** + * Copies all RotPosPathInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + RotPosPathInterpolator ri = (RotPosPathInterpolator) originalNode; + + int len = ri.getArrayLengths(); + + // No API availble to set array size, so explicitly set it here + positions = new Point3f[len]; + quats = new Quat4f[len]; + + Point3f point = new Point3f(); + Quat4f quat = new Quat4f(); + + for (int i = 0; i < len; i++) { + positions[i] = new Point3f(); + ri.getPosition(i, point); + setPosition(i, point); + + quats[i] = new Quat4f(); + ri.getQuat(i, quat); + setQuat(i, quat); + } + + } +} diff --git a/src/classes/share/javax/media/j3d/RotPosScalePathInterpolator.java b/src/classes/share/javax/media/j3d/RotPosScalePathInterpolator.java new file mode 100644 index 0000000..e5f3ff9 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RotPosScalePathInterpolator.java @@ -0,0 +1,450 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Matrix4d; +import javax.vecmath.Quat4f; +import javax.vecmath.Vector3f; +import javax.vecmath.Point3f; + + +/** + * RotPosScalePathInterpolation behavior. This class defines a behavior + * that varies the rotational, translational, and scale components of its + * target TransformGroup by linearly interpolating among a series of + * predefined knot/position, knot/orientation, and knot/scale pairs + * (using the value generated by the specified Alpha object). The + * interpolated position, orientation, and scale are used to generate + * a transform in the local coordinate system of this interpolator. The + * first knot must have a value of 0.0. The last knot must have a value + * of 1.0. An intermediate knot with index k must have a value strictly + * greater than any knot with index less than k. + */ + +public class RotPosScalePathInterpolator extends PathInterpolator { + private Transform3D rotation = new Transform3D(); + + private Vector3f pos = new Vector3f(); + private Quat4f tQuat = new Quat4f(); + private Matrix4d tMat = new Matrix4d(); + private Matrix4d sMat = new Matrix4d(); + + // Arrays of quaternions, positions, and scales at each knot + private Quat4f quats[]; + private Point3f positions[]; + private float scales[]; + + private float prevInterpolationValue = Float.NaN; + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, default constructor used by cloneNode + RotPosScalePathInterpolator() { + } + + + /** + * Constructs a new RotPosScalePathInterpolator object that varies the + * rotation, translation, and scale of the target TransformGroup's + * transform. + * @param alpha the alpha object for this interpolator. + * @param target the TransformGroup node affected by this interpolator. + * @param axisOfTransform the transform that specifies the local + * coordinate system in which this interpolator operates. + * @param knots an array of knot values that specify interpolation points. + * @param quats an array of quaternion values at the knots. + * @param positions an array of position values at the knots. + * @param scales an array of scale component values at the knots. + * @exception IllegalArgumentException if the lengths of the + * knots, quats, positions, and scales arrays are not all the same. + */ + public RotPosScalePathInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + float[] knots, + Quat4f[] quats, + Point3f[] positions, + float[] scales) { + super(alpha, target, axisOfTransform, knots); + + if (knots.length != quats.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosScalePathInterpolator1")); + + if (knots.length != positions.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosScalePathInterpolator0")); + + if (knots.length != scales.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosScalePathInterpolator2")); + + setPathArrays(quats, positions, scales); + } + + + /** + * Sets the quat value at the specified index for this + * interpolator. + * @param index the index to be changed + * @param quat the new quat value at index + */ + public void setQuat(int index, Quat4f quat) { + this.quats[index].set(quat); + } + + + /** + * Retrieves the quat value at the specified index. + * @param index the index of the value requested + * @return the interpolator's quat value at the index + */ + public void getQuat(int index, Quat4f quat) { + quat.set(this.quats[index]); + } + + + /** + * Sets the position value at the specified index for + * this interpolator. + * @param index the index to be changed + * @param position the new position value at index + */ + public void setPosition(int index, Point3f position) { + this.positions[index].set(position); + } + + + /** + * Retrieves the position value at the specified index. + * @param index the index of the value requested + * @return the interpolator's position value at the index + */ + public void getPosition(int index, Point3f position) { + position.set(this.positions[index]); + } + + + /** + * Sets the scale at the specified index for this + * interpolator. + * @param index the index to be changed + * @param scale the new scale at index + */ + public void setScale(int index, float scale) { + this.scales[index] = scale; + } + + + /** + * Retrieves the scale at the specified index. + * @param index the index of the value requested + * @return the interpolator's scale value at index + */ + public float getScale(int index) { + return this.scales[index]; + } + + + /** + * Replaces the existing arrays of knot values, quaternion + * values, position values, and scale values with the specified arrays. + * The arrays of knots, quats, positions, and scales are copied + * into this interpolator object. + * @param knots a new array of knot values that specify + * interpolation points. + * @param quats a new array of quaternion values at the knots. + * @param positions a new array of position values at the knots. + * @param scales a new array of scale component values at the knots. + * @exception IllegalArgumentException if the lengths of the + * knots, quats, positions, and scales arrays are not all the same. + * + * @since Java 3D 1.2 + */ + public void setPathArrays(float[] knots, + Quat4f[] quats, + Point3f[] positions, + float[] scales) { + if (knots.length != quats.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosScalePathInterpolator1")); + + if (knots.length != positions.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosScalePathInterpolator0")); + + if (knots.length != scales.length) + throw new IllegalArgumentException(J3dI18N.getString("RotPosScalePathInterpolator2")); + + setKnots(knots); + setPathArrays(quats, positions, scales); + } + + + // Set the specific arrays for this path interpolator + private void setPathArrays(Quat4f[] quats, + Point3f[] positions, + float[] scales) { + + this.quats = new Quat4f[quats.length]; + for(int i = 0; i < quats.length; i++) { + this.quats[i] = new Quat4f(); + this.quats[i].set(quats[i]); + } + + this.positions = new Point3f[positions.length]; + for(int i = 0; i < positions.length; i++) { + this.positions[i] = new Point3f(); + this.positions[i].set(positions[i]); + } + + this.scales = new float[scales.length]; + for(int i = 0; i < scales.length; i++) { + this.scales[i] = scales[i]; + } + } + + + /** + * Copies the array of quaternion values from this interpolator + * into the specified array. + * The array must be large enough to hold all of the quats. + * The individual array elements must be allocated by the caller. + * @param quats array that will receive the quats. + * + * @since Java 3D 1.2 + */ + public void getQuats(Quat4f[] quats) { + for (int i = 0; i < this.quats.length; i++) { + quats[i].set(this.quats[i]); + } + } + + + /** + * Copies the array of position values from this interpolator + * into the specified array. + * The array must be large enough to hold all of the positions. + * The individual array elements must be allocated by the caller. + * @param positions array that will receive the positions. + * + * @since Java 3D 1.2 + */ + public void getPositions(Point3f[] positions) { + for (int i = 0; i < this.positions.length; i++) { + positions[i].set(this.positions[i]); + } + } + + + /** + * Copies the array of scale values from this interpolator + * into the specified array. + * The array must be large enough to hold all of the scales. + * @param scales array that will receive the scales. + * + * @since Java 3D 1.2 + */ + public void getScales(float[] scales) { + for (int i = 0; i < this.scales.length; i++) { + scales[i] = this.scales[i]; + } + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.setTransformAxis(Transform3D)</code> + */ + + public void setAxisOfRotPosScale(Transform3D axisOfRotPosScale) { + setTransformAxis(axisOfRotPosScale); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.geTransformAxis()</code> + */ + public Transform3D getAxisOfRotPosScale() { + return getTransformAxis(); + } + + + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + + float scale; + double quatDot; + + computePathInterpolation(alphaValue); + + if (currentKnotIndex == 0 && + currentInterpolationValue == 0f) { + tQuat.x = quats[0].x; + tQuat.y = quats[0].y; + tQuat.z = quats[0].z; + tQuat.w = quats[0].w; + pos.x = positions[0].x; + pos.y = positions[0].y; + pos.z = positions[0].z; + scale = scales[0]; + } else { + quatDot = quats[currentKnotIndex].x * + quats[currentKnotIndex+1].x + + quats[currentKnotIndex].y * + quats[currentKnotIndex+1].y + + quats[currentKnotIndex].z * + quats[currentKnotIndex+1].z + + quats[currentKnotIndex].w * + quats[currentKnotIndex+1].w; + if (quatDot < 0) { + tQuat.x = quats[currentKnotIndex].x + + (-quats[currentKnotIndex+1].x - + quats[currentKnotIndex].x)*currentInterpolationValue; + tQuat.y = quats[currentKnotIndex].y + + (-quats[currentKnotIndex+1].y - + quats[currentKnotIndex].y)*currentInterpolationValue; + tQuat.z = quats[currentKnotIndex].z + + (-quats[currentKnotIndex+1].z - + quats[currentKnotIndex].z)*currentInterpolationValue; + tQuat.w = quats[currentKnotIndex].w + + (-quats[currentKnotIndex+1].w - + quats[currentKnotIndex].w)*currentInterpolationValue; + } else { + tQuat.x = quats[currentKnotIndex].x + + (quats[currentKnotIndex+1].x - + quats[currentKnotIndex].x)*currentInterpolationValue; + tQuat.y = quats[currentKnotIndex].y + + (quats[currentKnotIndex+1].y - + quats[currentKnotIndex].y)*currentInterpolationValue; + tQuat.z = quats[currentKnotIndex].z + + (quats[currentKnotIndex+1].z - + quats[currentKnotIndex].z)*currentInterpolationValue; + tQuat.w = quats[currentKnotIndex].w + + (quats[currentKnotIndex+1].w - + quats[currentKnotIndex].w)*currentInterpolationValue; + } + pos.x = positions[currentKnotIndex].x + + (positions[currentKnotIndex+1].x - + positions[currentKnotIndex].x) * currentInterpolationValue; + pos.y = positions[currentKnotIndex].y + + (positions[currentKnotIndex+1].y - + positions[currentKnotIndex].y) * currentInterpolationValue; + pos.z = positions[currentKnotIndex].z + + (positions[currentKnotIndex+1].z - + positions[currentKnotIndex].z) * currentInterpolationValue; + scale = scales[currentKnotIndex] + + (scales[currentKnotIndex+1] - + scales[currentKnotIndex]) * currentInterpolationValue; + } + tQuat.normalize(); + + sMat.set(scale); + tMat.set(tQuat); + tMat.mul(sMat); + // Set the translation components. + + tMat.m03 = pos.x; + tMat.m13 = pos.y; + tMat.m23 = pos.z; + rotation.set(tMat); + + // construct a Transform3D from: axis * rotation * axisInverse + transform.mul(axis, rotation); + transform.mul(transform, axisInverse); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + RotPosScalePathInterpolator ri = new RotPosScalePathInterpolator(); + ri.duplicateNode(this, forceDuplicate); + return ri; + } + + + /** + * Copies all RotPosScalePathInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + + super.duplicateAttributes(originalNode, forceDuplicate); + + RotPosScalePathInterpolator ri = + (RotPosScalePathInterpolator) originalNode; + + int len = ri.getArrayLengths(); + + // No API available to change size of array, so set here explicitly + positions = new Point3f[len]; + quats = new Quat4f[len]; + scales = new float[len]; + + Point3f point = new Point3f(); + Quat4f quat = new Quat4f(); + + for (int i = 0; i < len; i++) { + positions[i] = new Point3f(); + ri.getPosition(i, point); + setPosition(i, point); + + quats[i] = new Quat4f(); + ri.getQuat(i, quat); + setQuat(i, quat); + + setScale(i, ri.getScale(i)); + } + } + + +} diff --git a/src/classes/share/javax/media/j3d/RotationInterpolator.java b/src/classes/share/javax/media/j3d/RotationInterpolator.java new file mode 100644 index 0000000..c88ce23 --- /dev/null +++ b/src/classes/share/javax/media/j3d/RotationInterpolator.java @@ -0,0 +1,202 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * Rotation interpolator behavior. This class defines a behavior + * that modifies the rotational component of its target TransformGroup + * by linearly interpolating between a pair of specified angles + * (using the value generated by the specified Alpha object). + * The interpolated angle is used to generate a rotation transform + * about the local Y-axis of this interpolator. + */ + +public class RotationInterpolator extends TransformInterpolator { + + float minimumAngle; + float maximumAngle; + private Transform3D rotation = new Transform3D(); + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, default constructor used by cloneNode + RotationInterpolator() { + } + + /** + * Constructs a trivial rotation interpolator with a specified target, + * an default axisOfTranform set to identity, a minimum angle of 0.0f, and + * a maximum angle of 2*pi radians. + * @param alpha The alpha object for this Interpolator + * @param target The target for this rotation Interpolator + */ + public RotationInterpolator(Alpha alpha, TransformGroup target) { + super(alpha, target); + this.minimumAngle = 0.0f; + this.maximumAngle = 2.0f*(float)Math.PI; + } + + + /** + * Constructs a new rotation interpolator that varies the target + * transform node's rotational component. + * @param alpha the alpha generator to use in the rotation computation + * @param target the TransformGroup node affected by this interpolator + * @param axisOfTransform the transform that defines the local coordinate + * system in which this interpolator operates. The rotation is done + * about the Y-axis of this local coordinate system. + * @param minimumAngle the starting angle in radians + * @param maximumAngle the ending angle in radians + */ + public RotationInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + float minimumAngle, + float maximumAngle) { + super(alpha, target, axisOfTransform); + this.minimumAngle = minimumAngle; + this.maximumAngle = maximumAngle; + } + + /** + * This method sets the minimumAngle for this interpolator, in + * radians. + * @param angle the new minimal angle + */ + public void setMinimumAngle(float angle) { + this.minimumAngle = angle; + } + + /** + * This method retrieves this interpolator's minimumAngle, in + * radians. + * @return the interpolator's minimal angle value + */ + public float getMinimumAngle() { + return this.minimumAngle; + } + + /** + * This method sets the maximumAngle for this interpolator, in + * radians. + * @param angle the new maximal angle value + */ + public void setMaximumAngle(float angle) { + this.maximumAngle = angle; + } + + /** + * This method retrieves this interpolator's maximumAngle, in + * radians. + * @return the interpolator's maximal angle value + */ + public float getMaximumAngle() { + return this.maximumAngle; + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.setTransformAxis(Transform3D)</code> + */ + public void setAxisOfRotation(Transform3D axisOfRotation) { + setTransformAxis(axisOfRotation); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.getTransformAxis()</code> + */ + public Transform3D getAxisOfRotation() { + return getTransformAxis(); + } + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + double val = (1.0-alphaValue)*minimumAngle + alphaValue*maximumAngle; + + // construct a Transform3D from: axis * rotation * axisInverse + rotation.rotY(val); + transform.mul(axis, rotation); + transform.mul(transform, axisInverse); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + RotationInterpolator ri = new RotationInterpolator(); + ri.duplicateNode(this, forceDuplicate); + return ri; + } + + + /** + * Copies all RotationInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + RotationInterpolator ri = (RotationInterpolator) originalNode; + + setMinimumAngle(ri.getMinimumAngle()); + setMaximumAngle(ri.getMaximumAngle()); + + } + + +} diff --git a/src/classes/share/javax/media/j3d/RotationPathInterpolator.java b/src/classes/share/javax/media/j3d/RotationPathInterpolator.java new file mode 100644 index 0000000..6c4446b --- /dev/null +++ b/src/classes/share/javax/media/j3d/RotationPathInterpolator.java @@ -0,0 +1,296 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Quat4f; + + +/** + * RotationPathInterpolator behavior. This class defines a behavior + * that varies the rotational component of its target TransformGroup + * by linearly interpolating among a series of predefined knot/orientation + * pairs (using the value generated by the specified Alpha object). The + * interpolated orientation is used to generate a rotation transform in + * the local coordinate system. The first knot must have a value of 0.0. + * The last knot must have a value + * of 1.0. An intermediate knot with index k must have a value strictly + * greater than any knot with index less than k. + */ + +public class RotationPathInterpolator extends PathInterpolator { + private Transform3D rotation = new Transform3D(); + + private Quat4f tQuat = new Quat4f(); + + // Array of quaternions at each knot + private Quat4f quats[]; + private float prevInterpolationValue = Float.NaN; + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + // non-public, default constructor used by cloneNode + RotationPathInterpolator() { + } + + + /** + * Constructs a new RotationPathInterpolator object that varies the + * target TransformGroup node's transform. + * @param alpha the alpha object of this interpolator + * @param target the TransformGroup node affected by this interpolator + * @param axisOfTransform the transform that defines the local coordinate + * system in which this interpolator operates + * @param knots an array of knot values that specify interpolation points + * @param quats an array of quaternion values at the knots + * @exception IllegalArgumentException if the lengths of the + * knots and quats arrays are not the same. + */ + public RotationPathInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + float[] knots, + Quat4f[] quats) { + super(alpha,target, axisOfTransform, knots); + + if (knots.length != quats.length) + throw new IllegalArgumentException(J3dI18N.getString("RotationPathInterpolator0")); + + setPathArrays(quats); + } + + + /** + * Sets the quat value at the specified index for this + * interpolator. + * @param index the index to be changed + * @param quat the new quat value at the index + */ + public void setQuat(int index, Quat4f quat) { + this.quats[index].set(quat); + } + + + /** + * Retrieves the quat value at the specified index. + * @param index the index of the value requested + * @param quat the quat object that will have the + * quat value at index copied into it. + */ + public void getQuat(int index, Quat4f quat) { + quat.set(this.quats[index]); + } + + + /** + * Replaces the existing arrays of knot values and quaternion + * values with the specified arrays. + * The arrays of knots and quats are copied + * into this interpolator object. + * @param knots a new array of knot values that specify + * interpolation points + * @param quats a new array of quaternion values at the knots + * @exception IllegalArgumentException if the lengths of the + * knots and quats arrays are not the same. + * + * @since Java 3D 1.2 + */ + public void setPathArrays(float[] knots, + Quat4f[] quats) { + if (knots.length != quats.length) + throw new IllegalArgumentException(J3dI18N.getString("RotationPathInterpolator0")); + + setKnots(knots); + setPathArrays(quats); + } + + + // Set the specific arrays for this path interpolator + private void setPathArrays(Quat4f[] quats) { + this.quats = new Quat4f[quats.length]; + for(int i = 0; i < quats.length; i++) { + this.quats[i] = new Quat4f(); + this.quats[i].set(quats[i]); + } + } + + + /** + * Copies the array of quaternion values from this interpolator + * into the specified array. + * The array must be large enough to hold all of the quats. + * The individual array elements must be allocated by the caller. + * @param quats array that will receive the quats + * + * @since Java 3D 1.2 + */ + public void getQuats(Quat4f[] quats) { + for (int i = 0; i < this.quats.length; i++) { + quats[i].set(this.quats[i]); + } + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.seTransformAxis(Transform3D)</code> + */ + public void setAxisOfRotation(Transform3D axisOfRotation) { + setTransformAxis(axisOfRotation); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.getTransformAxis()</code> + */ + public Transform3D getAxisOfRotation() { + return getTransformAxis(); + } + + // The RotationPathInterpolator's initialize routine uses the default + // initialization routine. + + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + float tt; + double quatDot; + computePathInterpolation(alphaValue); + // For RPATH, take quaternion average and set rotation in TransformGroup + + if (currentKnotIndex == 0 && + currentInterpolationValue == 0f) { + tQuat.x = quats[0].x; + tQuat.y = quats[0].y; + tQuat.z = quats[0].z; + tQuat.w = quats[0].w; + } else { + quatDot = quats[currentKnotIndex].x * + quats[currentKnotIndex+1].x + + quats[currentKnotIndex].y * + quats[currentKnotIndex+1].y + + quats[currentKnotIndex].z * + quats[currentKnotIndex+1].z + + quats[currentKnotIndex].w * + quats[currentKnotIndex+1].w; + if (quatDot < 0) { + tQuat.x = quats[currentKnotIndex].x + + (-quats[currentKnotIndex+1].x - + quats[currentKnotIndex].x)*currentInterpolationValue; + tQuat.y = quats[currentKnotIndex].y + + (-quats[currentKnotIndex+1].y - + quats[currentKnotIndex].y)*currentInterpolationValue; + tQuat.z = quats[currentKnotIndex].z + + (-quats[currentKnotIndex+1].z - + quats[currentKnotIndex].z)*currentInterpolationValue; + tQuat.w = quats[currentKnotIndex].w + + (-quats[currentKnotIndex+1].w - + quats[currentKnotIndex].w)*currentInterpolationValue; + } else { + tQuat.x = quats[currentKnotIndex].x + + (quats[currentKnotIndex+1].x - + quats[currentKnotIndex].x)*currentInterpolationValue; + tQuat.y = quats[currentKnotIndex].y + + (quats[currentKnotIndex+1].y - + quats[currentKnotIndex].y)*currentInterpolationValue; + tQuat.z = quats[currentKnotIndex].z + + (quats[currentKnotIndex+1].z - + quats[currentKnotIndex].z)*currentInterpolationValue; + tQuat.w = quats[currentKnotIndex].w + + (quats[currentKnotIndex+1].w - + quats[currentKnotIndex].w)*currentInterpolationValue; + } + } + + tQuat.normalize(); + rotation.set(tQuat); + + // construct a Transform3D from: axis * rotation * axisInverse + transform.mul(axis, rotation); + transform.mul(transform, axisInverse); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + RotationPathInterpolator rpi = new RotationPathInterpolator(); + rpi.duplicateNode(this, forceDuplicate); + return rpi; + } + + + /** + * Copies all RotationPathInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + RotationPathInterpolator ri = + (RotationPathInterpolator) originalNode; + + int len = ri.getArrayLengths(); + + // No API available to change size of array, so set here explicitly + quats = new Quat4f[len]; + Quat4f quat = new Quat4f(); + + for (int i = 0; i < len; i++) { + quats[i] = new Quat4f(); + ri.getQuat(i, quat); + setQuat(i, quat); + } + + } +} diff --git a/src/classes/share/javax/media/j3d/ScaleInterpolator.java b/src/classes/share/javax/media/j3d/ScaleInterpolator.java new file mode 100644 index 0000000..7897525 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ScaleInterpolator.java @@ -0,0 +1,205 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Scale interpolation behavior. This class defines a behavior + * that modifies the uniform scale component of its target + * TransformGroup by linearly interpolating between a pair of + * specified scale values (using the value generated by the + * specified Alpha object). The interpolated scale value is + * used to generate a scale transform in the local coordinate + * system of this interpolator. + */ + +public class ScaleInterpolator extends TransformInterpolator { + + float minimumScale; + float maximumScale; + private Transform3D scale = new Transform3D(); + + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, default constructor used by cloneNode + ScaleInterpolator() { + } + + /** + * Constructs a trivial scale interpolator that varies its target + * TransformGroup node between the two specified alpha values + * using the specified alpha, an identity matrix, + * a minimum scale = 0.1f, and a maximum scale = 1.0f. + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node affected by this interpolator + */ + public ScaleInterpolator(Alpha alpha, + TransformGroup target) { + + super(alpha, target); + this.minimumScale = 0.1f; + this.maximumScale = 1.0f; + } + + /** + * Constructs a new scaleInterpolator object that varies its target + * TransformGroup node's scale component between two scale values + * (minimumScale and maximumScale). + * @param alpha the alpha object for this interpolator + * @param target the TransformGroup node affected by this interpolator + * @param axisOfTransform the transform that defines the local coordinate + * system in which this interpolator operates; the scale is done + * about the origin of this local coordinate system. + * @param minimumScale the starting scale + * @param maximumScale the ending scale + */ + public ScaleInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform, + float minimumScale, + float maximumScale) { + + super(alpha, target, axisOfTransform); + + this.minimumScale = minimumScale; + this.maximumScale = maximumScale; + } + + /** + * This method sets the minimumScale for this interpolator. + * @param scale The new minimal scale + */ + public void setMinimumScale(float scale) { + this.minimumScale = scale; + } + + /** + * This method retrieves this interpolator's minimumScale. + * @return the interpolator's minimal scale value + */ + public float getMinimumScale() { + return this.minimumScale; + } + + /** + * This method sets the maximumScale for this interpolator. + * @param scale the new maximum scale + */ + public void setMaximumScale(float scale) { + this.maximumScale = scale; + } + + /** + * This method retrieves this interpolator's maximumScale. + * @return the interpolator's maximum scale vslue + */ + public float getMaximumScale() { + return this.maximumScale; + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.setTransformAxis(Transform3D)</code> + */ + public void setAxisOfScale(Transform3D axisOfScale) { + setTransformAxis(axisOfScale); + } + + /** + * @deprecated As of Java 3D version 1.3, replaced by + * <code>TransformInterpolator.getTransformAxis()</code> + */ + public Transform3D getAxisOfScale() { + return getTransformAxis(); + } + + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + * + * @since Java 3D 1.3 + */ + public void computeTransform(float alphaValue, Transform3D transform) { + + double val = (1.0-alphaValue)*minimumScale + alphaValue*maximumScale; + + // construct a Transform3D from: axis * scale * axisInverse + scale.set(val); + transform.mul(axis, scale); + transform.mul(transform, axisInverse); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + ScaleInterpolator si = new ScaleInterpolator(); + si.duplicateNode(this, forceDuplicate); + return si; + } + + + /** + * Copies all ScaleInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + + super.duplicateAttributes(originalNode, forceDuplicate); + + ScaleInterpolator si = (ScaleInterpolator) originalNode; + + setMinimumScale(si.getMinimumScale()); + setMaximumScale(si.getMaximumScale()); + + } +} diff --git a/src/classes/share/javax/media/j3d/SceneGraphCycleException.java b/src/classes/share/javax/media/j3d/SceneGraphCycleException.java new file mode 100644 index 0000000..52dacaf --- /dev/null +++ b/src/classes/share/javax/media/j3d/SceneGraphCycleException.java @@ -0,0 +1,44 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates a graph that contains a cycle. + * Java 3D scene graphs are directed acyclic graphs and, as such, do not + * permit cycles. + * This exception is thrown when a graph containing a cycle: + * <ul> + * <li> is made live + * <li> is compiled + * <li> is cloned + * <li> has getBounds() called on it. + * </ul> + */ +public class SceneGraphCycleException extends IllegalSceneGraphException{ + +/** + * Create the exception object with default values. + */ + public SceneGraphCycleException(){ + } + +/** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public SceneGraphCycleException(String str){ + + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/SceneGraphObject.java b/src/classes/share/javax/media/j3d/SceneGraphObject.java new file mode 100644 index 0000000..71052f2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SceneGraphObject.java @@ -0,0 +1,405 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Hashtable; + +/** + * SceneGraphObject is a common superclass for + * all scene graph component objects. This includes Node, + * Geometry, Appearance, etc. + */ +public abstract class SceneGraphObject extends Object { + // Any global flags? (e.g., execution cullable, collideable) + + // Reference to the retained-mode scene-graph element. + SceneGraphObjectRetained retained; + + // This object's capability bits + private long capabilityBits = 0L; + + // This object's capabilityIsFrequent bits + private long capabilityIsFrequentBits = ~0L; + + //boolean indicating is Scene Graph is compiled + private boolean compiled = false; + + //boolean indicating if Scene Graph is live. + private boolean live = false; + + //boolean indicating if Scene Graph is live or compiled + private boolean liveOrCompiled = false; + + // A reference to user data + private Object userData = null; + + // use for cloneTree/cloneNode only, set to null after the operation + Hashtable nodeHashtable = null; + + + + /** + * Constructs a SceneGraphObject with default parameters. The default + * values are as follows: + * <ul> + * capability bits : clear (all bits)<br> + * isLive : false<br> + * isCompiled : false<br> + * user data : null<br> + * </ul> + */ + public SceneGraphObject() { + createRetained(); + } + + /** + * Creates the retained mode object that this scene graph object + * will point to. This should be overridden by those classes + * that have a specific retained mode object. + */ + void createRetained() { + this.retained = null; + + // Non-abstract subclasses of SceneGraphObject should override + // this function with code which is something like the following: + // + // this.retained = new <ClassName>Retained(); + // this.retained.setSource(this); + } + + /** + * Retrieves the specified capability bit. Note that only one capability + * bit may be retrieved per method invocation--capability bits cannot + * be ORed together. + * @param bit the bit whose value is returned + * @return true if the bit is set, false if the bit is clear + */ + public final boolean getCapability(int bit) { + return (capabilityBits & (1L << bit)) != 0L; + } + + /** + * Sets the specified capability bit. Note that only one capability bit + * may be set per method invocation--capability bits cannot be ORed + * together. + * @param bit the bit to set + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + */ + public final void setCapability(int bit) { + if (isLiveOrCompiled()) { + throw new RestrictedAccessException(J3dI18N.getString("SceneGraphObject0")); + } + + capabilityBits |= (1L << bit); + retained.handleFrequencyChange(bit); + + } + + /** + * Clear the specified capability bit. Note that only one capability bit + * may be cleared per method invocation--capability bits cannot be ORed + * together. + * @param bit the bit to clear + * @exception RestrictedAccessException if this object is part of live + * or compiled scene graph + */ + public final void clearCapability(int bit) { + if (isLiveOrCompiled()) + throw new RestrictedAccessException(J3dI18N.getString("SceneGraphObject0")); + + capabilityBits &= ~(1L << bit); + retained.handleFrequencyChange(bit); + } + + + // Internal method, returns true if no capability bits are set + final boolean capabilityBitsEmpty() { + return capabilityBits == 0L; + } + + + /** + * Retrieves the isFrequent bit associated with the specified capability + * bit. + * + * Note that only one isFrequent bit, for a single capability + * bit, may be retrieved per method invocation--capability bits cannot + * be ORed together. + * + * @param bit the bit whose value is returned + * + * @return true if the isFrequent bit is set, false if the isFrequent + * bit is clear + * + * @since Java 3D 1.3 + */ + public final boolean getCapabilityIsFrequent(int bit) { + return (capabilityIsFrequentBits & (1L << bit)) != 0L; + } + + /** + * Sets the isFrequent bit associated with the specified + * capability bit. Setting the isFrequent bit indicates that the + * application may frequently access or modify those attributes + * permitted by the associated capability bit. This can be used + * by Java 3D as a hint to avoid certain optimizations that could + * cause those accesses or modifications to be expensive. By + * default the isFrequent bit associated with each capability bit + * is set. + * + * <p> + * Unlike setCapability, this method may be called on a live scene + * graph object (but not on a compiled object). + * + * <p> + * Note that only one isFrequent bit, for a single capability bit, + * may be set per method invocation--capability bits cannot be ORed + * together. + * + * @param bit the capability bit for which to set the associated + * isFrequent bit + * + * @exception RestrictedAccessException if this object is part of a + * compiled scene graph + * + * @since Java 3D 1.3 + */ + public final void setCapabilityIsFrequent(int bit) { + if (isCompiled()) + throw new RestrictedAccessException(J3dI18N.getString("SceneGraphObject1")); + + capabilityIsFrequentBits |= (1L << bit); + retained.handleFrequencyChange(bit); + } + + /** + * Clears the isFrequent bit associated with the specified + * capability bit. Clearing the isFrequent bit indicates that the + * application will infrequently access or modify those attributes + * permitted by the associated capability bit. This can be used + * by Java 3D as a hint to enable certain optimizations that it + * might otherwise avoid, for example, optimizations that could + * cause those accesses or modifications to be expensive. + * + * <p> + * Unlike clearCapability, this method may be called on a live scene + * graph object (but not on a compiled object). + * + * <p> + * Note that only one isFrequent bit, for a single capability bit, + * may be cleared per method invocation--capability bits cannot be ORed + * together. + * + * @param bit the capability bit for which to clear the associated + * isFrequent bit + * + * @exception RestrictedAccessException if this object is part of a + * compiled scene graph + * + * @since Java 3D 1.3 + */ + public final void clearCapabilityIsFrequent(int bit) { + if (isCompiled()) + throw new RestrictedAccessException(J3dI18N.getString("SceneGraphObject1")); + + capabilityIsFrequentBits &= ~(1L << bit); + retained.handleFrequencyChange(bit); + } + + + /** + * Sets an internal flag which indicates that this scene graph object + * has been compiled. + */ + final void setCompiled() { + this.compiled = true; + this.liveOrCompiled = this.live || this.compiled; + } + + /** + * Returns a flag indicating whether the node is part of a scene graph + * that has been compiled. If so, then only those capabilities explicitly + * allowed by the object's capability bits are allowed. + * @return true if node is part of a compiled scene graph, else false + */ + + public final boolean isCompiled() { + return this.compiled; + } + + /** + * Sets an internal flag which indicates that this scene graph object + * is part of a live scene graph. + */ + final void setLive() { + this.live = true; + this.liveOrCompiled = this.live || this.compiled; + } + + /** + * Clears an internal flag which indicates that this scene graph object + * is no longer part of a live scene graph. + */ + final void clearLive() { + this.live = false; + this.liveOrCompiled = this.live || this.compiled; + } + + /** + * Returns a flag indicating whether the node is part of a live + * scene graph. + * @return true if node is part of a live scene graph, else false + */ + public final boolean isLive() { + return this.live; + } + + /** + * Returns a flag indicating whether the node is part of a live + * scene graph or a compiled scene graph. + * @return true if either live or compiled + */ + final boolean isLiveOrCompiled() { + return liveOrCompiled; + } + + final void checkForLiveOrCompiled() { + if (isLiveOrCompiled()) + throw new RestrictedAccessException(J3dI18N.getString("SceneGraphObject2")); + } + + /** + * Sets the userData field associated with this scene graph object. + * The userData field is a reference to an arbitrary object + * and may be used to store any user-specific data associated + * with this scene graph object--it is not used by the Java 3D API. + * If this object is cloned, the userData field is copied + * to the newly cloned object. + * @param userData a reference to the new userData field + */ + public void setUserData(Object userData) { + this.userData = userData; + } + + /** + * Retrieves the userData field from this scene graph object. + * @return the current userData field + */ + public Object getUserData() { + return this.userData; + } + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced by that node have been duplicated via a call to + * <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf + * node and cloned NodeComponent's method + * will be called and the Leaf node/NodeComponent can then look up + * any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + } + + + /** + * Copies all SceneGraphObject information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * + * @see Group#cloneNode + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + protected void duplicateSceneGraphObject(SceneGraphObject originalNode) { + // Duplicate any class specific data here. + capabilityBits = originalNode.capabilityBits; + userData = originalNode.userData; + } + + + /** + * If <code>forceDuplicate</code> is <code>true</code> or + * <code>duplicateOnCloneTree</code> flag is true. This procedure + * will return a clone of originalNode or the value in + * in <code>nodeHashtable</code> if found. Otherwise return + * <code>originalNode</code> + * + * This method is called from the + * <code>duplicateAttributes</code> method during cloneNodeComponent. + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @param nodeHashtable is used to keep track of mapping between old and + * new node references. + */ + NodeComponent getNodeComponent(NodeComponent originalNodeComponent, + boolean forceDuplicate, + Hashtable hashtable) { + if ((originalNodeComponent != null) && + (forceDuplicate || + originalNodeComponent.duplicateChild())) { + NodeComponent nc = (NodeComponent) + hashtable.get(originalNodeComponent); + if (nc == null) { + originalNodeComponent.nodeHashtable = hashtable; + try { + nc = originalNodeComponent. + cloneNodeComponent(forceDuplicate); + } catch (RuntimeException e) { + // must reset nodeHashtable in any case + originalNodeComponent.nodeHashtable = null; + throw e; + } + originalNodeComponent.nodeHashtable = null; + // put link to be shared by other Node + hashtable.put(originalNodeComponent, nc); + } // use the share clone node otherwise + return nc; + } else { + return originalNodeComponent; + } + } +} diff --git a/src/classes/share/javax/media/j3d/SceneGraphObjectRetained.java b/src/classes/share/javax/media/j3d/SceneGraphObjectRetained.java new file mode 100644 index 0000000..b776be0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SceneGraphObjectRetained.java @@ -0,0 +1,170 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; +import java.util.Hashtable; + +/** + * SceneGraphObjectRetained is a superclass, which has things that + * are common to all retained scene graph component objects. + */ +abstract class SceneGraphObjectRetained extends IndexedObject + implements Cloneable { + + // The object which created this retained mode object + SceneGraphObject source; + + // This boolean is true when the object is in a Background BranchGroup + boolean inBackgroundGroup = false; + + // This boolean is true when the object is in the update list + boolean onUpdateList = false; + + // A flag to indicate if the node is in setLive, note that + // since the live is set to true only at the end, this flag + // is need for scoping to mark the nodes that are inSetLive + + boolean inSetLive = false; + + // A flag used in compile to indicate if this node needs to go + // through the second pass + + final static int DONT_MERGE = 0; + final static int MERGE = 1; + final static int MERGE_DONE = 2; + + int mergeFlag = 0; + + /** + * Caches the source object that created this retained mode object. + * @param source the object which created this retained mode object. + */ + void setSource(SceneGraphObject source) { + this.source = source; + } + + /** + * Returns the cached source object that created this retained mode + * object. + * @return the object which created this retained mode object. + */ + SceneGraphObject getSource() { + return this.source; + } + + void markAsLive() { + this.source.setLive(); + inSetLive = false; + } + + void setLive(boolean inBackgroundGroup) { + doSetLive(inBackgroundGroup); + markAsLive(); + } + boolean isInSetLive() { + return inSetLive; + } + + /** + * Makes the internal node live. + */ + void doSetLive(boolean inBackgroundGroup) { + inSetLive = true; + this.inBackgroundGroup = inBackgroundGroup; + } + + void setLive(SetLiveState s) { + doSetLive(s); + markAsLive(); + } + + /** + * Makes the internal node live. + */ + void doSetLive(SetLiveState s) { + inSetLive = true; + inBackgroundGroup = s.inBackgroundGroup; + } + + /** + * Makes the internal node not live + */ + void clearLive(VirtualUniverse univ, int index, + boolean sharedGroup, HashKey [] keys) { + inBackgroundGroup = false; + this.source.clearLive(); + } + + /** + * Makes the internal node not live + */ + void clearLive() { + inBackgroundGroup = false; + this.source.clearLive(); + } + + /** + * This marks this object as compiled. + */ + void setCompiled() { + this.source.setCompiled(); + } + + + /** + * This is the default compile() method, which just marks the sgo as + * compiled. + */ + void compile(CompileState compState) { + setCompiled(); + } + + void merge(CompileState compState) { + } + + void mergeTransform(TransformGroupRetained xform) { + } + + void traverse(boolean sameLevel, int level) { + + System.out.println(); + for (int i = 0; i < level; i++) { + System.out.print("."); + } + System.out.print(this); + } + + /** + * true if component can't be read or written after compile or setlive() + */ + boolean isStatic() { + return source.capabilityBitsEmpty(); + } + + protected Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + void handleFrequencyChange(int bit) { + } + + VirtualUniverse getVirtualUniverse() { + return null; + } + +} diff --git a/src/classes/share/javax/media/j3d/SceneGraphPath.java b/src/classes/share/javax/media/j3d/SceneGraphPath.java new file mode 100644 index 0000000..123d3b8 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SceneGraphPath.java @@ -0,0 +1,652 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Point3d; +import javax.vecmath.Point4d; + +/** + * A SceneGraphPath object represents the path from a Locale to a + * terminal node in the scene graph. This path consists of a Locale, a + * terminal node, and an array of internal nodes that are in the path + * from the Locale to the terminal node. The terminal node may be + * either a Leaf node or a Group node. A valid SceneGraphPath must + * uniquely identify a specific instance of the terminal node. For + * nodes that are not under a SharedGroup, the minimal SceneGraphPath + * consists of the Locale and the terminal node itself. For nodes that + * are under a SharedGroup, the minimal SceneGraphPath consists of the + * Locale, the terminal node, and a list of all Link nodes in the path + * from the Locale to the terminal node. A SceneGraphPath may optionally + * contain other interior nodes that are in the path. + * A SceneGraphPath is verified for correctness and uniqueness when + * it is sent as an argument to other methods of Java 3D. + * <p> + * In the array of internal nodes, the node at index 0 is the node + * closest to the Locale. The indices increase along the path to the + * terminal node, with the node at index length-1 being the node closest + * to the terminal node. The array of nodes does not contain either the + * Locale (which is not a node) or the terminal node. + * <p> + * When a SceneGraphPath is returned from the picking or collision + * methods of Java 3D, it will also contain the value of the + * LocalToVworld transform of the terminal node that was in effect at + * the time the pick or collision occurred. + * Note that ENABLE_PICK_REPORTING and ENABLE_COLLISION_REPORTING are + * disabled by default. This means that the picking and collision + * methods will return the minimal SceneGraphPath by default. + * + * @see Node#ENABLE_PICK_REPORTING + * @see Node#ENABLE_COLLISION_REPORTING + * @see BranchGroup#pickAll + * @see BranchGroup#pickAllSorted + * @see BranchGroup#pickClosest + * @see BranchGroup#pickAny + */ + +public class SceneGraphPath { + + Locale root = null; + Node[] interior = null; + Node item = null; + Transform3D transform = new Transform3D(); + + // Intersect Point for item when picked + Point3d intersectPoint = new Point3d(); + + double pickDistance; // distance to pick location + + /** + * Constructs a SceneGraphPath object with default parameters. + * The default values are as follows: + * <ul> + * root : null<br> + * object : null<br> + * list of (interior) nodes : null<br> + * transform : identity<br> + * </ul> + */ + public SceneGraphPath() { + // Just use defaults + } + + /** + * Constructs a new SceneGraphPath object. + * @param root the Locale object of this path + * @param object the terminal node of this path + */ + public SceneGraphPath(Locale root, Node object) { + + this.item = object; + this.root = root; + } + + /** + * Constructs a new SceneGraphPath object. + * @param root the Locale object of this path + * @param nodes an array of node objects in the path from + * the Locale to the terminal node + * @param object the terminal node of this path + */ + public SceneGraphPath(Locale root, Node nodes[], Node object) { + + this.item = object; + this.root = root; + this.interior = new Node[nodes.length]; + for (int i = 0; i < nodes.length; i++) + this.interior[i] = nodes[i]; + } + + + /** + * Constructs a new SceneGraphPath object + * @param sgp the SceneGraphPath to copy from + */ + SceneGraphPath(SceneGraphPath sgp) { + set(sgp); + } + + /** + * Sets this path's values to that of the specified path. + * @param newPath the SceneGraphPath to copy + */ + public final void set(SceneGraphPath newPath) { + this.root = newPath.root; + this.item = newPath.item; + this.transform.set(newPath.transform); + if(newPath.interior != null && newPath.interior.length > 0) { + interior = new Node[newPath.interior.length]; + for (int i = 0; i < interior.length; i++) + this.interior[i] = newPath.interior[i]; + } + else + interior = null; + } + + /** + * Sets this path's Locale to the specified Locale. + * @param newLocale The new Locale + */ + public final void setLocale(Locale newLocale) { + root = newLocale; + } + + /** + * Sets this path's terminal node to the specified node object. + * @param object the new terminal node + */ + public final void setObject(Node object) { + this.item = object; + } + + /** + * Sets this path's node objects to the specified node objects. + * @param nodes an array of node objects in the path from + * the Locale to the terminal node + */ + public final void setNodes(Node nodes[]) { + + if(nodes != null && nodes.length > 0) { + interior = new Node[nodes.length]; + for (int i = 0; i < nodes.length; i++) + this.interior[i] = nodes[i]; + } + else + interior = null; + } + + /** + * Replaces the node at the specified index with newNode. + * @param index the index of the node to replace + * @param newNode the new node + * @exception NullPointerException if the node array pointer is null. + * + */ + public final void setNode(int index, Node newNode) { + if(interior == null) + throw new NullPointerException(J3dI18N.getString("SceneGraphPath0")); + + interior[index] = newNode; + } + + /** + * Sets the transform component of this SceneGraphPath to the value of + * the passed transform. + * @param trans the transform to be copied. trans should be the + * localToVworld matrix of this SceneGraphPath object. + */ + public final void setTransform(Transform3D trans) { + transform.set(trans); + } + + /** + * Returns a copy of the transform associated with this SceneGraphPath; + * returns null if there is no transform associated. + * If this SceneGraphPath was returned by a Java 3D picking or + * collision method, the local coordinate to virtual world + * coordinate transform for this scene graph object at the + * time of the pick or collision is recorded. + * @return the local to VWorld transform + */ + public final Transform3D getTransform() { + return new Transform3D(transform); + } + + /** + * Retrieves the path's Locale + * @return this path's Locale + */ + public final Locale getLocale() { + return this.root; + } + + /** + * Retrieves the path's terminal node object. + * @return the terminal node + */ + public final Node getObject() { + return this.item; + } + + /** + * Retrieves the number of nodes in this path. The number of nodes + * does not include the Locale or the terminal node object itself. + * @return a count of the number of nodes in this path + */ + public final int nodeCount() { + if(interior == null) + return 0; + return interior.length; + } + + /** + * Retrieves the node at the specified index. + * @param index the index specifying which node to retrieve + * @return the specified node + */ + public final Node getNode(int index) { + if(interior == null) + throw new + ArrayIndexOutOfBoundsException(J3dI18N.getString("SceneGraphPath1")); + return interior[index]; + } + + /** + * Returns true if all of the data members of path testPath are + * equal to the corresponding data members in this SceneGraphPath and + * if the values of the transforms is equal. + * @param testPath the path we will compare this object's path against. + * @return true or false + */ + public boolean equals(SceneGraphPath testPath) { + boolean result = true; + try { + + if(testPath == null || root != testPath.root || item != testPath.item) + return false; + + result = transform.equals(testPath.transform); + + if(result == false) + return false; + + if(interior == null || testPath.interior == null) { + if(interior != testPath.interior) + return false; + else + result = (root == testPath.root && item == testPath.item); + + } else { + if (interior.length == testPath.interior.length) { + for (int i = 0; i < interior.length; i++) + if (interior[i] != testPath.interior[i]) { + return false; + } + } + else + return false; + } + + } + catch (NullPointerException e2) {return false;} + + return result; + } + + /** + * Returns true if the Object o1 is of type SceneGraphPath and all of the + * data members of o1 are equal to the corresponding data members in + * this SceneGraphPath and if the values of the transforms is equal. + * @param o1 the object we will compare this SceneGraphPath's path against. + * @return true or false + */ + public boolean equals(Object o1) { + boolean result = true; + + try { + SceneGraphPath testPath = (SceneGraphPath)o1; + if(testPath == null || root != testPath.root || item != testPath.item) + return false; + + result = transform.equals(testPath.transform); + + if(result == false) + return false; + + if(interior == null || testPath.interior == null) { + if(interior != testPath.interior) + return false; + else + result = (root == testPath.root && item == testPath.item); + + } else { + if (interior.length == testPath.interior.length) { + for (int i = 0; i < interior.length; i++) + if (interior[i] != testPath.interior[i]) { + return false; + } + } + else + return false; + } + + return result; + } + catch (NullPointerException e2) {return false;} + catch (ClassCastException e1) {return false;} + } + + + /** + * Returns a hash number based on the data values in this + * object. Two different SceneGraphPath objects with identical data + * values (ie, returns true for trans.equals(SceneGraphPath) ) will + * return the same hash number. Two Paths with different data members + * may return the same hash value, although this is not likely. + * @return the integer hash value + */ + public int hashCode() { + HashKey key = new HashKey(250); + // NOTE: Needed to add interior != null because this method is called + // by object.toString() when interior is null. + if(interior != null && item != null) { + for(int i=0; i<interior.length; i++) { + key.append(LinkRetained.plus).append( item.toString() ); + } + } + return( key.hashCode() + transform.hashCode() ); + } + + /** + * Determines whether two SceneGraphPath objects represent the same + * path in the scene graph; either object might include a different + * subset of internal nodes; only the internal link nodes, the Locale, + * and the Node itself are compared. The paths are not validated for + * correctness or uniqueness. + * @param testPath the SceneGraphPath to be compared to this SceneGraphPath + * @return true or false + */ + public final boolean isSamePath(SceneGraphPath testPath) { + int count=0, i; + + if(testPath == null || testPath.item != this.item || root != testPath.root) + return false; + + if(interior != null && testPath.interior != null) { + for(i=0 ; i<interior.length ; i++) { + if(interior[i] instanceof Link) { + // found Link in this, now check for matching in testPath + while(count < testPath.interior.length) { + if(testPath.interior[count] instanceof Link) { + if(testPath.interior[count] != interior[i]) { + return false; + } + count++; + break; + } + count++; + // if true, this had an extra Link + if(count == testPath.interior.length) + return false; + } + } + } + // make sure testPath doesn't have any extra Links + while(count < testPath.interior.length) { + if(testPath.interior[count] instanceof Link) + return false; + count++; + } + } else if(interior != testPath.interior) // ==> they are not both null + return false; + + return true; + } + + /** + * Returns a string representation of this object; + * the string contains the class names of all Nodes in the SceneGraphPath, + * the toString() method of any associated user data provided by + * SceneGraphObject.getUserData(), and also prints out the transform, + * if it is not null. + * @return String representation of this object + */ + public String toString() { + + StringBuffer str = new StringBuffer(); + Object obj; + + if(root == null && interior == null && item == null) + return (super.toString()); + + if(root != null) + str.append(root + " : "); + + if(interior != null) { + for(int i=0; i<interior.length; i++) { + + str.append( interior[i].getClass().getName()); + obj = interior[i].getUserData(); + if(obj == null) + str.append(" : "); + else + str.append(", " + obj + " : "); + } + } + + if(item != null) { + // str.append( item + ", "+ item.getUserData() + "--"+intersectPoint ); + str.append( item.getClass().getName() ); + obj = item.getUserData(); + if(obj != null) + str.append(", " + obj); + + try { + if (item.getClass().getName().equals("javax.media.j3d.Shape3D")) + str.append( ((Shape3D)item).getGeometry() ); + } + catch( CapabilityNotSetException e) {} + } + + str.append("\n" + "LocalToVworld Transform:\n" + transform); + + return new String(str); + } + + /** + * Determine if this SceneGraphPath is unique and valid + * The graph don't have to be live for this checking. + * Set Locale when it is null. + * Only the essential link node which led to the Locale + * is validated. + */ + boolean validate() { + NodeRetained node = (NodeRetained) item.retained; + + Locale locale = node.locale; + + if (root != null) { + if (item.isLive()) { + if (locale != root) { + return false; + } + } + } else { + root = locale; + } + + int idx = (interior == null ? 0: interior.length); + + do { + if (node instanceof SharedGroupRetained) { + if (interior == null) + return false; + while (--idx > 0) { + if (((SharedGroupRetained) + node).parents.contains(interior[idx].retained)) { + break; + } + } + if (idx < 0) { + return false; + } + node = (NodeRetained) interior[idx].retained; + } else { + node = node.parent; + } + } while (node != null); + + return true; + } + + + // return key of this path or null is not in SharedGroup + void getHashKey(HashKey key) { + if (interior != null) { + key.reset(); + key.append(root.nodeId); + for(int i=0; i<interior.length; i++) { + Node node = interior[i]; + + if (!node.isLive()) { + throw new RuntimeException(J3dI18N.getString("SceneGraphPath3")); + } + + NodeRetained nodeR = (NodeRetained) node.retained; + if (nodeR.nodeType == NodeRetained.LINK) { + key.append("+").append(nodeR.nodeId); + } + } + } + } + + /** + * Determines whether this SceneGraphPath is unique and valid. The + * verification determines that all of the nodes are live, that the + * specified path is unique, that the correct Locale is specified, and + * that there is a Node specified. + */ + boolean validate(HashKey key) { + + int i; + + // verify that there is at least a Locale and Node specified + if( root == null ) + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath2")); + + if( item == null ) + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath10")); + + // verify liveness + if( !item.isLive() ) + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath3")); + + try { + getHashKey(key); + } catch (RuntimeException ex) { + throw new IllegalArgumentException(ex.getMessage()); + } + + // The rest of the code verifies uniqueness; it traverses the retained + // hierarchy of the scene graph. This could be problematic later in + // when certain compile mode optimizations are added. */ + + NodeRetained bottomNR, currentNR, nextNR=null; + Node currentNode; + int count = 0; + + // Need to traverse the retained hierarchy on a live scene graph + // from bottom to top + // + // bottomNR = last verified node; as nodes are verified, bottomNR + // moves up the scen graph + // nextNR = Next node that the user has specified after bottomNR + // currentNR = current node; is changing as it covers all the + // nodes from bottomNR to nextNR + + // If the parent of a NodeRetained is null, we know that the parent + // is either a BranchGroupRetained at the top of a scene graph or + // it is a SharedGroupRetained, potentially with multiple parents. + + bottomNR = (NodeRetained)(item.retained); + + if(interior != null) { + for(i=interior.length-1; i >=0 ; i--) { + nextNR = (NodeRetained)(interior[i].retained); + currentNR = bottomNR.parent; + if(currentNR == null && bottomNR instanceof SharedGroupRetained) { + if(((SharedGroupRetained)(bottomNR)).parents.contains(nextNR) ) + currentNR = nextNR; + else + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath5")); + + } + + while(currentNR != nextNR) { + if(currentNR == null) { + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath11")); + } + + if(currentNR instanceof SharedGroupRetained) { + if(((SharedGroupRetained) + (currentNR)).parents.contains(nextNR) ) + currentNR = nextNR; + else + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath5")); + + } else { + currentNR = currentNR.parent; + } + } + bottomNR = currentNR; + } + } + + // Now go from bottomNR to Locale + currentNR = bottomNR.parent; + if(currentNR == null && bottomNR instanceof SharedGroupRetained) { + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath5")); + } + + while(currentNR != null) { + if(currentNR instanceof LinkRetained) { + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath5")); + } + + bottomNR = currentNR; + currentNR = currentNR.parent; + if(currentNR == null && bottomNR instanceof SharedGroupRetained) { + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath5")); + } + } + + // get the real BranchGroup from the BranchGroupRetained + currentNode = (Node)(bottomNR.source); + // now bottomNR should be a BranchGroup -- should try an assert here + if(!root.branchGroups.contains(currentNode)) { + throw new IllegalArgumentException(J3dI18N.getString("SceneGraphPath9")); + } + + return true; + } + + /** + * Returns the distance from the intersectPoint for item and + * origin. + */ + double getDistanceFrom( Point3d origin ) { + return intersectPoint.distance(origin); + } + + /** + * Returns the distance of the pick + */ + double getDistance() { + return pickDistance; + } + + final void setIntersectPoint( Point3d point ) { + intersectPoint.set(point); + } + + final void setIntersectPointDis( Point4d pickLocation ) { + // System.out.println( "setIntersectPointDis pickLocation= "+pickLocation); + intersectPoint.x = pickLocation.x; + intersectPoint.y = pickLocation.y; + intersectPoint.z = pickLocation.z; + pickDistance = pickLocation.w; + } + + final Point3d getIntersectPoint() { + return intersectPoint; + } +} diff --git a/src/classes/share/javax/media/j3d/Screen3D.java b/src/classes/share/javax/media/j3d/Screen3D.java new file mode 100644 index 0000000..dc9cf82 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Screen3D.java @@ -0,0 +1,490 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.Hashtable; + +/** + * The Screen3D Object contains all information about a particular screen. + * All Canvas3D objects on the same physical screen (display device) + * refer to the same Screen3D object. Note that Screen3D has no public + * constructors--it is obtained from the Canvas3D via the getScreen3D + * method. + * <p> + * Default values for Screen3D parameters are as follows: + * <ul> + * physical screen width : 0.0254/90.0 * screen width (in pixels)<br> + * physical screen height : 0.0254/90.0 * screen height (in pixels)<br> + * tracker base to image plate transform : identity<br> + * head tracker to left image plate transform : identity<br> + * head tracker to right image plate transform : identity<br> + * off-screen size : (0,0)<br> + * </ul> + * <P> + * <b>Offscreen Rendering</b><P> + * New for Java 3D 1.2, an off-screen rendering mode allows rendering + * to a memory image, which is possibly larger than the screen. The + * setSize and getSize methods are defined specifically for this + * mode. Note that the off-screen size, physical width, and physical height + * must be set prior to rendering + * to the associated off-screen canvas. Failure to do so will result + * in an exception.<P> + * <b>Calibration Parameters</b><P> + * The Screen3D object must be calibrated with the coexistence volume. + * The Screen3D class provides several methods for defining the + * calibration parameters.<P> + * <UL>Measured Parameters<P> + * The screen's (image plate's) physical width and height (in meters) + * is set once, typically by a browser, calibration program, system + * administrator, or system calibrator, not by an applet. These values + * must be determined by measuring the display's active image width + * and height. In the case of a head-mounted display, this should be + * the display's apparent width and height at the focal plane. These + * values are defined by the setPhysicalScreenWidth and + * setPhysicalScreenHeight methods.<P> + * + * Head-tracker Coordinate System<P> + * If head tracking is enabled, one of two parameters need to be specified:<P> + * <UL><LI>If the view policy is SCREEN_VIEW, the tracker-base-to-image-plate + * coordinate system must be specified (setTrackerBaseToImagePlate method). + * This coordinate system must be recalibrated whenever the image + * plate moves relative to the tracker.</LI><P> + * + * <LI>If the view policy is HMD_VIEW, the head-tracker-to-left-image-plate + * and head-tracker-to-right-image-plate coordinate systems must be + * specified (setHeadTrackerToLeftImagePlate and + * setHeadTrackerToRightImagePlate methods).</LI><P></UL> + * </UL><P> + * @see Canvas3D + * @see Canvas3D#getScreen3D + */ + +public class Screen3D extends Object { + private static final boolean debug = false; + + // Assume a default of 90 DPI: 90 pix/inch = 1/90 inch/pix = + // 0.0254/90 meter/pix + private static final double METERS_PER_PIXEL = 0.0254/90.0; + + // GraphicsDevice associated with this Screen3D object. Note that + // all on-screen Canvas3D objects that are created on the same + // GraphicsDevice will share the same Screen3D. + GraphicsDevice graphicsDevice; + + // Flag indicating whether this Screen3D is associated with + // an off-screen Canvas3D or with one or more on-screen Canvas3Ds + boolean offScreen; + + // The display connection (X11 only) and the screen ID + long display; + int screen; + + // The width and height of the screen in meters. + double physicalScreenWidth; + double physicalScreenHeight; + + // Screen size in pixels + Dimension screenSize = new Dimension(0, 0); + + // + // Tracker-base coordinate system to image-plate coordinate + // system transform. This transform + // is typically a calibration constant. + // This is used only in SCREEN_VIEW mode. + // + Transform3D trackerBaseToImagePlate = new Transform3D(); + + // + // Head-tracker coordinate system to left and right image-plate + // coordinate system transforms. These transforms are typically + // calibration constants. These are used only in HMD_VIEW mode. + // + Transform3D headTrackerToLeftImagePlate = new Transform3D(); + Transform3D headTrackerToRightImagePlate = new Transform3D(); + + + // Physical screen size related field has changed. + static final int PHYSICAL_SCREEN_SIZE_DIRTY = 0x01; + // Screen size field has changed. + static final int SCREEN_SIZE_DIRTY_DIRTY = 0x02; + // Tracker base to image plate field has changed. + static final int TRACKER_BASE_TO_IMAGE_PLATE_DIRTY = 0x04; + // Head tracker to image plate field has changed. + static final int HEAD_TRACKER_TO_IMAGE_PLATE_DIRTY = 0x08; + + // Mask that indicates this Screen3D view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + int scrDirtyMask = (PHYSICAL_SCREEN_SIZE_DIRTY | SCREEN_SIZE_DIRTY_DIRTY + | TRACKER_BASE_TO_IMAGE_PLATE_DIRTY + | HEAD_TRACKER_TO_IMAGE_PLATE_DIRTY); + + // + // View cache for this screen + // + ScreenViewCache screenViewCache = null; + + // The renderer for this screen + Renderer renderer = null; + + // Hashtable that maps a GraphicsDevice to its associated renderer + static Hashtable deviceRendererMap = new Hashtable(); + + // A count of the number of canvases associated with this screen + int canvasCount = 0; + + // A count of the number of active View associated with this screen + UnorderList activeViews = new UnorderList(1, View.class); + + // A list of Canvas3D Objects that refer to this + ArrayList users = new ArrayList(); + + void addActiveView(View v) { + activeViews.addUnique(v); + } + + void removeActiveView(View v) { + activeViews.remove(v); + } + + boolean activeViewEmpty() { + return activeViews.isEmpty(); + } + + // Add a user to the list of users + synchronized void removeUser(Canvas3D c) { + int idx = users.indexOf(c); + if (idx >= 0) { + users.remove(idx); + } + } + + // Add a user to the list of users + synchronized void addUser(Canvas3D c) { + int idx = users.indexOf(c); + if (idx < 0) { + users.add(c); + } + } + + // Add a user to the list of users + synchronized void notifyUsers() { + int i; + Canvas3D c; + + for (i=0; i<users.size(); i++) { + c = (Canvas3D)users.get(i); + c.redraw(); + } + } + + /** + * Retrieves the width and height (in pixels) of this Screen3D. + * + * @return a new Dimension object containing the width and height + * of this Screen3D. + */ + public Dimension getSize() { + return new Dimension(screenSize); + } + + /** + * Retrieves the width and height (in pixels) of this Screen3D + * and copies it into the specified Dimension object. + * + * @param rv Dimension object into which the size of + * this Screen3D is copied. + * If <code>rv</code> is null, a new Dimension object is allocated. + * + * @return <code>rv</code> + * + * @since Java 3D 1.2 + */ + public Dimension getSize(Dimension rv) { + if (rv == null) { + return new Dimension(screenSize); + } + else { + rv.setSize(screenSize); + return rv; + } + } + + /** + * Sets the width and height (in pixels) of this off-screen Screen3D. + * The default size for off-screen Screen3D objects is (0,0). + * <br> + * NOTE: the size must be + * set prior to rendering to the associated off-screen canvas. + * Failure to do so will result in an exception. + * + * @param width the new width of this Screen3D object + * @param height the new height of this Screen3D object + * + * @exception IllegalStateException if this Screen3D is not in + * off-screen mode. + * + * @since Java 3D 1.2 + */ + public void setSize(int width, int height) { + + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Screen3D1")); + + synchronized(this) { + screenSize.width = width; + screenSize.height = height; + scrDirtyMask |= SCREEN_SIZE_DIRTY_DIRTY; + } + } + + /** + * Sets the width and height (in pixels) of this off-screen Screen3D. + * The default size for off-screen Screen3D objects is (0,0). + * <br> + * NOTE: the size must be + * set prior to rendering to the associated off-screen canvas. + * Failure to do so will result in an exception. + * + * @param d the new dimension of this Screen3D object + * + * @exception IllegalStateException if this Screen3D is not in + * off-screen mode. + * + * @since Java 3D 1.2 + */ + public void setSize(Dimension d) { + if (!offScreen) + throw new IllegalStateException(J3dI18N.getString("Screen3D1")); + + synchronized(this) { + screenSize.width = d.width; + screenSize.height = d.height; + scrDirtyMask |= SCREEN_SIZE_DIRTY_DIRTY; + } + } + + /** + * Sets the screen physical width in meters. In the case of a + * head-mounted display, this should be the apparent width + * at the focal plane. + * @param width the screen's physical width in meters + */ + public void setPhysicalScreenWidth(double width) { + synchronized(this) { + physicalScreenWidth = width; + scrDirtyMask |= PHYSICAL_SCREEN_SIZE_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the screen's physical width in meters. + * @return the screen's physical width in meters + */ + public double getPhysicalScreenWidth() { + return physicalScreenWidth; + } + + /** + * Sets the screen physical height in meters. In the case of a + * head-mounted display, this should be the apparent height + * at the focal plane. + * @param height the screen's physical height in meters + */ + public void setPhysicalScreenHeight(double height) { + synchronized(this) { + physicalScreenHeight = height; + scrDirtyMask |= PHYSICAL_SCREEN_SIZE_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the the screen's physical height in meters. + * @return the screen's physical height in meters + */ + public double getPhysicalScreenHeight() { + return physicalScreenHeight; + } + + public String toString() { + return "Screen3D: size = " + + "(" + getSize().width + " x " + getSize().height + ")" + + ", physical size = " + + "(" + getPhysicalScreenWidth() + "m x " + + getPhysicalScreenHeight() + "m)"; + } + + // Static initializer for Screen3D class + static { + VirtualUniverse.loadLibraries(); + } + + /** + * Construct a new Screen3D object with the specified size in pixels. + * Note that currently, there is no AWT equivalent of screen so Java 3D + * users need to get this through the Canvas3D object (via getScreen()) if + * they need it. + * @param graphicsConfiguration the AWT graphics configuration associated + * with this Screen3D + * @param offScreen a flag that indicates whether this Screen3D is + * associated with an off-screen Canvas3D + */ + Screen3D(GraphicsConfiguration graphicsConfiguration, boolean offScreen) { + NativeScreenInfo nativeScreenInfo; + + this.offScreen = offScreen; + this.graphicsDevice = graphicsConfiguration.getDevice(); + + screenViewCache = new ScreenViewCache(this); + nativeScreenInfo = new NativeScreenInfo(graphicsDevice); + + // Get the display from the native code (X11 only) and the + // screen ID + display = nativeScreenInfo.getDisplay(); + screen = nativeScreenInfo.getScreen(); + + if (debug) + System.out.println("Screen3D: display " + display + + " screen " + screen + " hashcode " + + this.hashCode()); + + if (!offScreen) { + // Store the information in this screen object + Rectangle bounds = graphicsConfiguration.getBounds(); + screenSize.width = bounds.width; + screenSize.height = bounds.height; + } + + // Set the default physical size based on size in pixels + physicalScreenWidth = screenSize.width * METERS_PER_PIXEL; + physicalScreenHeight = screenSize.height * METERS_PER_PIXEL; + } + + + /** + * Sets the tracker-base coordinate system to image-plate coordinate + * system transform. This transform + * is typically a calibration constant. + * This is used only in SCREEN_VIEW mode. + * @param t the new transform + * @exception BadTransformException if the transform is not rigid + */ + public void setTrackerBaseToImagePlate(Transform3D t) { + synchronized(this) { + if (!t.isRigid()) { + throw new BadTransformException(J3dI18N.getString("Screen3D0")); + } + trackerBaseToImagePlate.setWithLock(t); + scrDirtyMask |= Screen3D.TRACKER_BASE_TO_IMAGE_PLATE_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the tracker-base coordinate system to image-plate + * coordinate system transform and copies it into the specified + * Transform3D object. + * @param t the object that will receive the transform + */ + public void getTrackerBaseToImagePlate(Transform3D t) { + t.set(trackerBaseToImagePlate); + } + + /** + * Sets the head-tracker coordinate system to left image-plate coordinate + * system transform. This transform + * is typically a calibration constant. + * This is used only in HMD_VIEW mode. + * @param t the new transform + * @exception BadTransformException if the transform is not rigid + */ + public void setHeadTrackerToLeftImagePlate(Transform3D t) { + synchronized(this) { + if (!t.isRigid()) { + throw new BadTransformException(J3dI18N.getString("Screen3D0")); + } + headTrackerToLeftImagePlate.setWithLock(t); + scrDirtyMask |= Screen3D.HEAD_TRACKER_TO_IMAGE_PLATE_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the head-tracker coordinate system to left image-plate + * coordinate system transform and copies it into the specified + * Transform3D object. + * @param t the object that will receive the transform + */ + public void getHeadTrackerToLeftImagePlate(Transform3D t) { + t.set(headTrackerToLeftImagePlate); + } + + /** + * Sets the head-tracker coordinate system to right image-plate coordinate + * system transform. This transform + * is typically a calibration constant. + * This is used only in HMD_VIEW mode. + * @param t the new transform + * @exception BadTransformException if the transform is not rigid + */ + public void setHeadTrackerToRightImagePlate(Transform3D t) { + synchronized(this) { + if (!t.isRigid()) { + throw new BadTransformException(J3dI18N.getString("Screen3D0")); + } + headTrackerToRightImagePlate.setWithLock(t); + scrDirtyMask |= Screen3D.HEAD_TRACKER_TO_IMAGE_PLATE_DIRTY; + } + notifyUsers(); + } + + /** + * Retrieves the head-tracker coordinate system to right image-plate + * coordinate system transform and copies it into the specified + * Transform3D object. + * @param t the object that will receive the transform + */ + public void getHeadTrackerToRightImagePlate(Transform3D t) { + t.set(headTrackerToRightImagePlate); + } + + /** + * Update the view cache associated with this screen. + */ + void updateViewCache() { + if (false) + System.out.println("Screen3D.updateViewCache()"); + synchronized(this) { + screenViewCache.snapshot(); + } + } + + /** + * Increment canvas count, initialize renderer if needed + */ + synchronized void incCanvasCount() { + canvasCount++; + } + + /** + * Decrement canvas count, kill renderer if needed + */ + synchronized void decCanvasCount() { + canvasCount--; + } + +} diff --git a/src/classes/share/javax/media/j3d/ScreenViewCache.java b/src/classes/share/javax/media/j3d/ScreenViewCache.java new file mode 100644 index 0000000..a87dd3e --- /dev/null +++ b/src/classes/share/javax/media/j3d/ScreenViewCache.java @@ -0,0 +1,114 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.Dimension; +import javax.vecmath.*; + +/** + * The ScreenViewCache class is used to cache all API data + * from the Screen3D object. + */ +class ScreenViewCache extends Object { + // The screen associated with this screen view cache + Screen3D screen; + + // + // API/INPUT DATA + // + + // The width and height of the screen in meters. + double physicalScreenWidth; + double physicalScreenHeight; + + // The width and height of the screen in pixels. + int screenWidth; + int screenHeight; + + // Mask that indicates Screen3D view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + int scrvcDirtyMask = 0; + + // + // Tracker-base coordinate system to image-plate coordinate + // system transform. If head tracking is enabled, this transform + // is a calibration constant. If head tracking is not enabled, + // this transform is not used. + // This is used only in SCREEN_VIEW mode. + // + Transform3D trackerBaseToImagePlate = new Transform3D(); + + // + // Head-tracker coordinate system to left and right image-plate coordinate + // system transforms. If head tracking is enabled, these transforms + // are calibration constants. If head tracking is not enabled, + // these transforms are not used. + // These are used only in HMD_VIEW mode. + // + Transform3D headTrackerToLeftImagePlate = new Transform3D(); + Transform3D headTrackerToRightImagePlate = new Transform3D(); + + + // + // DERIVED DATA + // + + // Meters per pixel in the X and Y dimension + double metersPerPixelX; + double metersPerPixelY; + + + /** + * Take snapshot of all per-screen API parameters. + */ + synchronized void snapshot() { + + // accumulate the dirty bits for offscreen because + // the dirty bits will not be processed until renderOffScreen + // or triggered by RenderBin at some little time + if (screen.offScreen) + scrvcDirtyMask |= screen.scrDirtyMask; + else + scrvcDirtyMask = screen.scrDirtyMask; + + screen.scrDirtyMask = 0; + physicalScreenWidth = screen.physicalScreenWidth; + physicalScreenHeight = screen.physicalScreenHeight; + screenWidth = screen.screenSize.width; + screenHeight = screen.screenSize.height; + + screen.trackerBaseToImagePlate.getWithLock(trackerBaseToImagePlate); + + screen.headTrackerToLeftImagePlate.getWithLock + (headTrackerToLeftImagePlate); + screen.headTrackerToRightImagePlate.getWithLock + (headTrackerToRightImagePlate); + + // This isn't really API data, but since we have no other derived + // data, and it's a simple calculation, it's easier if we just do + // it here. + metersPerPixelX = physicalScreenWidth / (double) screenWidth; + metersPerPixelY = physicalScreenHeight / (double) screenHeight; + } + + + /** + * Constructs and initializes a ScreenViewCache object. + */ + ScreenViewCache(Screen3D screen) { + this.screen = screen; + + if (false) + System.out.println("Constructed a ScreenViewCache"); + } +} diff --git a/src/classes/share/javax/media/j3d/Sensor.java b/src/classes/share/javax/media/j3d/Sensor.java new file mode 100644 index 0000000..2f02485 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Sensor.java @@ -0,0 +1,697 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * The Sensor Class encapsulates an object that provides real-time + * data. Examples include six-degree-of-freedom tracking, a joystick, + * or a data file being read back during a program. A sensor must be + * used in conjuction with an implementation of the InputDevice + * interface.<P> + * + * The Sensor object provides an abstract concept of a hardware + * input device. A Sensor consists of a timestamped sequence of + * input values and the state of buttons or switches at the time + * that Java 3D sampled the value. A sensor also contains a hotspot + * offset specified in the sensor's local coordinate system. If not + * specified, the hotspot is (0.0, 0.0, 0.0).<P> + * + * Since a typical hardware environment may contain multiple sensing + * elements, Java 3D maintains an array of sensors. Users can access + * a sensor directly from their Java code or they can assign a sensor + * to one of Java 3D's predefined 6DOF entities, such as UserHead.<P> + * + * Using a sensor is as easy as accessing an object. Write your + * Java code to extract the associated sensor value from the array of + * sensors. You can then directly apply that value to an element in a + * scene graph or process the sensor values in whatever way necessary.<P> + * + * Java 3D includes three special six-degrees-of-freedom (6DOF) entities. + * These include UserHead, DominantHand, and NondominantHand. You + * can assign or change which sensor drives one + * of these predefined entities. Java 3D uses the specified sensor to + * drive the 6DOF entity - most visibly the View.<P> + * + * Java 3D does not provide raw tracker or joystick-generated data in + * a sensor. At a minimum, Java 3D normalizes the raw data using the + * registration and calibration parameters either provided by or + * provided for the end user. It additionally may filter and process + * the data to remove noise and improve latency. + * The application programmer can suppress this latter effect on a + * sensor-by-sensor basis.<P> + * + * @see SensorRead + */ + +public class Sensor { + + /** + * Set predictor type to do no prediction; this is the default. + */ + public static final int PREDICT_NONE = 1; + + /** + * Set predictor type to generate the SensorRead to correspond with + * the next frame time. + */ + public static final int PREDICT_NEXT_FRAME_TIME = 2; + + /** + * Use no prediction policy; this is the default. + */ + public static final int NO_PREDICTOR = 16; + + /** + * Set the predictor policy to assume the sensor is predicting head + * position/orientation. + */ + public static final int HEAD_PREDICTOR = 32; + + /** + * Set the predictor policy to assume the sensor is predicting hand + * position/orientation. + */ + public static final int HAND_PREDICTOR = 64; + + /** + * Default SensorRead object count (30); the number of SensorRead + * objects constructed if no count is specified. + */ + public static final int DEFAULT_SENSOR_READ_COUNT = 30; + + /** + * SENSOR_READ_COUNT_BUFFER is the number of extra sensor reading + * values to store at the end of the circular list. It helps provide + * MT-safeness. This is necessary if someone asks for the last + * k sensor values and k is close to sensor read count. + * This helps avoid some synchronization statements in getRead + * and setNextSensorRead. + */ + static final int SENSOR_READ_COUNT_BUFFER = 15; + + static int num_reads_so_far = 0; + + // specifies whether a DEMAND_DRIVEN device has been added that + // manages this sensor + boolean demand_driven = false; + + // size of the sensor read buffer + int sensorReadCount; + + // Default prediction policy: don't predict + int predictionPolicy = NO_PREDICTOR; + + // Default Predictor none + int predictorType = PREDICT_NONE; + + // This sensor's associated device + InputDevice device; + + SensorRead readings[]; + int currentIndex; + int lastIndex; + Point3d hotspot; + int MaxSensorReadIndex; + + // The count of the number of buttons associated with this sensor. + int sensorButtonCount; + + // These matrices used as a temporary workspace for the local SVD + // calculations (thus minimimizing garbage collection). + Matrix3d orig_rot = new Matrix3d(); + Matrix3d orig_rot_transpose = new Matrix3d(); + Matrix3d temp_rot = new Matrix3d(); + Matrix3d local_svd = new Matrix3d(); + + // Prediction workspace -- these may go away when the readings array + // is used. + static int MAX_PREDICTION_LENGTH = 20; + Transform3D[] previousReads = new Transform3D[MAX_PREDICTION_LENGTH]; + long[] times = new long[MAX_PREDICTION_LENGTH]; + + + /** + * Constructs a Sensor object for the specified input device using + * default parameters. The default values are as follows: + * <ul> + * sensor read count : 30<br> + * sensor button count : 0<br> + * hot spot : (0,0,0)<br> + * predictor : PREDICT_NONE<br> + * prediction policy : NO_PREDICTOR<br> + * </ul> + * @param device the Sensor's associated device. + */ + public Sensor(InputDevice device){ + this(device, DEFAULT_SENSOR_READ_COUNT, 0, new Point3d(0.0, 0.0, 0.0)); + } + + /** + * Constructs a Sensor object for the specified input device using + * the specified number of SensorRead objects. + * Default values are used for all other parameters. + * @param device the Sensor's associated device + * @param sensorReadCount the number of SensorReads to associate with + * this sensor + */ + public Sensor(InputDevice device, int sensorReadCount){ + this(device, sensorReadCount, 0, new Point3d(0.0, 0.0, 0.0)); + } + + /** + * Constructs a Sensor object for the specified input device using + * the specified number of SensorRead objects and number of buttons. + * Default values are used for all other parameters. + * @param device the Sensor's associated device + * @param sensorReadCount the number of SensorReads to associate with + * this sensor + * @param sensorButtonCount the number of buttons associated with each + * sensor read + */ + public Sensor(InputDevice device, int sensorReadCount, + int sensorButtonCount){ + this(device, sensorReadCount, sensorButtonCount, + new Point3d(0.0,0.0, 0.0)); + } + + /** + * Constructs a Sensor object for the specified input device using + * the specified hotspot. + * Default values are used for all other parameters. + * @param device the Sensor's associated device + * @param hotspot the Sensor's hotspot defined in its local coordinate + * system + */ + public Sensor(InputDevice device, Point3d hotspot){ + this(device, DEFAULT_SENSOR_READ_COUNT, 0, hotspot); + } + + /** + * Constructs a Sensor object for the specified input device using + * the specified number of SensorRead objects and hotspot. + * Default values are used for all other parameters. + * @param device the Sensor's associated device + * @param sensorReadCount the number of SensorReads to associate with + * this sensor + * @param hotspot the Sensor's hotspot defined in its local coordinate + * system + */ + public Sensor(InputDevice device, int sensorReadCount, Point3d hotspot){ + this(device, sensorReadCount, 0, hotspot); + } + + /** + * Constructs a Sensor object for the specified input device using + * the specified number of SensorRead objects, number of buttons, and + * hotspot. + * Default values are used for all other parameters. + * @param device the Sensor's associated device + * @param sensorReadCount the number of SensorReads to associate with + * this sensor + * @param sensorButtonCount the number of buttons associated with each + * sensor read + * @param hotspot the Sensor's hotspot defined in its local coordinate + * system + */ + public Sensor(InputDevice device, int sensorReadCount, + int sensorButtonCount, Point3d hotspot){ + this.device = device; + this.sensorReadCount = sensorReadCount; + this.MaxSensorReadIndex = sensorReadCount + SENSOR_READ_COUNT_BUFFER - 1; + this.sensorButtonCount = sensorButtonCount; + readings = new SensorRead[MaxSensorReadIndex + 1]; + for(int i = 0; i < MaxSensorReadIndex + 1; i++){ + readings[i] = new SensorRead(sensorButtonCount); + } + currentIndex = 0; + this.hotspot = new Point3d(hotspot); + + // prediction initialization + for(int i=0 ; i<MAX_PREDICTION_LENGTH ; i++) { + previousReads[i] = new Transform3D(); + } + } + + // argument of 0 is last reading (ie, currentIndex), argument + // of 1 means next to last index, etc. + int previousIndex(int k){ + int temp = currentIndex - k; + return(temp >= 0 ? temp : MaxSensorReadIndex + temp + 1); + } + + /** + * This function sets the type of predictor to use with this sensor. + * @param predictor predictor type one of PREDICT_NONE or + * PREDICT_NEXT_FRAME_TIME + * @exception IllegalArgumentException if an invalid predictor type + * is specified. + */ + public void setPredictor(int predictor){ + if (predictor != PREDICT_NONE && predictor != PREDICT_NEXT_FRAME_TIME) { + throw new IllegalArgumentException(J3dI18N.getString("Sensor0")); + } else { + predictorType = predictor; + } + } + + /** + * This function returns the type of predictor used by this sensor. + * @return returns the predictor type. One of PREDICT_NONE or + * PREDICT_NEXT_FRAME_TIME. + */ + public int getPredictor(){ + return predictorType; + } + + /** + * This function sets the prediction policy use by this sensor. + * @param policy prediction policy one of NO_PREDICTOR, HEAD_PREDICTOR, + * or HAND_PREDICTOR + * @exception IllegalArgumentException if an invalid prediction policy + * is specified. + */ + public void setPredictionPolicy(int policy){ + if (policy != NO_PREDICTOR && policy != HEAD_PREDICTOR && + policy != HAND_PREDICTOR) + throw new IllegalArgumentException(J3dI18N.getString("Sensor1")); + else + predictionPolicy = policy; + } + + /** + * This function returns the prediction policy used by this sensor. + * @return returns the prediction policy. one of NO_PREDICTOR, + * HEAD_PREDICTOR, or HAND_PREDICTOR. + */ + public int getPredictionPolicy(){ + return predictionPolicy; + } + + /** + * Set the sensor's hotspot in this sensor's coordinate system. + * @param hotspot the sensor's new hotspot + */ + public void setHotspot(Point3d hotspot){ + this.hotspot.set(hotspot); + } + + /** + * Get the sensor's hotspot in this sensor's coordinate system. + * @param hotspot the variable to receive the sensor's hotspot + */ + public void getHotspot(Point3d hotspot){ + hotspot.set(this.hotspot); + } + + /** + * Set the sensor's associated input device. + * @param device the sensor's new device + */ + public void setDevice(InputDevice device){ + this.device = device; + } + + /** + * Retrieves the sensor's associated input device. + * @return the sensor's device + */ + public InputDevice getDevice(){ + return device; + } + + /** + * Computes the sensor reading consistent with the prediction policy + * and copies that value into the specified argument; calling this method + * with a prediction policy of NO_PREDICTOR will return the last sensor + * reading; calling this method with a prediction policy of HAND_PREDICTOR, + * or HEAD_PREDICTOR will extrapolate previous sensor readings to the + * current time. + * @param read The matrix that will receive the predicted sensor reading + */ + public void getRead(Transform3D read){ + long time; + + if(demand_driven == true) + device.pollAndProcessInput(); + + time = System.currentTimeMillis(); + + // before using prediction, fill in some values + if(num_reads_so_far < 40*SENSOR_READ_COUNT_BUFFER) { + num_reads_so_far++; + read.set(readings[currentIndex].read); + return; + } + + switch(predictionPolicy) { + case NO_PREDICTOR: + read.set(readings[currentIndex].read); + break; + case HAND_PREDICTOR: + read.set(readings[currentIndex].read); + //getPredictedRead(read, time, 3, 2); + break; + case HEAD_PREDICTOR: + read.set(readings[currentIndex].read); + //getPredictedRead(read, time, 3, 2); + break; + } + } + + /** + * Computes the sensor reading consistent as of time deltaT in the future + * and copies that value into the specified argument; the reading is + * computed using the current prediction policy; a prediction policy of + * NO_PREDICTOR will yield the most recent sensor reading for any + * deltaT argument (i.e., this method is the same as getRead for a prediction + * policy of NO_PREDICTOR). The time argument must be >= 0. + * @param read the matrix that will receive the predicted sensor reading + * @param deltaT the time delta into the future for this read + */ + public void getRead(Transform3D read, long deltaT){ + long current_time; + + if(deltaT < 0L) { + throw new IllegalArgumentException(J3dI18N.getString("Sensor2")); + } + + if(demand_driven == true) + device.pollAndProcessInput(); + + current_time = System.currentTimeMillis(); + + switch(predictionPolicy) { + case NO_PREDICTOR: + read.set(readings[currentIndex].read); + break; + case HAND_PREDICTOR: + read.set(readings[currentIndex].read); + //getPredictedRead(read, current_time + deltaT, 3, 2); + break; + case HEAD_PREDICTOR: + read.set(readings[currentIndex].read); + //getPredictedRead(read, current_time + deltaT, 3, 2); + break; + } + } + + /** + * Extracts the most recent sensor reading and copies that value into + * the specified argument. + * @param read the matrix that will receive the most recent sensor reading + */ + public void lastRead(Transform3D read){ + read.set(readings[currentIndex].read); + } + + /** + * Extracts the kth-most recent sensor reading and copies that value into + * the specified argument; where 0 is the most recent sensor reading, 1 is + * the next most recent sensor reading, etc. + * @param read the matrix that will receive the most recent sensor reading + * @param kth the kth previous sensor reading + */ + public void lastRead(Transform3D read, int kth){ + if(kth >= sensorReadCount) { + throw new IllegalArgumentException(J3dI18N.getString("Sensor3")); + } + read.set(readings[previousIndex(kth)].read); + } + + /** + * Returns the time associated with the most recent sensor reading. + * @return the time associated with the most recent sensor reading. + */ + public long lastTime(){ + return readings[currentIndex].time; + } + + /** + * Returns the time associated with the kth-most recent sensor reading; + * where 0 is the most recent sensor reading, 1 is the next most recent + * sensor reading, etc. + * @return the time associated with the kth-most recent sensor reading. + */ + public long lastTime(int k){ + if(k >= sensorReadCount) { + throw new IllegalArgumentException(J3dI18N.getString("Sensor4")); + } + return readings[previousIndex(k)].time; + } + + /** + * Places the most recent sensor reading value for each button into + * the array parameter; will throw an ArrayIndexOutOfBoundsException + * if values.length is less than the number of buttons. + * @param values the array into which the button values will be + * placed + */ + public void lastButtons(int[] values) { + System.arraycopy(readings[currentIndex].buttonValues, 0, values, + 0, sensorButtonCount); + } + + /** + * Places the kth-most recent sensor reading value for each button into + * the array parameter; where k=0 is the most recent sensor reading, k=1 + * is the next most recent sensor reading, etc.; will throw an + * ArrayIndexOutOfBoundsException if values.length is less than + * the number of buttons. + * @param k the time associated with the most recent sensor reading + * @param values the array into which the button values will be + * placed. + */ + public void lastButtons(int k, int[] values) { + if(k >= sensorReadCount) { + throw new IllegalArgumentException(J3dI18N.getString("Sensor5")); + } + System.arraycopy(readings[previousIndex(k)].buttonValues, 0, values, + 0, sensorButtonCount); + } + + /** + * Returns the number of SensorRead objects associated with + * this sensor. + * @return the number of SensorReadObjects associated with this sensor + */ + public int getSensorReadCount() { + return this.sensorReadCount; + } + + /** + * Set the number of sensor read objects per Sensor. This is a + * calibration parameter that should normally be set in this + * object's constructor. Calling this method resets all of this + * sensor's values that are already in the buffer. + * It is illegal to change this value after the device has been + * added to the scheduler. + * @param count the new sensor read count + */ + public void setSensorReadCount(int count) { + sensorReadCount = count; + MaxSensorReadIndex = sensorReadCount + SENSOR_READ_COUNT_BUFFER - 1; + readings = new SensorRead[MaxSensorReadIndex + 1]; + for(int i = 0; i < MaxSensorReadIndex + 1; i++){ + readings[i] = new SensorRead(sensorButtonCount); + } + currentIndex = 0; + } + + + /** + * Returns the number of buttons associated with this sensor. + * @return the number of buttons associated with this sensor. + */ + public int getSensorButtonCount() { + return sensorButtonCount; + } + + /** + * Gets the current sensor read. + * @return the current sensor read object + */ + public SensorRead getCurrentSensorRead() { + // not sure if this should return a reference or a copy + SensorRead read = new SensorRead(sensorButtonCount); + read.set(readings[currentIndex]); + return read; + } + + /** + * Sets the next sensor read to the specified values; once these + * values are set via this method they become the current values + * returned by methods such as lastRead(), lastTime(), and + * lastButtons(); note that if there are no buttons associated with + * this sensor, values can just be an empty array. + * @param time the next SensorRead's associated time + * @param transform the next SensorRead's transformation + * @param values the next SensorRead's buttons' states + */ + public void setNextSensorRead(long time, Transform3D transform, + int[] values) { + + int temp = currentIndex + 1; + if (temp > MaxSensorReadIndex) temp = 0; + + readings[temp].setTime(time); + readings[temp].set(transform); + if(sensorButtonCount > 0) + readings[temp].setButtons(values); + currentIndex = temp; + } + + /** + * Sets the next sensor read to the specified values; once these + * values are set via this method they become the current values + * returned by methods such as lastRead(), lastTime(), and + * lastButtons(). + * @param read the next SensorRead's values + */ + public void setNextSensorRead(SensorRead read) { + int temp = currentIndex + 1; + if (temp > MaxSensorReadIndex) temp = 0; + readings[temp].set(read); + currentIndex = temp; + } + + /** + * This routine does an nth order fit of the last num_readings, which + * can be plotted on a graph of time vs. sensor reading. There is a + * separate fit done for each of the 16 matrix elements, then an SVD + * done on the final matrix. The curve that is produced takes into + * account non-constant times between each sample (it is fully general). + * This curve can then be used to produce a prediction for any + * time in the future by simply inserting a time value and using the + * solution matrix. + */ + void getPredictedRead(Transform3D transform, long time, int num_readings, + int order) { + + int index = currentIndex; // lock in current_index for MT-safety + long time_basis = readings[index].time; + long tempTime; + + time -= time_basis; + + GMatrix A = new GMatrix(num_readings, order+1); + + for(int i=0 ; i<num_readings ; i++) { + A.setElement(i, 0, 1.0); + tempTime = lastTimeRelative(num_readings-i-1, index, time_basis); + A.setElement(i, 1, (double)tempTime); + for(int j=2; j<=order ; j++) { + // powerAndDiv(time, n) = times^n/n + A.setElement(i, j, powerAndDiv(tempTime, j)); + } + } + + GMatrix A_Transpose = new GMatrix(A); + A_Transpose.transpose(); + GMatrix M = new GMatrix(order+1, order+1); + M.mul(A_Transpose, A); + try { + M.invert(); + } catch (SingularMatrixException e) { + System.out.println("SINGULAR MATRIX EXCEPTION in prediction"); + System.out.println(M); + } + + // TODO: can be class scope + double[] transformArray = new double[16]; + GMatrix solMatrix = new GMatrix(order+1, num_readings); + solMatrix.mul(M,A_Transpose); + + GVector P = new GVector(order+1); + + // fill in the time for which we are trying to predict a sensor value + GVector predTimeVec = new GVector(order+1); + predTimeVec.setElement(0, 1); + predTimeVec.setElement(1, time); + for(int i=2 ; i<=order ; i++) { + predTimeVec.setElement(i, powerAndDiv(time, i)); + } + + GVector R = new GVector(num_readings); + + for(int transElement=0 ; transElement<16 ; transElement++) { + + for(int i=0 ; i<num_readings ; i++) { + R.setElement(i, lastReadRelative(num_readings-i-1, index, + transElement)); + } + + P.mul(solMatrix,R); + transformArray[transElement] = P.dot(predTimeVec); + } + + //Matrix4d temp = new Matrix4d(transformArray); + //localSVD(temp); + //transform.set(temp); + transform.set(transformArray); + transform.normalize(); + } + + /** + * Extracts the kth most recent sensor reading and copies that value into + * the specified argument; where 0 is the most recent sensor reading, 1 is + * the next most recent sensor reading, etc. + * @param read The matrix that will receive the most recent sensor reading + * @param k The kth previous sensor reading + */ + double lastReadRelative(int kth, int base_index, int mat_element){ + // kth should be < sensorReadCount + return + readings[previousIndexRelative(kth, base_index)].read.mat[mat_element]; + } + + /** + * Returns the time associated with the kth most recent sensor reading; + * where 0 is the most recent sensor reading, 1 is the next most recent + * sensor reading, etc. However, unlike the public method, returns + * the kth reading relative to the index given, instead of the + * current_index and returns the time relative to timeBasis. + * @return the time associated with the kthmost recent sensor reading. + */ + long lastTimeRelative(int k, int base_index, long timeBasis){ + // kth should be < sensorReadCount + long time; + time = timeBasis - readings[previousIndexRelative(k, base_index)].time; + return time; + } + + // argument of 0 is last reading, argument of 1 means next to last + // index, etc. , but all of these are relative to *base_index* + int previousIndexRelative(int k, int base_index){ + int temp = base_index - k; + return(temp >= 0 ? temp : MaxSensorReadIndex + temp + 1); + } + + // this method returns (value^order)/order + double powerAndDiv(double value, int order) { + + if(order == 0) + return 1; + else if(order == 1) + return value; + + double total = 1.0; + for(int i=0 ; i< order ; i++) + total *= value; + + total = total / (double)order; + return total; + } + +} diff --git a/src/classes/share/javax/media/j3d/SensorRead.java b/src/classes/share/javax/media/j3d/SensorRead.java new file mode 100644 index 0000000..5e0e48b --- /dev/null +++ b/src/classes/share/javax/media/j3d/SensorRead.java @@ -0,0 +1,167 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * A SensorRead encapsulates all the information associated with a single + * reading of a sensor, including a timestamp, a transform, and, + * optionally, button values. + */ + +public class SensorRead { + + /** + * The maximum number of sensor-attached buttons tracked on a per + * sensor basis. + */ + public static final int MAXIMUM_SENSOR_BUTTON_COUNT = 12; + + /** + * This reading's time stamp + */ + long time; + + /** + * The six-degree-of-freedom reading + */ + Transform3D read; + + /** + * The state of the sensor's buttons + */ + int[] buttonValues; + + /** + * The number of buttons associated with this SensorRead + */ + int numButtons; + + /** + * Constructs a SensorRead object with default parameters. + * The default values are as follows: + * <ul> + * number of buttons : 0<br> + * button values : 0 (for all array elements)<br> + * transform : identity<br> + * time : current time<br> + * </ul> + */ + public SensorRead(){ + this(0); + } + + /** + * Constructs a SensorRead object with the specified number + * of buttons. + * @param numButtons the number of buttons for this SensorRead + */ + public SensorRead(int numButtons){ + this.read = new Transform3D(); + this.numButtons = numButtons; + this.buttonValues = new int[numButtons]; + + // Do this last + this.time = System.currentTimeMillis(); + } + + final void set(SensorRead sensorRead) { + this.time = sensorRead.time; + this.numButtons = sensorRead.numButtons; + this.read.set(sensorRead.read); + if(numButtons > 0) + System.arraycopy(sensorRead.buttonValues, 0, this.buttonValues, + 0, sensorRead.numButtons); + } + + /** + * Set the SensorRead's transform to the value specified + * @param t1 this sensor's reading + */ + public void set(Transform3D t1) { + read.set(t1); + } + + /** + * Retrieve the SensorRead's transform and place it in result + * @param result the recipient of the this sensor's reading + */ + public void get(Transform3D result) { + result.set(read); + } + + /** + * Sets this SensorRead's time stamp to the specified argument + * @param time the time to associate with this reading + */ + public void setTime(long time) { + this.time = time; + } + + /** + * Retrieve this SensorRead's associated time stamp + * @return the SensorRead's time as a long + */ + public long getTime() { + return this.time; + } + + /** + * Sets the values of all buttons for this SensorRead object. + * @param values array contining the new buttons for this SensorRead + * @exception ArrayIndexOutOfBoundsException if this object + * has 0 buttons or if values.length is less than the number of + * buttons in this object. + */ + public void setButtons(int[] values) { + if(numButtons == 0) + + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("SensorRead1")); + + else if(values.length < numButtons) + + throw new ArrayIndexOutOfBoundsException(J3dI18N.getString("SensorRead0")); + System.arraycopy(values, 0, buttonValues, 0, numButtons); + } + + + /** + * Copies the array of button values for this SensorRead object into + * the specified array. + * This method has no effect + * if this SensorRead object has 0 buttons. The array must be + * large enough to hold all of the buttons. + * @param values array that will receive the values of all buttons + * for this SensorRead + */ + public void getButtons(int[] values) { + if(numButtons > 0) + System.arraycopy(buttonValues, 0, values, 0, numButtons); + } + + + /** + * Returns the number of buttons associated with this SensorRead + * object. + * + * @return the number of buttons associated with this SensorRead + * object + * + * @since Java 3D 1.2 + */ + public int getNumButtons() { + return numButtons; + } + +} diff --git a/src/classes/share/javax/media/j3d/SetLiveState.java b/src/classes/share/javax/media/j3d/SetLiveState.java new file mode 100644 index 0000000..a9a7df0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SetLiveState.java @@ -0,0 +1,252 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * SetLiveState is used to encapsulate all state needed when a branch + * group is added to the scene graph + */ + +class SetLiveState extends Object { + // The VirtualUniverse for this branch group + VirtualUniverse universe = null; + + // The Locale for this Branch Graph + Locale locale = null; + + // The transforms used to update state + Transform3D[][] currentTransforms = new Transform3D[1][]; + int[][] currentTransformsIndex = new int[1][]; + + // The keys used when dealing with SharedGroups + HashKey[] keys = null; + + // flags for detecting what we are under + boolean inSharedGroup = false; + boolean inBackgroundGroup = false; + boolean inViewSpecificGroup = false; + + /** + * The list of nodes added/removed during setLive/clearLive + */ + ArrayList nodeList = new ArrayList(); + + /** + * List of nodes that are viewScoped. Note that all nodes + * except Shape3D nodes can be in viewScopedNodeList, shape3D + * nodes will always be in the nodeList regardless of scoped + * or not. Also, only renderbin and renderingEnv structure is + * interested in viewScopedNodeList + */ + ArrayList viewScopedNodeList = null; + + /** + * Parallel list to viewScopedNodeList containing a list of views + * that the viewScopedNode is scoped to + */ + ArrayList scopedNodesViewList = null; + + // Threads to notify after setLive/clearLive + int notifyThreads = 0; + + // The current list of leaf nodes for transform targets + Targets[] transformTargets = null; + + // List of transform level, one per shared path + int transformLevels[] = new int[]{-1}; + + // List of scoped lights + ArrayList lights = null; + + // List of scoped fogs + ArrayList fogs =null; + + // List of scoped modelClips + ArrayList modelClips = null; + + // List of scoped alt app + ArrayList altAppearances =null; + + // List of viewes scoped to this Group, for all subclasses + // of group, except ViewSpecificGroup its a pointer to closest + // ViewSpecificGroup parent + // viewList for this node, if inSharedGroup is + // false then only viewList(0) is valid + ArrayList viewLists = null; + ArrayList changedViewGroup = null; + ArrayList changedViewList = null; + int[] keyList = null; + + + // The current bitmask of types in transformTragets + //int transformTargetThreads = 0; + + ArrayList orderedPaths = null; + + ArrayList ogList = new ArrayList(5); + ArrayList ogChildIdList = new ArrayList(5); + ArrayList ogOrderedIdList = new ArrayList(5); + // ogCIOList contains a list of OG with affected child index order. + ArrayList ogCIOList = new ArrayList(5); + // ogCIOTableList contains a list of affected child index order. + ArrayList ogCIOTableList = new ArrayList(5); + + /** + * List of BranchGroup from this node to the root of tree + * This is used by BranchGroupRetained to construct + * BranchGroup lists for picking. + * + * @see NodeRetained.branchGroupPaths + */ + ArrayList branchGroupPaths = null; + ArrayList parentBranchGroupPaths = null; + + /** + * List of Pickable flags, one for each share path. + * This flag is true when all the NodeRetained.pickable is true + * along the path except current node. + */ + boolean pickable[] = new boolean[]{true}; + + /** + * List of collidable flags, one for each share path. + * This flag is true when all the NodeRetained.pickable is true + * along the path except current node. + */ + boolean collidable[] = new boolean[]{true}; + + // reference count use in set/clear Live to remember how + // many references of the original branch that attach()/detach() + int refCount = 1; + + // background node whose geometry branch contains this node + BackgroundRetained geometryBackground = null; + + // behavior nodes + ArrayList behaviorNodes = new ArrayList(1); + + // The current list of child transform group nodes or link nodes + // under a transform group + ArrayList childTransformLinks = null; + + // closest parent which is a TransformGroupRetained or sharedGroupRetained + GroupRetained parentTransformLink = null; + + // switch Level, start from -1, increment by one for each SwitchNode + // encounter in a branch, one per key + int switchLevels[] = new int[]{-1}; + + // closest switch parent, one per key + SwitchRetained closestSwitchParents[] = new SwitchRetained[]{null}; + + // the child id from the closest switch parent, one per key + int closestSwitchIndices[] = new int[]{-1}; + + // The current list of leaf nodes for switch targets + Targets[] switchTargets = null; + + // The current list of closest child switch nodes or + // link nodes under a switch node + ArrayList childSwitchLinks = null; + + // closest parent which is a SwitchRetained or sharedGroupRetained + GroupRetained parentSwitchLink = null; + + SharedGroupRetained lastSharedGroup = null; + + int traverseFlags = 0; + + // Use for set live. + Transform3D[][] localToVworld = null; + int[][] localToVworldIndex = null; + HashKey[] localToVworldKeys = null; + + // cached hashkey index to eliminate duplicate hash key index search + // currently used by Switch, can be extended for other node types + int[] hashkeyIndex = null; + + ArrayList switchStates = null; + + SetLiveState(VirtualUniverse u) { + universe = u; + } + + + void reset(Locale l) { + locale = l; + clear(); + } + + void clear() { + inSharedGroup = false; + inBackgroundGroup = false; + inViewSpecificGroup = false; + nodeList.clear(); + viewScopedNodeList = null; + scopedNodesViewList = null; + + notifyThreads = 0; + transformTargets = null; + lights = null; + fogs = null; + modelClips = null; + altAppearances = null; + viewLists = null; + changedViewGroup = null; + changedViewList = null; + keyList = null; + + behaviorNodes.clear(); + traverseFlags = 0; + + ogList.clear(); + ogChildIdList.clear(); + ogOrderedIdList.clear(); + ogCIOList.clear(); + ogCIOTableList.clear(); + + pickable = new boolean[]{true}; + collidable = new boolean[]{true}; + refCount = 1; + geometryBackground = null; + transformLevels = new int[]{-1}; + childTransformLinks = null; + parentTransformLink = null; + + switchTargets = null; + switchLevels = new int[]{-1}; + switchStates = null; + closestSwitchIndices = new int[]{-1}; + closestSwitchParents = new SwitchRetained[]{null}; + childSwitchLinks = null; + parentSwitchLink = null; + + lastSharedGroup = null; + + keys = null; + currentTransforms = new Transform3D[1][]; + currentTransformsIndex = new int[1][]; + + localToVworld = null; + localToVworldIndex = null; + localToVworldKeys = null; + + // TODO: optimization for targetThreads computation, require + // cleanup in GroupRetained.doSetLive() + //transformTargetThreads = 0; + + hashkeyIndex = null; + } +} diff --git a/src/classes/share/javax/media/j3d/Shape3D.java b/src/classes/share/javax/media/j3d/Shape3D.java new file mode 100644 index 0000000..91d32e1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Shape3D.java @@ -0,0 +1,759 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; + + +/** + * The Shape3D leaf node specifies all geometric objects. It contains + * a list of one or more Geometry component objects and a single + * Appearance component object. The geometry objects define the shape + * node's geometric data. The appearance object specifies that + * object's appearance attributes, including color, material, texture, + * and so on. + * <p> + * The list of geometry objects must all be of the same equivalence + * class, that is, the same basic type of primitive. For subclasses + * of GeometryArray, all point objects are equivalent, all line + * objects are equivalent, and all polygon objects are equivalent. + * For other subclasses of Geometry, only objects of the same + * subclass are equivalent. The equivalence classes are as follows: + * <ul> + * <li>GeometryArray (point): [Indexed]PointArray</li> + * <li>GeometryArray (line): [Indexed]{LineArray, LineStripArray}</li> + * <li>GeometryArray (polygon): [Indexed]{TriangleArray, TriangleStripArray, + * TriangleFanArray, QuadArray}</li> + * <li>CompressedGeometry</li> + * <li>Raster</li> + * <li>Text3D</li> + * </ul> + * <p> + * When Shape3D is used with multiple geometry components, Java 3D may + * choose to use individual geometry bounds instead of the shape's + * bounds for region of influence operations, such as lighting. + * For example, the individual characters of a Text3D shape object + * may be rendered with a different light set. + */ + +public class Shape3D extends Leaf { + + /** + * Id used in the compile optimization to determine + * how to get to the geometry in the case of read + * or picking .. + */ + int id; + + /** + * Specifies that the node allows read access to its geometry information. + */ + public static final int + ALLOW_GEOMETRY_READ = CapabilityBits.SHAPE3D_ALLOW_GEOMETRY_READ; + + /** + * Specifies that the node allows write access to its geometry information. + */ + public static final int + ALLOW_GEOMETRY_WRITE = CapabilityBits.SHAPE3D_ALLOW_GEOMETRY_WRITE; + + /** + * Specifies that the node allows read access to its appearance + * information. + */ + public static final int + ALLOW_APPEARANCE_READ = CapabilityBits.SHAPE3D_ALLOW_APPEARANCE_READ; + + /** + * Specifies that the node allows write access to its appearance + * information. + */ + public static final int + ALLOW_APPEARANCE_WRITE = CapabilityBits.SHAPE3D_ALLOW_APPEARANCE_WRITE; + + /** + * Specifies that the node allows reading its collision Bounds + */ + public static final int + ALLOW_COLLISION_BOUNDS_READ = CapabilityBits.SHAPE3D_ALLOW_COLLISION_BOUNDS_READ; + + /** + * Specifies the node allows writing its collision Bounds + */ + public static final int + ALLOW_COLLISION_BOUNDS_WRITE = CapabilityBits.SHAPE3D_ALLOW_COLLISION_BOUNDS_WRITE; + + /** + * Specifies that this node allows reading its appearance override + * enable flag. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_APPEARANCE_OVERRIDE_READ = + CapabilityBits.SHAPE3D_ALLOW_APPEARANCE_OVERRIDE_READ; + + /** + * Specifies that this node allows writing its appearance override + * enable flag. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_APPEARANCE_OVERRIDE_WRITE = + CapabilityBits.SHAPE3D_ALLOW_APPEARANCE_OVERRIDE_WRITE; + + /** + * Constructs a Shape3D node with default parameters. The default + * values are as follows: + * <ul> + * appearance : null<br> + * geometry : { null }<br> + * collision bounds : null<br> + * appearance override enable : false<br> + * </ul> + * The list of geometry components is initialized with a null + * geometry component as the single element with an index of 0. + * A null geometry component specifies + * that no geometry is drawn. A null appearance component specifies + * that default values are used for all appearance attributes. + */ + public Shape3D() { + } + + /** + * Constructs and initializes a Shape3D node with the specified + * geometry component and a null appearance component. + * The list of geometry components is initialized with the + * specified geometry component as the single element with an + * index of 0. + * A null appearance component specifies that default values are + * used for all appearance attributes. + * @param geometry the geometry component with which to initialize + * this shape node. + */ + public Shape3D(Geometry geometry) { + ((Shape3DRetained)retained).setGeometry(geometry, 0); + } + + /** + * Constructs and initializes a Shape3D node with the specified + * geometry and appearance components. + * The list of geometry components is initialized with the + * specified geometry component as the single element with an + * index of 0. + * @param geometry the geometry component with which to initialize + * this shape node + * @param appearance the appearance component of the shape node + */ + public Shape3D(Geometry geometry, Appearance appearance) { + ((Shape3DRetained)retained).setGeometry(geometry, 0); + ((Shape3DRetained)retained).setAppearance(appearance); + } + + /** + * Creates the retained mode Shape3DRetained object that this + * Shape3D object will point to. + */ + void createRetained() { + retained = new Shape3DRetained(); + retained.setSource(this); + } + + /** + * Sets the collision bounds of a node. + * @param bounds the collision bounding object for a node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setCollisionBounds(Bounds bounds) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLLISION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D0")); + + ((Shape3DRetained)this.retained).setCollisionBounds(bounds); + } + + /** + * Returns the collision bounding object of this node. + * @return the node's collision bounding object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getCollisionBounds() { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLLISION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D1")); + + return ((Shape3DRetained)this.retained).getCollisionBounds(id); + } + + + /** + * Replaces the geometry component at index 0 in this Shape3D node's + * list of geometry components with the specified geometry component. + * If there are existing geometry components in the list (besides + * the one being replaced), the new geometry component must be of + * the same equivalence class (point, line, polygon, CompressedGeometry, + * Raster, Text3D) as the others. + * @param geometry the geometry component to be stored at index 0. + * @exception IllegalArgumentException if the new geometry + * component is not of of the same equivalence class as the + * existing geometry components. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setGeometry(Geometry geometry) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D2")); + + ((Shape3DRetained)retained).setGeometry(geometry, 0); + } + + /** + * Retrieves the geometry component at index 0 from this Shape3D node's + * list of geometry components. + * @return the geometry component at index 0. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Geometry getGeometry() { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D3")); + + return ((Shape3DRetained)retained).getGeometry(0, id); + } + + + /** + * Replaces the geometry component at the specified index in this + * Shape3D node's list of geometry components with the specified + * geometry component. + * If there are existing geometry components in the list (besides + * the one being replaced), the new geometry component must be of + * the same equivalence class (point, line, polygon, CompressedGeometry, + * Raster, Text3D) as the others. + * @param geometry the geometry component to be stored at the + * specified index. + * @param index the index of the geometry component to be replaced. + * @exception IllegalArgumentException if the new geometry + * component is not of of the same equivalence class as the + * existing geometry components. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void setGeometry(Geometry geometry, int index) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D2")); + + ((Shape3DRetained)retained).setGeometry(geometry, index); + } + + + /** + * Retrieves the geometry component at the specified index from + * this Shape3D node's list of geometry components. + * @param index the index of the geometry component to be returned. + * @return the geometry component at the specified index. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public Geometry getGeometry(int index) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D3")); + + return ((Shape3DRetained)retained).getGeometry(index, id); + } + + + /** + * Inserts the specified geometry component into this Shape3D + * node's list of geometry components at the specified index. + * If there are existing geometry components in the list, the new + * geometry component must be of the same equivalence class + * (point, line, polygon, CompressedGeometry, Raster, Text3D) as + * the others. + * @param geometry the geometry component to be inserted at the + * specified index. + * @param index the index at which the geometry component is inserted. + * @exception IllegalArgumentException if the new geometry + * component is not of of the same equivalence class as the + * existing geometry components. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void insertGeometry(Geometry geometry, int index) { + + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_GEOMETRY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D2")); + + ((Shape3DRetained)retained).insertGeometry(geometry, index); + } + + + /** + * Removes the geometry component at the specified index from + * this Shape3D node's list of geometry components. + * @param index the index of the geometry component to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void removeGeometry(int index) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D2")); + + ((Shape3DRetained)retained).removeGeometry(index); + } + + + /** + * Returns an enumeration of this Shape3D node's list of geometry + * components. + * @return an Enumeration object containing all geometry components in + * this Shape3D node's list of geometry components. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public Enumeration getAllGeometries() { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D3")); + + return ((Shape3DRetained)retained).getAllGeometries(id); + } + + + /** + * Appends the specified geometry component to this Shape3D + * node's list of geometry components. + * If there are existing geometry components in the list, the new + * geometry component must be of the same equivalence class + * (point, line, polygon, CompressedGeometry, Raster, Text3D) as + * the others. + * @param geometry the geometry component to be appended. + * @exception IllegalArgumentException if the new geometry + * component is not of of the same equivalence class as the + * existing geometry components. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void addGeometry(Geometry geometry) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D2")); + + ((Shape3DRetained)retained).addGeometry(geometry); + } + + + /** + * Returns the number of geometry components in this Shape3D node's + * list of geometry components. + * @return the number of geometry components in this Shape3D node's + * list of geometry components. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int numGeometries() { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D3")); + return ((Shape3DRetained)retained).numGeometries(id); + } + + + /** + * Retrieves the index of the specified geometry component in + * this Shape3D node's list of geometry components. + * + * @param geometry the geometry component to be looked up. + * @return the index of the specified geometry component; + * returns -1 if the object is not in the list. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int indexOfGeometry(Geometry geometry) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D3")); + return ((Shape3DRetained)retained).indexOfGeometry(geometry); + } + + + /** + * Removes the specified geometry component from this + * Shape3D node's list of geometry components. + * If the specified object is not in the list, the list is not modified. + * + * @param geometry the geometry component to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeGeometry(Geometry geometry) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D2")); + ((Shape3DRetained)retained).removeGeometry(geometry); + } + + + /** + * Removes all geometry components from this Shape3D node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeAllGeometries() { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_GEOMETRY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D2")); + ((Shape3DRetained)retained).removeAllGeometries(); + } + + + /** + * Sets the appearance component of this Shape3D node. Setting it to null + * specifies that default values are used for all appearance attributes. + * @param appearance the new appearance component for this shape node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAppearance(Appearance appearance) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_APPEARANCE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D4")); + + ((Shape3DRetained)this.retained).setAppearance(appearance); + } + + /** + * Retrieves the appearance component of this shape node. + * @return the appearance component of this shape node + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Appearance getAppearance() { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_APPEARANCE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D5")); + + return ((Shape3DRetained)this.retained).getAppearance(); + } + + + /** + * Checks whether the geometry in this shape node intersects with + * the specified pickShape. + * + * @param path the SceneGraphPath to this shape node + * @param pickShape the PickShape to be intersected + * + * @return true if the pick shape intersects this node; false + * otherwise. + * + * @exception IllegalArgumentException if pickShape is a PickPoint. + * Java 3D doesn't have spatial information of the surface. + * Use PickBounds with BoundingSphere and a small radius, instead. + * + * @exception CapabilityNotSetException if the Geometry.ALLOW_INTERSECT + * capability bit is not set in all of the Geometry objects + * referred to by this shape node. + */ + public boolean intersect(SceneGraphPath path, PickShape pickShape) { + return intersect(path, pickShape, null); + } + + + /** + * Checks whether the geometry in this shape node intersects with + * the specified pickRay. + * + * @param path the SceneGraphPath to this shape node + * @param pickRay the PickRay to be intersected + * @param dist the closest distance of the intersection + * + * @return true if the pick shape intersects this node; false + * otherwise. If true, dist contains the closest distance of + * intersection. + * + * @exception CapabilityNotSetException if the Geometry.ALLOW_INTERSECT + * capability bit is not set in all of the Geometry objects + * referred to by this shape node. + */ + public boolean intersect(SceneGraphPath path, + PickRay pickRay, + double[] dist) { + + if (isLiveOrCompiled()) { + if (!((Shape3DRetained)retained).allowIntersect()) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D6")); + } + return ((Shape3DRetained)this.retained).intersect(path, pickRay, dist); + + } + + /** + * Checks whether the geometry in this shape node intersects with + * the specified pickShape. + * + * @param path the SceneGraphPath to this shape node + * @param pickShape the PickShape to be intersected + * @param dist the closest distance of the intersection + * + * @return true if the pick shape intersects this node; false + * otherwise. If true, dist contains the closest distance of + * intersection. + * + * @exception IllegalArgumentException if pickShape is a PickPoint. + * Java 3D doesn't have spatial information of the surface. + * Use PickBounds with BoundingSphere and a small radius, instead. + * + * @exception CapabilityNotSetException if the Geometry.ALLOW_INTERSECT + * capability bit is not set in all of the Geometry objects + * referred to by this shape node. + * + * @since Java 3D 1.3 + */ + public boolean intersect(SceneGraphPath path, + PickShape pickShape, + double[] dist) { + + if (isLiveOrCompiled()) { + if (!((Shape3DRetained)retained).allowIntersect()) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D6")); + } + + if (pickShape instanceof PickPoint) { + throw new IllegalArgumentException(J3dI18N.getString("Shape3D7")); + } + + return ((Shape3DRetained)this.retained).intersect(path, pickShape, dist); + } + + + /** + * Sets a flag that indicates whether this node's appearance can + * be overridden. If the flag is true, then this node's + * appearance may be overridden by an AlternateAppearance leaf + * node, regardless of the value of the ALLOW_APPEARANCE_WRITE + * capability bit. + * The default value is false. + * + * @param flag the apperance override enable flag. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see AlternateAppearance + * + * @since Java 3D 1.2 + */ + public void setAppearanceOverrideEnable(boolean flag) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_APPEARANCE_OVERRIDE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D8")); + + ((Shape3DRetained)this.retained).setAppearanceOverrideEnable(flag); + } + + /** + * Retrieves the appearanceOverrideEnable flag for this node. + * @return true if the appearance can be overridden; false + * otherwise. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public boolean getAppearanceOverrideEnable() { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_APPEARANCE_OVERRIDE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Shape3D9")); + + return ((Shape3DRetained)this.retained).getAppearanceOverrideEnable(); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * <code>cloneNode</code> should be overridden by any user subclassed + * objects. All subclasses must have their <code>cloneNode</code> + * method consist of the following lines: + * <P><blockquote><pre> + * public Node cloneNode(boolean forceDuplicate) { + * UserSubClass usc = new UserSubClass(); + * usc.duplicateNode(this, forceDuplicate); + * return usc; + * } + * </pre></blockquote> + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Shape3D s = new Shape3D(); + s.duplicateNode(this, forceDuplicate); + return s; + } + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @exception ClassCastException if originalNode is not an instance of + * <code>Shape3D</code> + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + checkDuplicateNode(originalNode, forceDuplicate); + } + + + + /** + * Copies all Shape3D information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + + super.duplicateAttributes(originalNode, forceDuplicate); + + Shape3DRetained attr = (Shape3DRetained) originalNode.retained; + Shape3DRetained rt = (Shape3DRetained) retained; + + rt.setAppearance((Appearance) getNodeComponent( + attr.getAppearance(), + forceDuplicate, + originalNode.nodeHashtable)); + int num = attr.numGeometries(id); + if (num > 0) { + rt.setGeometry((Geometry) getNodeComponent( + attr.getGeometry(0, id), + forceDuplicate, + originalNode.nodeHashtable), 0); + for(int i=1; i< num; i++) { + rt.addGeometry((Geometry) getNodeComponent( + attr.getGeometry(i, id), + forceDuplicate, + originalNode.nodeHashtable)); + } + } + + rt.setCollisionBounds(attr.getCollisionBounds(id)); + } + + /** + * See parent class for the documentation on getBounds(). + */ + public Bounds getBounds() { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_BOUNDS_READ)) { + throw new CapabilityNotSetException(J3dI18N.getString("Node2")); + } + } else { + // this will throw a SceneGraphCycleException if there is + // a cycle + checkForCycle(); + } + + return ((Shape3DRetained)this.retained).getBounds(); + } + + +} diff --git a/src/classes/share/javax/media/j3d/Shape3DCompileRetained.java b/src/classes/share/javax/media/j3d/Shape3DCompileRetained.java new file mode 100644 index 0000000..7acd916 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Shape3DCompileRetained.java @@ -0,0 +1,504 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.*; + +/** + * A leaf node that holds a merged shapes in compile mode + */ +class Shape3DCompileRetained extends Shape3DRetained { + + + int numShapes = 0; + + // Each element in the arraylist is an array of geometries for a + // particular merged shape + ArrayList geometryInfo = null; + + + Object[] srcList = null; + + Shape3DCompileRetained(Shape3DRetained[] shapes, int nShapes, int compileFlags) { + int i, j; + Shape3DRetained shape; + GeometryArrayRetained geo; + Vector list; + // Merged list, only merged if geometry is mergeable + Object[] mergedList = new Object[GeometryRetained.GEO_TYPE_GEOMETRYARRAY+1]; + // Sorted list of separate geometry by geoType + Object[] separateList = new Object[GeometryRetained.GEO_TYPE_GEOMETRYARRAY+1]; + + // Assign the num of shapes + numShapes = nShapes; + + Bounds shapeBounds; + + srcList = new Object[nShapes]; + + if (nShapes > 0) { + boundsAutoCompute = shapes[0].boundsAutoCompute; + source = shapes[0].source; + } + + // Remove the null that was added by Shape3DRetained constructor + geometryList.remove(0); + int geoIndex = 0; + + + + // Assign the fields for this compile shape + boundsAutoCompute = shapes[0].boundsAutoCompute; + isPickable = shapes[0].isPickable; + isCollidable = shapes[0].isCollidable; + appearanceOverrideEnable = shapes[0].appearanceOverrideEnable; + appearance = shapes[0].appearance; + collisionBound = shapes[0].collisionBound; + localBounds = shapes[0].localBounds; + + + if ((compileFlags & CompileState.GEOMETRY_READ) != 0) + geometryInfo = new ArrayList(); + + for (i = 0; i < nShapes; i++) { + shape = shapes[i]; + ((Shape3D)shape.source).id = i; + shape.source.retained = this; + srcList[i] = shape.source; + // If the transform has been pushd down + // to the shape, don't merge its geometry with other shapes + // geometry + // Put it in a separate list sorted by geo_type + // Have to handle shape.isPickable + + for (j = 0; j < shape.geometryList.size(); j++) { + geo = (GeometryArrayRetained)shape.geometryList.get(j); + if (geo != null) { + if (shape.willRemainOpaque(geo.geoType) && geo.isMergeable()) { + if (mergedList[geo.geoType] == null) { + mergedList[geo.geoType] = new ArrayList(); + } + ((ArrayList)mergedList[geo.geoType]).add(geo); + } + else { + // Keep a sorted list based on geoType; + if (separateList[geo.geoType] == null) { + separateList[geo.geoType] = new ArrayList(); + } + // add it to the geometryList separately + ((ArrayList)separateList[geo.geoType]).add(geo); + } + } + + } + + // Point to the geometryList's source, so the + // retained side will be garbage collected + if ((compileFlags & CompileState.GEOMETRY_READ) != 0) { + ArrayList sList = new ArrayList(); + for (j = 0; j < shape.geometryList.size(); j++) { + GeometryRetained g = (GeometryRetained)shape.geometryList.get(j); + if (g != null) + sList.add(g.source); + else + sList.add(null); + } + geometryInfo.add(sList); + } + + } + // Now, merged the mergelist and separate list based on geoType, + // this enables dlist optmization + for (i = 1; i <= GeometryRetained.GEO_TYPE_GEOMETRYARRAY; i++) { + GeometryArrayRetained cgeo = null; + ArrayList curList; + switch (i) { + case GeometryArrayRetained.GEO_TYPE_QUAD_SET: + if (mergedList[i] != null) { + cgeo = new QuadArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_TRI_SET: + if (mergedList[i] != null) { + cgeo = new TriangleArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_POINT_SET: + if (mergedList[i] != null) { + cgeo = new PointArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_LINE_SET: + if (mergedList[i] != null) { + cgeo = new LineArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_TRI_STRIP_SET: + if (mergedList[i] != null) { + cgeo = new TriangleStripArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_TRI_FAN_SET: + if (mergedList[i] != null) { + cgeo = new TriangleFanArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_LINE_STRIP_SET: + if (mergedList[i] != null) { + cgeo = new LineStripArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_INDEXED_QUAD_SET: + if (mergedList[i] != null) { + cgeo = new IndexedQuadArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_INDEXED_TRI_SET: + if (mergedList[i] != null) { + cgeo = new IndexedTriangleArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_INDEXED_POINT_SET: + if (mergedList[i] != null) { + cgeo = new IndexedPointArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_INDEXED_LINE_SET: + if (mergedList[i] != null) { + cgeo = new IndexedLineArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_INDEXED_TRI_STRIP_SET: + if (mergedList[i] != null) { + cgeo = new IndexedTriangleStripArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_INDEXED_TRI_FAN_SET: + if (mergedList[i] != null) { + cgeo = new IndexedTriangleFanArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + case GeometryArrayRetained.GEO_TYPE_INDEXED_LINE_STRIP_SET: + if (mergedList[i] != null) { + cgeo = new IndexedLineStripArrayRetained(); + curList = (ArrayList)mergedList[i]; + cgeo.setCompiled(curList); + geometryList.add(cgeo); + cgeo.setSource(((SceneGraphObjectRetained)curList.get(0)).source); + } + if (separateList[i] != null) { + ArrayList glist = (ArrayList)separateList[i]; + for (int k = 0; k < glist.size(); k++) { + geometryList.add(glist.get(k)); + } + + } + break; + } + } + + + } + + + Bounds getCollisionBounds(int childIndex) { + return collisionBound; + } + + + int numGeometries(int childIndex) { + ArrayList geo = (ArrayList) geometryInfo.get(childIndex); + return geo.size(); + } + + + Geometry getGeometry(int i, int childIndex) { + ArrayList geoInfo = (ArrayList) geometryInfo.get(childIndex); + return (Geometry)geoInfo.get(i); + + + } + + Enumeration getAllGeometries(int childIndex) { + ArrayList geoInfo = (ArrayList) geometryInfo.get(childIndex); + Vector geomList = new Vector(); + + for(int i=0; i<geoInfo.size(); i++) { + geomList.add(geoInfo.get(i)); + } + + return geomList.elements(); + } + + Bounds getBounds(int childIndex) { + if(boundsAutoCompute) { + ArrayList glist = (ArrayList) geometryInfo.get(childIndex); + + if(glist != null) { + BoundingBox bbox = new BoundingBox((Bounds) null); + for(int i=0; i<glist.size(); i++) { + Geometry g = (Geometry) glist.get(i); + if (g != null) { + GeometryRetained geometry = (GeometryRetained)g.retained; + if (geometry.geoType != GeometryRetained.GEO_TYPE_NONE) { + geometry.computeBoundingBox(); + synchronized(geometry.geoBounds) { + bbox.combine(geometry.geoBounds); + } + } + } + } + + return (Bounds) bbox; + + } else { + return null; + } + + } else { + return super.getBounds(); + } + } + + + /** + * Check if the geometry component of this shape node under path + * intersects with the pickRay. + * @return true if intersected else false. If return is true, dist + * contains the closest + * distance of intersection. + * @exception IllegalArgumentException if <code>path</code> is + * invalid. + */ + + boolean intersect(SceneGraphPath path, + PickShape pickShape, double[] dist) { + + // This method will not do bound intersect check, as it assume + // caller has already done that. ( For performance and code + // simplification reasons. ) + + Transform3D localToVworld = path.getTransform(); + int i; + + if (localToVworld == null) { + throw new IllegalArgumentException(J3dI18N.getString("Shape3DRetained3")); + } + + Transform3D t3d = VirtualUniverse.mc.getTransform3D(null); + t3d.invert(localToVworld); + PickShape newPS = pickShape.transform(t3d); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t3d); + + Shape3D shape = (Shape3D) path.getObject(); + // Get the geometries for this shape only, since the compiled + // geomtryList contains several shapes + ArrayList glist = (ArrayList) geometryInfo.get(shape.id); + + int geomListSize = glist.size(); + + Point3d iPnt = Shape3DRetained.getPoint3d(); + GeometryRetained geometry; + + if (dist == null) { + for (i=0; i < geomListSize; i++) { + Geometry g = (Geometry) glist.get(i); + if (g != null && g.retained != null) { + geometry = (GeometryRetained)g.retained; + if (geometry.mirrorGeometry != null) { + geometry = geometry.mirrorGeometry; + } + + if (geometry.intersect(newPS, null, iPnt)) { + Shape3DRetained.freePoint3d(iPnt); + return true; + } + } + } + } else { + double minDist = Double.POSITIVE_INFINITY; + // TODO : BugId 4351579 -- Need to return the index of nearest + // intersected geometry too. + + for (i=0; i < geomListSize; i++) { + Geometry g = (Geometry) glist.get(i); + if (g != null && g.retained != null) { + geometry = (GeometryRetained)g.retained; + if (geometry.mirrorGeometry != null) { + geometry = geometry.mirrorGeometry; + } + if (geometry.intersect(newPS, dist, iPnt)) { + localToVworld.transform(iPnt); + dist[0] = pickShape.distance(iPnt); + if (minDist > dist[0]) { + minDist = dist[0]; + } + } + } + } + + if (minDist < Double.POSITIVE_INFINITY) { + dist[0] = minDist; + Shape3DRetained.freePoint3d(iPnt); + return true; + } + } + + Shape3DRetained.freePoint3d(iPnt); + return false; + } +} diff --git a/src/classes/share/javax/media/j3d/Shape3DRetained.java b/src/classes/share/javax/media/j3d/Shape3DRetained.java new file mode 100644 index 0000000..f115c99 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Shape3DRetained.java @@ -0,0 +1,2770 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Vector; + +/** + * A shape leaf node consisting of geometry and appearance properties. + */ + +class Shape3DRetained extends LeafRetained { + + static final int GEOMETRY_CHANGED = 0x00001; + static final int APPEARANCE_CHANGED = 0x00002; + static final int COLLISION_CHANGED = 0x00004; + static final int BOUNDS_CHANGED = 0x00008; + static final int APPEARANCEOVERRIDE_CHANGED = 0x00010; + static final int LAST_DEFINED_BIT = 0x00010; + + + // Target threads to be notified when light changes + static final int targetThreads = J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER; + + /** + * The appearance component of the shape node. + */ + AppearanceRetained appearance = null; + + /** + * The arraylist of geometry component of the shape node. + */ + ArrayList geometryList = null; + + /** + * A 2D storage of all geometry atoms associated with this shape node. + * There may be more than one geometry for a Shape3D node. + * Do not change the following private variables to public, its access need to synchronize + * via mirrorShape3DLock. + */ + + // geomAtomArr should always be a 1 element array, unless S3D contains multiple Text3Ds. + private GeometryAtom geomAtom = null; + + /** + * To sychronize access of the mirrorShape3D's geomAtomArray*. + * A multiple read single write Lock to sychronize access into mirrorShape3D. + * To prevent deadlock a call to read/write lock must end with a read/write unlock + * respectively. + */ + private MRSWLock mirrorShape3DLock = null; + + /** + * The mirror Shape3DRetained nodes for this object. There is one + * mirror for each instance of this Shape3D node. If it is not in + * a SharedGroup, only index 0 is valid. + * Do not change the following private variables to public, its access need to synchronize + * via mirrorShape3DLock. + */ + ArrayList mirrorShape3D = new ArrayList(1); + + /** + * This field is used for mirror Shape3D nodes accessing their + * original nodes. It is a NodeRetained because the original + * node may be a Shape3DRetained or a MorphRetained node. + */ + NodeRetained sourceNode = null; + + /** + * The hashkey for this Shape3DRetained mirror object + */ + HashKey key = null; + + // This is true when this geometry is referenced in an IMM mode context + boolean inImmCtx = false; + + // A bitmask to indicate when something has changed + int isDirty = 0xffff; + + // The list of lights that are scoped to this node + LightRetained[] lights =null; + + // The number of lights in the above array, may be less than lights.length + int numlights = 0; + + // The list of fogs that are scoped to this node + FogRetained[] fogs = null; + + // The number of fogs in the above array, may be less than fogs.length + int numfogs = 0; + + // The list of modelClips that are scoped to this node + ModelClipRetained[] modelClips = null; + + // The number of modelClips in the above array, may be less than modelClips.length + int numModelClips = 0; + + // The list of alt app that are scoped to this node + AlternateAppearanceRetained[] altApps = null; + + //The number of alt app in the above array, may be less than alt app.length + int numAltApps = 0; + + /** + * Reference to the BranchGroup path of this mirror shape + * This is used for picking and GeometryStructure only. + */ + BranchGroupRetained branchGroupPath[]; + + // cache value for picking in mirror shape. + // True if all the node of the path from this to root are all pickable + boolean isPickable = true; + + // cache value for collidable in mirror shape. + // True if all the node of the path from this to root are all collidable + boolean isCollidable = true; + + // closest switch parent + SwitchRetained closestSwitchParent = null; + + // the child index from the closest switch parent + int closestSwitchIndex = -1; + + // Is this S3D visible ? The default is true. + boolean visible = true; + + // Whether the normal appearance is overrided by the alternate app + boolean appearanceOverrideEnable = false; + + // AlternateAppearance retained that is applicable to this + // mirror shape when the override flag is true + AppearanceRetained otherAppearance = null; + + // geometry Bounds in local coordinate + Bounds bounds = null; + + // geometry Bounds in virtual world coordinate + BoundingBox vwcBounds = null; + + // collision Bounds in local coordinate + Bounds collisionBound = null; + + // collision Bounds in virtual world coordinate + Bounds collisionVwcBound = null; + + // a path of OrderedGroup, childrenId pairs which leads to this node + OrderedPath orderedPath = null; + + // List of views that a mirror object is scoped to + ArrayList viewList = null; + + int changedFrequent = 0; + + Shape3DRetained() { + super(); + this.nodeType = NodeRetained.SHAPE; + numlights = 0; + numfogs = 0; + numModelClips = 0; + numAltApps = 0; + localBounds = new BoundingBox((BoundingBox) null); + + mirrorShape3DLock = new MRSWLock(); + geometryList = new ArrayList(1); + geometryList.add(null); + } + + /** + * Sets the collision bounds of a node. + * @param bounds the bounding object for the node + */ + void setCollisionBounds(Bounds bounds) { + if (bounds == null) { + this.collisionBound = null; + } else { + this.collisionBound = (Bounds)bounds.clone(); + } + + if (source.isLive()) { + // Notify Geometry Structure to check for collision + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.COLLISION_BOUND_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM; + message.universe = universe; + message.args[0] = getGeomAtomsArray(mirrorShape3D); + // no need to clone collisionBound + message.args[1] = collisionBound; + VirtualUniverse.mc.processMessage(message); + } + } + + Bounds getLocalBounds(Bounds bounds) { + if(localBounds != null) { + localBounds.set(bounds); + } + else { + localBounds = new BoundingBox(bounds); + } + return localBounds; + } + + + /** + * Sets the geometric bounds of a node. + * @param bounds the bounding object for the node + */ + void setBounds(Bounds bounds) { + super.setBounds(bounds); + + if (source.isLive() && !boundsAutoCompute) { + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.REGION_BOUND_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_RENDER; + + message.universe = universe; + message.args[0] = getGeomAtomsArray(mirrorShape3D); + // no need to clone localBounds + message.args[1] = localBounds; + VirtualUniverse.mc.processMessage(message); + } + } + + /** + * Gets the collision bounds of a node. + * @return the node's bounding object + */ + Bounds getCollisionBounds(int id) { + return (collisionBound == null ? + null: (Bounds)collisionBound.clone()); + } + + /** + * Appends the specified geometry component to this Shape3D + * node's list of geometry components. + * If there are existing geometry components in the list, the new + * geometry component must be of the same equivalence class + * (point, line, polygon, CompressedGeometry, Raster, Text3D) as + * the others. + * @param geometry the geometry component to be appended. + * @exception IllegalArgumentException if the new geometry + * component is not of of the same equivalence class as the + * existing geometry components. + * + * @since Java 3D 1.2 + */ + void addGeometry(Geometry geometry) { + int i; + Shape3DRetained s; + GeometryRetained newGeom = null; + + checkEquivalenceClass(geometry, -1); + + if(((Shape3D)this.source).isLive()) { + if (geometry != null) { + + newGeom = ((GeometryRetained)geometry.retained); + newGeom.setLive(inBackgroundGroup, refCount); + + geometryList.add(newGeom); + + } else { + geometryList.add(null); + newGeom = null; + } + sendDataChangedMessage(newGeom); + + } else { + if (geometry != null) { + geometryList.add((GeometryRetained) geometry.retained); + } else { + geometryList.add(null); + } + } + + } + + /** + * Replaces the geometry component at the specified index in this + * Shape3D node's list of geometry components with the specified + * geometry component. + * If there are existing geometry components in the list (besides + * the one being replaced), the new geometry component must be of + * the same equivalence class (point, line, polygon, CompressedGeometry, + * Raster, Text3D) as the others. + * @param geometry the geometry component to be stored at the + * specified index. + * @param index the index of the geometry component to be replaced. + * @exception IllegalArgumentException if the new geometry + * component is not of of the same equivalence class as the + * existing geometry components. + * + * @since Java 3D 1.2 + */ + void setGeometry(Geometry geometry, int index) { + int i; + Shape3DRetained mShape; + GeometryRetained newGeom = null; + GeometryRetained oldGeom = null; + + checkEquivalenceClass(geometry, index); + + if (((Shape3D)this.source).isLive()) { + + oldGeom = (GeometryRetained) (geometryList.get(index)); + if (oldGeom != null) { + oldGeom.clearLive(refCount); + for (i=0; i<mirrorShape3D.size(); i++) { + mShape = (Shape3DRetained)mirrorShape3D.get(i); + oldGeom.removeUser(mShape); + } + oldGeom.decRefCnt(); + } + + if (geometry != null) { + newGeom = (GeometryRetained) geometry.retained; + newGeom.incRefCnt(); + newGeom.setLive(inBackgroundGroup, refCount); + geometryList.set(index, newGeom); + sendDataChangedMessage(newGeom); + } else { + geometryList.set(index, null); + sendDataChangedMessage(null); + } + + } else { + + oldGeom = (GeometryRetained) (geometryList.get(index)); + if (oldGeom != null) { + oldGeom.decRefCnt(); + } + if (geometry != null) { + geometryList.set(index,(GeometryRetained) geometry.retained); + ((GeometryRetained)geometry.retained).incRefCnt(); + } else { + geometryList.set(index,null); + } + } + } + + /** + * Inserts the specified geometry component into this Shape3D + * node's list of geometry components at the specified index. + * If there are existing geometry components in the list, the new + * geometry component must be of the same equivalence class + * (point, line, polygon, CompressedGeometry, Raster, Text3D) as + * the others. + * @param geometry the geometry component to be inserted at the + * specified index. + * @param index the index at which the geometry component is inserted. + * + * @since Java 3D 1.2 + */ + void insertGeometry(Geometry geometry, int index) { + int i; + Shape3DRetained mShape; + GeometryRetained newGeom = null; + GeometryRetained oldGeom = null; + + checkEquivalenceClass(geometry, -1); + + if (((Shape3D)this.source).isLive()) { + + if (geometry != null) { + // Note : The order of the statements in important. Want ArrayList class to do index bounds + // check before creating internal object. + newGeom = (GeometryRetained) geometry.retained; + newGeom.incRefCnt(); + geometryList.add(index, newGeom); + newGeom.setLive(inBackgroundGroup, refCount); + sendDataChangedMessage(newGeom); + } else { + geometryList.add(index, null); + sendDataChangedMessage(null); + } + + } else { + + if (geometry != null) { + geometryList.add(index,(GeometryRetained) geometry.retained); + ((GeometryRetained)geometry.retained).incRefCnt(); + } else { + geometryList.add(index,null); + } + } + } + + /** + * Removes the geometry component at the specified index from + * this Shape3D node's list of geometry components. + * @param index the index of the geometry component to be removed. + * + * @since Java 3D 1.2 + */ + void removeGeometry(int index) { + int i; + Shape3DRetained mShape; + GeometryRetained oldGeom = null; + + if (((Shape3D)this.source).isLive()) { + + oldGeom = (GeometryRetained) (geometryList.get(index)); + if (oldGeom != null) { + oldGeom.clearLive(refCount); + oldGeom.decRefCnt(); + for (i=0; i<mirrorShape3D.size(); i++) { + mShape = (Shape3DRetained)mirrorShape3D.get(i); + oldGeom.removeUser(mShape); + + } + } + + geometryList.remove(index); + sendDataChangedMessage(null); + + } else { + oldGeom = (GeometryRetained) (geometryList.get(index)); + if (oldGeom != null) { + oldGeom.decRefCnt(); + } + geometryList.remove(index); + } + + + + } + + /** + * Retrieves the geometry component of this Shape3D node. + * @return the geometry component of this shape node + * + * @since Java 3D 1.2 + */ + Geometry getGeometry(int index, int id) { + GeometryRetained ga = (GeometryRetained) geometryList.get(index); + return (ga == null ? null : (Geometry)ga.source); + } + + + /** + * Returns an enumeration of this Shape3D node's list of geometry + * components. + * @return an Enumeration object containing all geometry components in + * this Shape3D node's list of geometry components. + * + * @since Java 3D 1.2 + */ + Enumeration getAllGeometries(int id) { + GeometryRetained ga = null; + Vector geomList = new Vector(geometryList.size()); + + for(int i=0; i<geometryList.size(); i++) { + ga = (GeometryRetained) geometryList.get(i); + if(ga != null) + geomList.add((Geometry)ga.source); + else + geomList.add(null); + } + + return geomList.elements(); + } + + /** + * Returns the number of geometry components in this Shape3D node's + * list of geometry components. + * @return the number of geometry components in this Shape3D node's + * list of geometry components. + * + * @since Java 3D 1.2 + */ + int numGeometries(int id) { + + return geometryList.size(); + } + + /** + * Sets the appearance component of this Shape3D node. + * @param appearance the new apearance component for this shape node + */ + void setAppearance(Appearance newAppearance) { + + Shape3DRetained s; + boolean visibleIsDirty = false; + + if (((Shape3D)this.source).isLive()) { + if (appearance != null) { + appearance.clearLive(refCount); + for (int i=0; i<mirrorShape3D.size(); i++) { + s = (Shape3DRetained)mirrorShape3D.get(i); + appearance.removeAMirrorUser(s); + } + } + + if (newAppearance != null) { + ((AppearanceRetained)newAppearance.retained).setLive(inBackgroundGroup, refCount); + appearance = ((AppearanceRetained)newAppearance.retained); + for (int i=0; i<mirrorShape3D.size(); i++) { + s = (Shape3DRetained)mirrorShape3D.get(i); + appearance.addAMirrorUser(s); + } + if((appearance.renderingAttributes != null) && + (visible != appearance.renderingAttributes.visible)) { + visible = appearance.renderingAttributes.visible; + visibleIsDirty = true; + } + } + else { + if(visible == false) { + visible = true; + visibleIsDirty = true; + } + } + int size = 0; + if (visibleIsDirty) + size = 2; + else + size = 1; + J3dMessage[] createMessage = new J3dMessage[size]; + // Send a message + createMessage[0] = VirtualUniverse.mc.getMessage(); + createMessage[0].threads = targetThreads; + createMessage[0].type = J3dMessage.SHAPE3D_CHANGED; + createMessage[0].universe = universe; + createMessage[0].args[0] = this; + createMessage[0].args[1]= new Integer(APPEARANCE_CHANGED); + Shape3DRetained[] s3dArr = new Shape3DRetained[mirrorShape3D.size()]; + mirrorShape3D.toArray(s3dArr); + createMessage[0].args[2] = s3dArr; + Object[] obj = new Object[2]; + if (newAppearance == null) { + obj[0] = null; + } + else { + obj[0] = appearance.mirror; + } + obj[1] = new Integer(changedFrequent); + createMessage[0].args[3] = obj; + createMessage[0].args[4] = getGeomAtomsArray(mirrorShape3D); + if(visibleIsDirty) { + createMessage[1] = VirtualUniverse.mc.getMessage(); + createMessage[1].threads = J3dThread.UPDATE_GEOMETRY; + createMessage[1].type = J3dMessage.SHAPE3D_CHANGED; + createMessage[1].universe = universe; + createMessage[1].args[0] = this; + createMessage[1].args[1]= new Integer(APPEARANCE_CHANGED); + createMessage[1].args[2]= visible?Boolean.TRUE:Boolean.FALSE; + createMessage[1].args[3]= createMessage[0].args[4]; + } + VirtualUniverse.mc.processMessage(createMessage); + + } + else { // not live. + if (newAppearance == null) { + appearance = null; + } else { + appearance = (AppearanceRetained) newAppearance.retained; + } + } + } + + /** + * Retrieves the shape node's appearance component. + * @return the shape node's appearance + */ + Appearance getAppearance() { + return (appearance == null ? null: (Appearance) appearance.source); + } + + + /** + * Check if the geometry component of this shape node under path + * intersects with the pickShape. + * This is an expensive method. It should only be called if and only + * if the path's bound intersects pickShape. + * @exception IllegalArgumentException if <code>path</code> is + * invalid. + */ + + boolean intersect(SceneGraphPath path, + PickShape pickShape, double[] dist) { + + // This method will not do bound intersect check, as it assume + // caller has already done that. ( For performance and code + // simplification reasons. ) + + Transform3D localToVworld = path.getTransform(); + int i; + + if (localToVworld == null) { + throw new IllegalArgumentException(J3dI18N.getString("Shape3DRetained3")); + } + + // Support OrientedShape3D here. + // TODO - BugId : 4363899 - APIs issue : OrientedShape3D's intersect needs view + // info. temp. fix use the primary view. + if (this instanceof OrientedShape3DRetained) { + Transform3D orientedTransform = ((OrientedShape3DRetained)this). + getOrientedTransform(getPrimaryViewIdx()); + localToVworld.mul(orientedTransform); + } + + Transform3D t3d = VirtualUniverse.mc.getTransform3D(null); + t3d.invert(localToVworld); + PickShape newPS = pickShape.transform(t3d); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t3d); + + // TODO: For optimization - Should do a geobounds check of + // each geometry first. But this doesn't work for + // OrientedShape3D case... + int geomListSize = geometryList.size(); + Point3d iPnt = getPoint3d(); + GeometryRetained geometry; + + if (dist == null) { + for (i=0; i < geomListSize; i++) { + geometry = (GeometryRetained) geometryList.get(i); + if (geometry != null) { + if (geometry.mirrorGeometry != null) { + geometry = geometry.mirrorGeometry; + } + if (geometry.intersect(newPS, null, iPnt)) { + freePoint3d(iPnt); + return true; + } + } + } + } else { + double minDist = Double.POSITIVE_INFINITY; + // TODO : BugId 4351579 -- Need to return the index of nearest + // intersected geometry too. + + for (i=0; i < geomListSize; i++) { + geometry = (GeometryRetained) geometryList.get(i); + if (geometry != null) { + if (geometry.mirrorGeometry != null) { + geometry = geometry.mirrorGeometry; + } + if (geometry.intersect(newPS, dist, iPnt)) { + localToVworld.transform(iPnt); + dist[0] = pickShape.distance(iPnt); + if (minDist > dist[0]) { + minDist = dist[0]; + } + } + } + } + + if (minDist < Double.POSITIVE_INFINITY) { + dist[0] = minDist; + freePoint3d(iPnt); + return true; + } + } + + freePoint3d(iPnt); + + return false; + } + + void setAppearanceOverrideEnable(boolean flag) { + if (((Shape3D)this.source).isLive()) { + + // Send a message + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.SHAPE3D_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(APPEARANCEOVERRIDE_CHANGED); + Shape3DRetained[] s3dArr = new Shape3DRetained[mirrorShape3D.size()]; + mirrorShape3D.toArray(s3dArr); + createMessage.args[2] = s3dArr; + Object[] obj = new Object[2]; + if (flag) { + obj[0] = Boolean.TRUE; + } + else { + obj[0] = Boolean.FALSE; + } + obj[1] = new Integer(changedFrequent); + createMessage.args[3] = obj; + createMessage.args[4] = getGeomAtomsArray(mirrorShape3D); + + VirtualUniverse.mc.processMessage(createMessage); + } + appearanceOverrideEnable = flag; + } + + boolean getAppearanceOverrideEnable() { + return appearanceOverrideEnable; + } + + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return (inImmCtx); + } + + /** + * This updates the mirror shape to reflect the state of the + * real shape3d. + */ + private void initMirrorShape3D(SetLiveState s, Shape3DRetained ms, int index) { + + // New 1.2.1 code + + ms.inBackgroundGroup = inBackgroundGroup; + ms.geometryBackground = geometryBackground; + ms.source = source; + ms.universe = universe; + // Has to be false. We have a instance of mirror for every link to the shape3d. + ms.inSharedGroup = false; + ms.locale = locale; + ms.parent = parent; + + OrderedPath op = (OrderedPath)s.orderedPaths.get(index); + if (op.pathElements.size() == 0) { + ms.orderedPath = null; + } else { + ms.orderedPath = op; +/* + System.out.println("initMirrorShape3D ms.orderedPath "); + ms.orderedPath.printPath(); +*/ + } + + // all mirror shapes point to the same transformGroupRetained + // for the static transform + ms.staticTransform = staticTransform; + + + ms.appearanceOverrideEnable = appearanceOverrideEnable; + + ms.geometryList = geometryList; + + // Assign the parent of this mirror shape node + ms.sourceNode = this; + + if (this instanceof OrientedShape3DRetained) { + OrientedShape3DRetained os = (OrientedShape3DRetained)this; + OrientedShape3DRetained oms = (OrientedShape3DRetained)ms; + oms.initAlignmentMode(os.mode); + oms.initAlignmentAxis(os.axis); + oms.initRotationPoint(os.rotationPoint); + oms.initConstantScaleEnable(os.constantScale); + oms.initScale(os.scaleFactor); + } + + } + + void updateImmediateMirrorObject(Object[] objs) { + int component = ((Integer)objs[1]).intValue(); + GeometryArrayRetained ga; + + Shape3DRetained[] msArr = (Shape3DRetained[]) objs[2]; + int i, j; + if ((component & APPEARANCE_CHANGED) != 0) { + Object[] arg = (Object[])objs[3]; + int val = ((Integer)arg[1]).intValue(); + for ( i = msArr.length-1; i >=0; i--) { + msArr[i].appearance = (AppearanceRetained)arg[0]; + msArr[i].changedFrequent = val; + } + } + if ((component & APPEARANCEOVERRIDE_CHANGED) != 0) { + Object[] arg = (Object[])objs[3]; + int val = ((Integer)arg[1]).intValue(); + for ( i = msArr.length-1; i >=0; i--) { + msArr[i].appearanceOverrideEnable = ((Boolean)arg[0]).booleanValue(); + msArr[i].changedFrequent = val; + } + } + } + + /** + * Gets the bounding object of a node. + * @return the node's bounding object + */ + + Bounds getBounds() { + + if(boundsAutoCompute) { + // System.out.println("getBounds ---- localBounds is " + localBounds); + + + if(geometryList != null) { + BoundingBox bbox = new BoundingBox((Bounds) null); + GeometryRetained geometry; + for(int i=0; i<geometryList.size(); i++) { + geometry = (GeometryRetained) geometryList.get(i); + if ((geometry != null) && + (geometry.geoType != GeometryRetained.GEO_TYPE_NONE)) { + geometry.computeBoundingBox(); + synchronized(geometry.geoBounds) { + bbox.combine(geometry.geoBounds); + } + } + } + return (Bounds) bbox; + + } else { + return null; + } + + } else { + return super.getBounds(); + } + } + + Bounds getEffectiveBounds() { + if(boundsAutoCompute) { + return getBounds(); + } + else { + return super.getEffectiveBounds(); + } + } + + + /** + * ONLY needed for SHAPE, MORPH, and LINK node type. + * Compute the combine bounds of bounds and its localBounds. + */ + void computeCombineBounds(Bounds bounds) { + + if(boundsAutoCompute) { + + if(geometryList != null) { + GeometryRetained geometry; + BoundingBox bbox = null; + + if (staticTransform != null) { + bbox = new BoundingBox((BoundingBox) null); + } + + for(int i=0; i<geometryList.size(); i++) { + geometry = (GeometryRetained) geometryList.get(i); + if ((geometry != null) && + (geometry.geoType != GeometryRetained.GEO_TYPE_NONE)) { + geometry.computeBoundingBox(); + // Should this be lock too ? ( MT safe ? ) + synchronized(geometry.geoBounds) { + if (staticTransform != null) { + bbox.set(geometry.geoBounds); + bbox.transform(staticTransform.transform); + bounds.combine((Bounds)bbox); + } else { + bounds.combine((Bounds)geometry.geoBounds); + } + } + } + } + } + + } else { + + // Should this be lock too ? ( MT safe ? ) + synchronized(localBounds) { + bounds.combine((Bounds) localBounds); + } + } + } + + /** + * assign a name to this node when it is made live. + */ + + void setLive(SetLiveState s) { + doSetLive(s); + markAsLive(); + } + + void doSetLive(SetLiveState s) { + // System.out.println("S3DRetained : setLive " + s); + Shape3DRetained shape; + GeometryRetained geometry; + int i, j, k, gaCnt; + ArrayList msList = new ArrayList(); + + super.doSetLive(s); + + nodeId = universe.getNodeId(); + + + if (inSharedGroup) { + for (i=0; i<s.keys.length; i++) { + if (this instanceof OrientedShape3DRetained) { + shape = new OrientedShape3DRetained(); + } else { + shape = new Shape3DRetained(); + } + shape.key = s.keys[i]; + shape.localToVworld = new Transform3D[1][]; + shape.localToVworldIndex = new int[1][]; + + j = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + /* + System.out.print("s.keys[i] = "+s.keys[i]+" j = "+j); + if(j < 0) { + System.out.println("Shape3dRetained : Can't find hashKey"); + } + */ + shape.localToVworld[0] = localToVworld[j]; + shape.localToVworldIndex[0] = localToVworldIndex[j]; + shape.branchGroupPath = (BranchGroupRetained []) branchGroupPaths.get(j); + shape.isPickable = s.pickable[i]; + shape.isCollidable = s.collidable[i]; + + initMirrorShape3D(s, shape, j); + + if (s.switchTargets != null && + s.switchTargets[i] != null) { + s.switchTargets[i].addNode(shape, Targets.GEO_TARGETS); + shape.closestSwitchParent = s.closestSwitchParents[i]; + shape.closestSwitchIndex = s.closestSwitchIndices[i]; + } + shape.switchState = (SwitchState)s.switchStates.get(j); + + + // Add any scoped lights to the mirror shape + if (s.lights != null) { + ArrayList l = (ArrayList)s.lights.get(j); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addLight((LightRetained)l.get(m)); + } + } + } + + // Add any scoped fog + if (s.fogs != null) { + ArrayList l = (ArrayList)s.fogs.get(j); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addFog((FogRetained)l.get(m)); + } + } + } + + // Add any scoped modelClip + if (s.modelClips != null) { + ArrayList l = (ArrayList)s.modelClips.get(j); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addModelClip((ModelClipRetained)l.get(m)); + } + } + } + + // Add any scoped alt app + if (s.altAppearances != null) { + ArrayList l = (ArrayList)s.altAppearances.get(j); + if (l != null) { + for (int m = 0; m < l.size(); m++) { + shape.addAltApp((AlternateAppearanceRetained)l.get(m)); + } + } + } + synchronized(mirrorShape3D) { + mirrorShape3D.add(j,shape); + } + + msList.add(shape); + if (s.viewLists != null) { + shape.viewList = (ArrayList)s.viewLists.get(j); + } else { + shape.viewList = null; + } + } + } else { + if (this instanceof OrientedShape3DRetained) { + shape = new OrientedShape3DRetained(); + } else { + shape = new Shape3DRetained(); + } + + shape.localToVworld = new Transform3D[1][]; + shape.localToVworldIndex = new int[1][]; + shape.localToVworld[0] = localToVworld[0]; + shape.localToVworldIndex[0] = localToVworldIndex[0]; + shape.branchGroupPath = (BranchGroupRetained []) branchGroupPaths.get(0); + shape.isPickable = s.pickable[0]; + shape.isCollidable = s.collidable[0]; + initMirrorShape3D(s, shape, 0); + + // Add any scoped lights to the mirror shape + if (s.lights != null) { + ArrayList l = (ArrayList)s.lights.get(0); + for (i = 0; i < l.size(); i++) { + shape.addLight((LightRetained)l.get(i)); + } + } + + // Add any scoped fog + if (s.fogs != null) { + ArrayList l = (ArrayList)s.fogs.get(0); + for (i = 0; i < l.size(); i++) { + shape.addFog((FogRetained)l.get(i)); + } + } + + // Add any scoped modelClip + if (s.modelClips != null) { + ArrayList l = (ArrayList)s.modelClips.get(0); + for (i = 0; i < l.size(); i++) { + shape.addModelClip((ModelClipRetained)l.get(i)); + } + + } + + // Add any scoped alt app + if (s.altAppearances != null) { + ArrayList l = (ArrayList)s.altAppearances.get(0); + for (i = 0; i < l.size(); i++) { + shape.addAltApp((AlternateAppearanceRetained)l.get(i)); + } + } + synchronized(mirrorShape3D) { + mirrorShape3D.add(shape); + } + + msList.add(shape); + if (s.viewLists != null) + shape.viewList = (ArrayList)s.viewLists.get(0); + else + shape.viewList = null; + + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(shape, Targets.GEO_TARGETS); + shape.closestSwitchParent = s.closestSwitchParents[0]; + shape.closestSwitchIndex = s.closestSwitchIndices[0]; + } + shape.switchState = (SwitchState)s.switchStates.get(0); + } + + for (k = 0; k < msList.size(); k++) { + Shape3DRetained sh = (Shape3DRetained)msList.get(k); + + if (appearance != null) { + synchronized(appearance.liveStateLock) { + if (k == 0) { // Do only first time + appearance.setLive(inBackgroundGroup, s.refCount); + appearance.initMirrorObject(); + if (appearance.renderingAttributes != null) + visible = appearance.renderingAttributes.visible; + } + sh.appearance = (AppearanceRetained)appearance.mirror; + appearance.addAMirrorUser(sh); + + } + } + else { + sh.appearance = null; + } + + if (geometryList != null) { + for(gaCnt=0; gaCnt<geometryList.size(); gaCnt++) { + geometry = (GeometryRetained) geometryList.get(gaCnt); + if(geometry != null) { + synchronized(geometry.liveStateLock) { + if (k == 0) { // Do only first time + geometry.setLive(inBackgroundGroup, s.refCount); + } + geometry.addUser(sh); + } + } + } + + } + + // after the geometry has been setLived and bounds computed + if (k== 0 && boundsAutoCompute) { // Do only once + // user may call setBounds with a bounds other than boundingBox + if (! (localBounds instanceof BoundingBox)) { + localBounds = new BoundingBox((BoundingBox) null); + } + getCombineBounds((BoundingBox)localBounds); + + } + // Assign GAtom and set the bounds if we are not using switch + initializeGAtom(sh); + + GeometryAtom ga = getGeomAtom(sh); + + // Add the geometry atom for this shape to the nodeList + s.nodeList.add(ga); + + if (s.transformTargets != null && + s.transformTargets[k] != null) { + // Add the geometry atom for this shape to the transformTargets + + s.transformTargets[k].addNode(ga, Targets.GEO_TARGETS); + } + } + + s.notifyThreads |= (J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_RENDERING_ENVIRONMENT); + + } + + /** + * This clears all references in a mirror shape + */ + // This is call in RenderingEnvironmentStructure.removeNode() because that is the + // last point that will reference this ms. + // called on the mirror shape .. + void clearMirrorShape() { + int i; + + source = null; + sourceNode = null; + parent = null; + + if (otherAppearance != null) { + otherAppearance.sgApp.removeAMirrorUser(this); + otherAppearance = null; + } + + appearance = null; + + branchGroupPath = null; + isPickable = true; + isCollidable = true; + branchGroupPath = null; + // No locking needed. Owner, s3dR, has already been destory. + // DO NOT clear geometryList, ie. geometryList.clear(). + // It is referred by the source s3DRetained. + geometryList = null; + + // Clear the mirror scoping info + // Remove all the fogs + for (i = 0; i < numfogs; i++) + fogs[i] = null; + numfogs = 0; + + // Remove all the modelClips + for (i = 0; i < numModelClips; i++) + modelClips[i] = null; + numModelClips = 0; + + // Remove all the lights + for (i = 0; i < numlights; i++) + lights[i] = null; + numlights = 0; + + // Remove all the al app + for (i = 0; i < numAltApps; i++) + altApps[i] = null; + numAltApps = 0; + + viewList = null; + + } + + /** + * assign a name to this node when it is made live. + */ + void clearLive(SetLiveState s) { + + //System.out.println("S3DRetained : clearLive " + s); + + int i, j, gaCnt; + Shape3DRetained shape; + GeometryRetained geometry; + Object[] shapes; + ArrayList msList = new ArrayList(); + + super.clearLive(s); + + + + if (inSharedGroup) { + synchronized(mirrorShape3D) { + shapes = mirrorShape3D.toArray(); + for (i=0; i<s.keys.length; i++) { + for (j=0; j<shapes.length; j++) { + shape = (Shape3DRetained)shapes[j]; + if (shape.key.equals(s.keys[i])) { + mirrorShape3D.remove(mirrorShape3D.indexOf(shape)); + if (s.switchTargets != null && + s.switchTargets[i] != null) { + s.switchTargets[i].addNode( + shape, Targets.GEO_TARGETS); + } + msList.add(shape); + GeometryAtom ga = getGeomAtom(shape); + + // Add the geometry atom for this shape to the nodeList + s.nodeList.add(ga); + if (s.transformTargets != null && + s.transformTargets[i] != null) { + s.transformTargets[i].addNode(ga, Targets.GEO_TARGETS); + } + } + } + } + } + } else { + // Only entry 0 is valid + shape = (Shape3DRetained)mirrorShape3D.get(0); + synchronized(mirrorShape3D) { + mirrorShape3D.remove(0); + } + + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(shape, Targets.GEO_TARGETS); + } + + + msList.add(shape); + + GeometryAtom ga = getGeomAtom(shape); + + // Add the geometry atom for this shape to the nodeList + s.nodeList.add(ga); + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(ga, Targets.GEO_TARGETS); + } + } + + + for (int k = 0; k < msList.size(); k++) { + Shape3DRetained sh = (Shape3DRetained)msList.get(k); + if (appearance != null) { + synchronized(appearance.liveStateLock) { + if (k == 0) { + appearance.clearLive(s.refCount); + } + appearance.removeAMirrorUser(sh); + } + } + if (geometryList != null) { + for(gaCnt=0; gaCnt<geometryList.size(); gaCnt++) { + geometry = (GeometryRetained) geometryList.get(gaCnt); + if(geometry != null) { + synchronized(geometry.liveStateLock) { + if (k == 0) { + geometry.clearLive(s.refCount); + } + geometry.removeUser(sh); + } + } + } + } + } + + s.notifyThreads |= (J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_TRANSFORM | + // This is used to clear the scope info + // of all the mirror shapes + J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_RENDER); + + if (!source.isLive()) { + // Clear the mirror scoping info + // Remove all the fogs + for (i = 0; i < numfogs; i++) + fogs[i] = null; + numfogs = 0; + + // Remove all the modelClips + for (i = 0; i < numModelClips; i++) + modelClips[i] = null; + numModelClips = 0; + + // Remove all the lights + for (i = 0; i < numlights; i++) + lights[i] = null; + numlights = 0; + + // Remove all the al app + for (i = 0; i < numAltApps; i++) + altApps[i] = null; + numAltApps = 0; + } + } + + boolean isStatic() { + if (source.getCapability(Shape3D.ALLOW_APPEARANCE_WRITE) || + source.getCapability(Shape3D.ALLOW_GEOMETRY_WRITE) || + source.getCapability(Shape3D.ALLOW_APPEARANCE_OVERRIDE_WRITE)) { + return false; + } else { + return true; + } + } + + boolean staticXformCanBeApplied() { + + // static xform can be applied if + // . shape is not pickable or collidable + // . geometry is not being shared by more than one shape nodes + // . geometry will be put in display list + // . geometry is not readable + + // no static xform if shape is pickable or collidable because + // otherwise the static xform will have to be applied to the + // currentLocalToVworld in the intersect test, it will then + // be more costly and really beat the purpose of eliminating + // the static transform group + if (isPickable || isCollidable || + source.getCapability(Shape3D.ALLOW_PICKABLE_WRITE) || + source.getCapability(Shape3D.ALLOW_COLLIDABLE_WRITE)) { + return false; + } + + if (appearance != null && + (appearance.transparencyAttributes != null && appearance.transparencyAttributes.transparencyMode != TransparencyAttributes.NONE)) + return false; + + GeometryRetained geo; + boolean alphaEditable; + + for (int i=0; i<geometryList.size(); i++) { + geo = (GeometryRetained) geometryList.get(i); + if (geo != null) { + if (geo.refCnt > 1) { + return false; + } + alphaEditable = isAlphaEditable(geo); + if (geo instanceof GeometryArrayRetained) { + geo.isEditable = !((GeometryArrayRetained)geo).isWriteStatic(); + + // TODO: for now if vertex data can be returned, then + // don't apply static transform + if (geo.source.getCapability( + GeometryArray.ALLOW_COORDINATE_READ) || + geo.source.getCapability( + GeometryArray.ALLOW_NORMAL_READ)) + return false; + + } + + if (!geo.canBeInDisplayList(alphaEditable)) { + return false; + } + } + } + return true; + } + + + void compile(CompileState compState) { + AppearanceRetained newApp; + + super.compile(compState); + + if (isStatic() && staticXformCanBeApplied()) { + mergeFlag = SceneGraphObjectRetained.MERGE; + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numShapesWStaticTG++; + } + } else + { + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + compState.keepTG = true; + } + + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numShapes++; + } + + if (appearance != null) { + appearance.compile(compState); + // Non-static apperanace can still be compiled, since in compile + // state we will be grouping all shapes that have same appearance + // so, when the appearance changes, all the shapes will be affected + // For non-static appearances, we don't get an equivalent appearance + // from the compile state + if (appearance.isStatic()) { + newApp = compState.getAppearance(appearance); + appearance = newApp; + } + } + + for (int i = 0; i < geometryList.size(); i++) { + GeometryRetained geo = (GeometryRetained)geometryList.get(i); + if (geo != null) + geo.compile(compState); + } + + } + + void merge(CompileState compState) { + + + if (mergeFlag == SceneGraphObjectRetained.DONT_MERGE) { + + // no need to save the staticTransform here + + TransformGroupRetained saveStaticTransform = + compState.staticTransform; + compState.staticTransform = null; + super.merge(compState); + compState.staticTransform = saveStaticTransform; + } else { + super.merge(compState); + } + + if (shapeIsMergeable(compState)) { + compState.addShape(this); + } + } + + + boolean shapeIsMergeable(CompileState compState) { + boolean mergeable = true; + AppearanceRetained newApp; + int i; + + GeometryRetained geometry = null; + int index = 0; + i = 0; + /* + if (isPickable) + return false; + */ + + // For now, don't merge if the shape has static transform + if (staticTransform != null) + return false; + + // If this shape's to be immediate parent is orderedGroup or a switchNode + // this shape is not mergerable + if (parent instanceof OrderedGroupRetained || + parent instanceof SwitchRetained) + return false; + + // Get the first geometry that is non-null + while (geometry == null && index < geometryList.size()) { + geometry = (GeometryRetained) geometryList.get(index); + index++; + } + + if (!(geometry instanceof GeometryArrayRetained)) { + return false; + } + + GeometryArrayRetained firstGeo = (GeometryArrayRetained) geometry; + + for(i=index; (i<geometryList.size() && mergeable); i++) { + geometry = (GeometryRetained) geometryList.get(i); + if (geometry != null) { + GeometryArrayRetained geo = (GeometryArrayRetained)geometry; + + if (! geo.isWriteStatic()) + mergeable = false; + + if (geo.vertexFormat != firstGeo.vertexFormat) + mergeable = false; + + + } + } + + // For now, turn off lots of capability bits + if (source.getCapability(Shape3D.ALLOW_COLLISION_BOUNDS_WRITE) || + source.getCapability(Shape3D.ALLOW_APPEARANCE_WRITE) || + source.getCapability(Shape3D.ALLOW_APPEARANCE_OVERRIDE_WRITE) || + source.getCapability(Shape3D.ALLOW_AUTO_COMPUTE_BOUNDS_WRITE) || + source.getCapability(Shape3D.ALLOW_BOUNDS_WRITE) || + source.getCapability(Shape3D.ALLOW_COLLIDABLE_WRITE) || + source.getCapability(Shape3D.ALLOW_PICKABLE_WRITE) || + source.getCapability(Shape3D.ALLOW_GEOMETRY_WRITE)) { + mergeable = false; + } + + return mergeable; + + } + + + void getMirrorObjects( ArrayList list, HashKey k) { + Shape3DRetained ms; + if (inSharedGroup) { + if (k.count == 0) { + // System.out.println("===> CAN NEVER BE TRUE"); + return; + } + else { + ms = getMirrorShape(k); + } + } + else { + ms = (Shape3DRetained)mirrorShape3D.get(0); + } + + list.add(getGeomAtom(ms)); + + } + + + // Called on the mirror Object + void addLight(LightRetained light) { + LightRetained[] newlights; + int i, n; + Shape3DRetained ms; + + if (lights == null) { + lights = new LightRetained[10]; + } + else if (lights.length == numlights) { + newlights = new LightRetained[numlights*2]; + for (i=0; i<numlights; i++) { + newlights[i] = lights[i]; + } + lights = newlights; + } + lights[numlights] = light; + numlights++; + } + + // called on the mirror object + void removeLight(LightRetained light) { + int i; + + for (i=0; i<numlights; i++) { + if (lights[i] == light) { + lights[i] = null; + break; + } + } + + // Shift everyone down one. + for (i++; i<numlights; i++) { + lights[i-1] = lights[i]; + } + numlights--; + } + + // Called on the mirror object + void addFog(FogRetained fog) { + FogRetained[] newfogs; + int i; + + if (fogs == null) { + fogs = new FogRetained[10]; + } + else if (fogs.length == numfogs) { + newfogs = new FogRetained[numfogs*2]; + for (i=0; i<numfogs; i++) { + newfogs[i] = fogs[i]; + } + fogs = newfogs; + } + fogs[numfogs] = fog; + numfogs++; + } + + // called on the mirror object + void removeFog(FogRetained fog) { + int i; + + for (i=0; i<numfogs; i++) { + if (fogs[i] == fog) { + fogs[i] = null; + break; + } + } + + // Shift everyone down one. + for (i++; i<numfogs; i++) { + fogs[i-1] = fogs[i]; + } + numfogs--; + + } + + // Called on the mirror object + void addModelClip(ModelClipRetained modelClip) { + ModelClipRetained[] newModelClips; + int i; + + + if (modelClips == null) { + modelClips = new ModelClipRetained[10]; + } + else if (modelClips.length == numModelClips) { + newModelClips = new ModelClipRetained[numModelClips*2]; + for (i=0; i<numModelClips; i++) { + newModelClips[i] = modelClips[i]; + } + modelClips = newModelClips; + } + modelClips[numModelClips] = modelClip; + numModelClips++; + } + + // called on the mirror object + void removeModelClip(ModelClipRetained modelClip) { + int i; + + for (i=0; i<numModelClips; i++) { + if (modelClips[i] == modelClip) { + modelClips[i] = null; + break; + } + } + + // Shift everyone down one. + for (i++; i<numModelClips; i++) { + modelClips[i-1] = modelClips[i]; + } + numModelClips--; + + } + + // Called on the mirror object + void addAltApp(AlternateAppearanceRetained aApp) { + AlternateAppearanceRetained[] newAltApps; + int i; + if (altApps == null) { + altApps = new AlternateAppearanceRetained[10]; + } + else if (altApps.length == numAltApps) { + newAltApps = new AlternateAppearanceRetained[numAltApps*2]; + for (i=0; i<numAltApps; i++) { + newAltApps[i] = altApps[i]; + } + altApps = newAltApps; + } + altApps[numAltApps] = aApp; + numAltApps++; + } + + // called on the mirror object + void removeAltApp(AlternateAppearanceRetained aApp) { + int i; + + for (i=0; i<numAltApps; i++) { + if (altApps[i] == aApp) { + altApps[i] = null; + break; + } + } + + // Shift everyone down one. + for (i++; i<numAltApps; i++) { + altApps[i-1] = altApps[i]; + } + numAltApps--; + + } + + + + void updatePickable(HashKey keys[], boolean pick[]) { + super.updatePickable(keys, pick); + Shape3DRetained shape; + + if (!inSharedGroup) { + shape = (Shape3DRetained) mirrorShape3D.get(0); + shape.isPickable = pick[0]; + } else { + int size = mirrorShape3D.size(); + for (int j=0; j< keys.length; j++) { + for (int i=0; i < size; i++) { + shape = (Shape3DRetained) mirrorShape3D.get(i); + if (keys[j].equals(shape.key)) { + shape.isPickable = pick[j]; + break; + } + + } + } + } + } + + + void updateCollidable(HashKey keys[], boolean collide[]) { + super.updateCollidable(keys, collide); + Shape3DRetained shape; + + if (!inSharedGroup) { + shape = (Shape3DRetained) mirrorShape3D.get(0); + shape.isCollidable = collide[0]; + } else { + int size = mirrorShape3D.size(); + for (int j=0; j< keys.length; j++) { + for (int i=0; i < size; i++) { + shape = (Shape3DRetained) mirrorShape3D.get(i); + if (keys[j].equals(shape.key)) { + shape.isCollidable = collide[j]; + break; + } + + } + } + } + } + + + + + // New 1.2.1 code .... + + // Remove the old geometry atoms and reInsert + // the new geometry atoms and update the transform + // target list + + private void sendDataChangedMessage( GeometryRetained newGeom ) { + + int i, j, gaCnt; + GeometryAtom[] newGAArray = null; + GeometryAtom[] oldGAArray = null; + GeometryAtom[] newGeometryAtoms = null; + int geometryCnt = 0; + GeometryRetained geometry = null; + + int s3dMSize = mirrorShape3D.size(); + + if(s3dMSize < 1) + return; + + Shape3DRetained mS3d = (Shape3DRetained) mirrorShape3D.get(0); + + mS3d.mirrorShape3DLock.writeLock(); + + GeometryAtom oldGA = mS3d.geomAtom; + + GeometryAtom newGA = new GeometryAtom(); + + if(newGeom != null) { + newGeom.addUser(mS3d); + } + + int gSize = geometryList.size(); + + for(i=0; i<gSize; i++) { + geometry = (GeometryRetained) geometryList.get(i); + if(geometry != null) { + newGA.geoType = geometry.geoType; + newGA.alphaEditable = mS3d.isAlphaEditable(geometry); + break; + } + } + + if((geometry != null) && + (geometry.geoType == GeometryRetained.GEO_TYPE_TEXT3D)) { + + for(i = 0; i<gSize; i++) { + geometry = (GeometryRetained) geometryList.get(i); + if(geometry != null) { + Text3DRetained tempT3d = (Text3DRetained)geometry; + geometryCnt += tempT3d.numChars; + } + else { + // This is slightly wasteful, but not quite worth to optimize yet. + geometryCnt++; + } + } + newGA.geometryArray = new GeometryRetained[geometryCnt]; + newGA.lastLocalTransformArray = new Transform3D[geometryCnt]; + // Reset geometryCnt; + geometryCnt = 0; + + } + else { + newGA.geometryArray = new GeometryRetained[gSize]; + } + + newGA.locale = mS3d.locale; + newGA.visible = visible; + newGA.source = mS3d; + + + for(gaCnt = 0; gaCnt<gSize; gaCnt++) { + geometry = (GeometryRetained) geometryList.get(gaCnt); + if(geometry == null) { + newGA.geometryArray[geometryCnt++] = null; + } + else { + if (geometry.geoType == GeometryRetained.GEO_TYPE_TEXT3D) { + Text3DRetained t = (Text3DRetained)geometry; + GeometryRetained geo; + for (i=0; i<t.numChars; i++, geometryCnt++) { + geo = t.geometryList[i]; + if (geo!= null) { + newGA.geometryArray[geometryCnt] = geo; + newGA.lastLocalTransformArray[geometryCnt] = + t.charTransforms[i]; + + } else { + newGA.geometryArray[geometryCnt] = null; + newGA.lastLocalTransformArray[geometryCnt] = null; + } + + } + + } else { + newGA.geometryArray[geometryCnt++] = geometry; + } + } + } + + oldGAArray = new GeometryAtom[s3dMSize]; + newGAArray = new GeometryAtom[s3dMSize]; + oldGAArray[0] = oldGA; + newGAArray[0] = newGA; + + mS3d.geomAtom = newGA; + mS3d.mirrorShape3DLock.writeUnlock(); + + // ..... clone the rest of mirrorS3D's GA with the above newGA, but modify + // its source. + + for (i = 1; i < s3dMSize; i++) { + mS3d = (Shape3DRetained) mirrorShape3D.get(i); + mS3d.mirrorShape3DLock.writeLock(); + oldGA = mS3d.geomAtom; + newGA = new GeometryAtom(); + + if(newGeom != null) { + newGeom.addUser(mS3d); + } + + newGA.geoType = newGAArray[0].geoType; + newGA.locale = mS3d.locale; + newGA.visible = visible; + newGA.source = mS3d; + newGA.alphaEditable = newGAArray[0].alphaEditable; + + newGA.geometryArray = new GeometryRetained[newGAArray[0].geometryArray.length]; + for(j=0; j<newGA.geometryArray.length; j++) { + newGA.geometryArray[j] = newGAArray[0].geometryArray[j]; + } + + oldGAArray[i] = oldGA; + newGAArray[i] = newGA; + + mS3d.geomAtom = newGA; + mS3d.mirrorShape3DLock.writeUnlock(); + } + + TargetsInterface ti = + ((GroupRetained)parent).getClosestTargetsInterface( + TargetsInterface.TRANSFORM_TARGETS); + CachedTargets[] newCtArr = null; + + if (ti != null) { + CachedTargets ct; + newCtArr = new CachedTargets[s3dMSize]; + + for (i=0; i<s3dMSize; i++) { + + ct = ti.getCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, i, -1); + if (ct != null) { + newCtArr[i] = new CachedTargets(); + newCtArr[i].copy(ct); + newCtArr[i].replace(oldGAArray[i], newGAArray[i], + Targets.GEO_TARGETS); + } else { + newCtArr[i] = null; + } + } + ti.resetCachedTargets(TargetsInterface.TRANSFORM_TARGETS, + newCtArr, -1); + } + + + J3dMessage changeMessage = VirtualUniverse.mc.getMessage(); + changeMessage.type = J3dMessage.SHAPE3D_CHANGED; + // Who to send this message to ? + changeMessage.threads = J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY; + changeMessage.universe = universe; + changeMessage.args[0] = this; + changeMessage.args[1] = new Integer(GEOMETRY_CHANGED); + changeMessage.args[2] = oldGAArray; + changeMessage.args[3] = newGAArray; + if (ti != null) { + changeMessage.args[4] = ti; + changeMessage.args[5] = newCtArr; + } + if (boundsAutoCompute) { + getCombineBounds((BoundingBox)localBounds); + } + VirtualUniverse.mc.processMessage(changeMessage); + + } + + + // ********** End of New 1.2.1 code .... + + + + + + Shape3DRetained getMirrorShape(SceneGraphPath path) { + if (!inSharedGroup) { + return (Shape3DRetained) mirrorShape3D.get(0); + } + HashKey key = new HashKey(""); + path.getHashKey(key); + return getMirrorShape(key); + } + + Shape3DRetained getMirrorShape(HashKey key) { + if (key == null) { + return (Shape3DRetained) mirrorShape3D.get(0); + } else { + int i = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + + if (i>=0) { + return (Shape3DRetained) mirrorShape3D.get(i); + } + } + // Not possible + throw new RuntimeException("Shape3DRetained: MirrorShape Not found!"); + } + + void setBoundsAutoCompute(boolean autoCompute) { + GeometryRetained geometry; + if (autoCompute != boundsAutoCompute) { + if (autoCompute) { + // localBounds may not have been set to bbox + localBounds = new BoundingBox((BoundingBox) null); + if (source.isLive() && geometryList != null) { + int size = geometryList.size()*mirrorShape3D.size(); + for (int i=0; i<size; i++) { + geometry = (GeometryRetained) geometryList.get(i); + geometry.incrComputeGeoBounds(); + } + } + + getCombineBounds((BoundingBox)localBounds); + } + else { + if (source.isLive() && geometryList != null) { + int size = geometryList.size()*mirrorShape3D.size(); + for (int i=0; i<size; i++) { + geometry = (GeometryRetained) geometryList.get(i); + geometry.decrComputeGeoBounds(); + } + + } + } + super.setBoundsAutoCompute(autoCompute); + if (source.isLive()) { + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.BOUNDS_AUTO_COMPUTE_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_RENDER; + message.universe = universe; + message.args[0] = getGeomAtomsArray(mirrorShape3D); + // no need to clone localBounds + message.args[1] = localBounds; + VirtualUniverse.mc.processMessage(message); + } + } + } + // This method is called when coordinates of a geometry in the geometrylist + // changed and autoBoundsCompute is true + + void updateBounds() { + localBounds = new BoundingBox((BoundingBox) null); + getCombineBounds((BoundingBox)localBounds); + synchronized(mirrorShape3D) { + if (source.isLive()) { + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.BOUNDS_AUTO_COMPUTE_CHANGED; + message.threads = J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_RENDER; + message.universe = universe; + message.args[0] = getGeomAtomsArray(mirrorShape3D); + // no need to clone localBounds + message.args[1] = localBounds; + VirtualUniverse.mc.processMessage(message); + } + } + } + + boolean allowIntersect() { + GeometryRetained ga = null; + + for(int i=0; i<geometryList.size(); i++) { + ga = (GeometryRetained) geometryList.get(i); + if(ga != null) + if (!ga.source.getCapability(Geometry.ALLOW_INTERSECT)) { + return false; + } + } + return true; + } + boolean intersectGeometryList(Shape3DRetained otherShape) { + GeometryRetained geom1, geom2; + ArrayList gaList = otherShape.geometryList; + int gaSize = gaList.size(); + Transform3D otherLocalToVworld = otherShape.getCurrentLocalToVworld(); + Transform3D thisLocalToVworld = getCurrentLocalToVworld(); + View views = null; + int primaryViewIdx = -1; + + + if (this instanceof OrientedShape3DRetained) { + primaryViewIdx = getPrimaryViewIdx(); + thisLocalToVworld.mul(((OrientedShape3DRetained)this). + getOrientedTransform(primaryViewIdx)); + } + + if (otherShape instanceof OrientedShape3DRetained) { + if (primaryViewIdx < 0) { + primaryViewIdx = getPrimaryViewIdx(); + } + otherLocalToVworld.mul(((OrientedShape3DRetained)otherShape). + getOrientedTransform(primaryViewIdx)); + } + + for (int i=geometryList.size()-1; i >=0; i--) { + geom1 = (GeometryRetained) geometryList.get(i); + if (geom1 != null) { + for (int j=gaSize-1; j >=0; j--) { + geom2 = (GeometryRetained) gaList.get(j); + if ((geom2 != null) && + geom1.intersect(thisLocalToVworld, + otherLocalToVworld, geom2)) { + return true; + } + } + } + } + + return false; + } + + boolean intersectGeometryList(Transform3D thisLocalToVworld, Bounds targetBound) { + + GeometryRetained geometry; + + if (this instanceof OrientedShape3DRetained) { + Transform3D orientedTransform = + ((OrientedShape3DRetained)this). + getOrientedTransform(getPrimaryViewIdx()); + thisLocalToVworld.mul(orientedTransform); + } + + for (int i=geometryList.size() - 1; i >=0; i--) { + geometry = (GeometryRetained) geometryList.get(i); + if ((geometry != null) && + geometry.intersect(thisLocalToVworld, targetBound)) { + return true; + } + } + + return false; + + } + + + /** + * This initialize the mirror shape to reflect the state of the + * real Morph. + */ + void initMirrorShape3D(SetLiveState s, MorphRetained morph, int index) { + + GeometryRetained geometry; + + GeometryAtom[] newGeometryAtoms = null; + + universe = morph.universe; + inSharedGroup = morph.inSharedGroup; + inBackgroundGroup = morph.inBackgroundGroup; + geometryBackground = morph.geometryBackground; + parent = morph.parent; + locale = morph.locale; + + OrderedPath op = (OrderedPath)s.orderedPaths.get(index); + if (op.pathElements.size() == 0) { + orderedPath = null; + } else { + orderedPath = op; + } + + staticTransform = morph.staticTransform; + if (morph.boundsAutoCompute) { + localBounds.set(morph.localBounds); + } + bounds = localBounds; + vwcBounds = new BoundingBox((BoundingBox) null); + vwcBounds.transform(bounds, getCurrentLocalToVworld(0)); + + if (morph.collisionBound == null) { + collisionBound = null; + collisionVwcBound = vwcBounds; + } else { + collisionBound = morph.collisionBound; + collisionVwcBound = (Bounds)collisionBound.clone(); + collisionVwcBound.transform(getCurrentLocalToVworld(0)); + } + + appearanceOverrideEnable = morph.appearanceOverrideEnable; + + // mga is the final geometry we're interested. + geometryList = new ArrayList(1); + geometryList.add((GeometryArrayRetained)morph.morphedGeometryArray.retained); + + GeometryAtom gAtom = new GeometryAtom(); + gAtom.geometryArray = new GeometryRetained[1]; + + gAtom.locale = locale; + gAtom.visible = morph.visible; + gAtom.source = this; + + geometry = (GeometryRetained) geometryList.get(0); + + if(geometry ==null) { + gAtom.geometryArray[0] = null; + } else { + gAtom.geometryArray[0] = (GeometryArrayRetained)morph. + morphedGeometryArray.retained; + gAtom.geoType = gAtom.geometryArray[0].geoType; + } + geomAtom = gAtom; + + // Assign the parent of this mirror shape node + sourceNode = morph; + } + + // geometries in morph object is modified, update the geometry + // list in the mirror shapes and the geometry array in the geometry atom + + void setMorphGeometry(Geometry geometry, ArrayList mirrorShapes) { + GeometryAtom oldGA, newGA; + Shape3DRetained ms; + TransformGroupRetained tg; + int nMirrorShapes = mirrorShapes.size(); + int i; + + GeometryAtom oldGAArray[] = new GeometryAtom[nMirrorShapes]; + GeometryAtom newGAArray[] = new GeometryAtom[nMirrorShapes]; + + + for (i = 0; i < nMirrorShapes; i++) { + ms = (Shape3DRetained) mirrorShapes.get(i); + + oldGA = Shape3DRetained.getGeomAtom(ms); + + ms.geometryList = new ArrayList(1); + ms.geometryList.add((GeometryArrayRetained)geometry.retained); + + newGA = new GeometryAtom(); + newGA.geometryArray = new GeometryRetained[1]; + + if (geometry ==null) { + newGA.geometryArray[0] = null; + } else { + newGA.geometryArray[0] = + (GeometryArrayRetained)geometry.retained; + newGA.geoType = newGA.geometryArray[0].geoType; + } + + newGA.locale = locale; + newGA.visible = oldGA.visible; + newGA.source = this; + + oldGAArray[i] = oldGA; + newGAArray[i] = newGA; + + Shape3DRetained.setGeomAtom(ms, newGA); + } + + TargetsInterface ti = + ((GroupRetained)parent).getClosestTargetsInterface( + TargetsInterface.TRANSFORM_TARGETS); + CachedTargets[] newCtArr = null; + + if (ti != null) { + CachedTargets ct; + newCtArr = new CachedTargets[nMirrorShapes]; + + for (i=0; i<nMirrorShapes; i++) { + + ct = ti.getCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, i, -1); + if (ct != null) { + newCtArr[i] = new CachedTargets(); + newCtArr[i].copy(ct); + newCtArr[i].replace(oldGAArray[i], newGAArray[i], + Targets.GEO_TARGETS); + } else { + newCtArr[i] = null; + } + } + } + + // send a Shape GEOMETRY_CHANGED message for all geometry atoms + + J3dMessage changeMessage = VirtualUniverse.mc.getMessage(); + changeMessage.type = J3dMessage.SHAPE3D_CHANGED; + changeMessage.threads = J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY; + changeMessage.universe = universe; + changeMessage.args[0] = this; + changeMessage.args[1] = new Integer(GEOMETRY_CHANGED); + changeMessage.args[2] = oldGAArray; + changeMessage.args[3] = newGAArray; + if (ti != null) { + changeMessage.args[4] = ti; + changeMessage.args[5] = newCtArr; + } + VirtualUniverse.mc.processMessage(changeMessage); + } + + + /** + * Return an array of geometry atoms belongs to userList. + * The input is an arraylist of Shape3DRetained type. + * This is used to send a message of the snapshot of the + * geometry atoms that are affected by this change. + */ + final static GeometryAtom[] getGeomAtomsArray(ArrayList userList) { + Shape3DRetained ms = null; + GeometryAtom[] gaArr = null; + int size, nullCnt=0, i, j; + + synchronized(userList) { + size = userList.size(); + gaArr = new GeometryAtom[size]; + for (i = 0; i < size; i++) { + ms = (Shape3DRetained) userList.get(i); + ms.mirrorShape3DLock.readLock(); + if(ms.geomAtom == null) { + nullCnt++; + } + gaArr[i] = ms.geomAtom; + ms.mirrorShape3DLock.readUnlock(); + } + } + if(nullCnt == 0) { + return gaArr; + } + else if(nullCnt == size) { + return null; + } + else { + GeometryAtom[] newGaArr = new GeometryAtom[size - nullCnt]; + + for (i=0, j=0; i < size; i++) { + if(gaArr[i] != null) { + newGaArr[j++] = gaArr[i]; + } + } + return newGaArr; + } + } + + /** + * Return a list of geometry atoms belongs to userList and places a list of + * universe found in userList in univList. + * The input is an array of Shape3DRetained type. + * univList is assume to be empty. + * This is used to send a message of the snapshot of the + * geometry atoms that are affected by this change. + */ + final static ArrayList getGeomAtomsList(ArrayList userList, ArrayList univList) { + ArrayList listPerUniverse = new ArrayList(); + int index; + ArrayList gaList = null; + Shape3DRetained ms = null; + boolean moreThanOneUniv = false; + VirtualUniverse firstFndUniv = null; + + synchronized(userList) { + for (int i = userList.size()-1; i >=0; i--) { + ms = (Shape3DRetained) userList.get(i); + + if(moreThanOneUniv == false) { + if(firstFndUniv == null) { + firstFndUniv = ms.universe; + univList.add(ms.universe); + + gaList = new ArrayList(); + listPerUniverse.add(gaList); + } + else if(firstFndUniv != ms.universe) { + moreThanOneUniv = true; + univList.add(ms.universe); + gaList = new ArrayList(); + listPerUniverse.add(gaList); + } + } + else { + index = univList.indexOf(ms.universe); + if (index < 0) { + univList.add(ms.universe); + gaList = new ArrayList(); + listPerUniverse.add(gaList); + } + else { + gaList = (ArrayList) listPerUniverse.get(index); + } + } + + + ms.mirrorShape3DLock.readLock(); + + if(ms.geomAtom != null) { + gaList.add(ms.geomAtom); + } + ms.mirrorShape3DLock.readUnlock(); + + } + } + return listPerUniverse; + } + + final static GeometryAtom getGeomAtom(Shape3DRetained shape) { + GeometryAtom ga; + + shape.mirrorShape3DLock.readLock(); + ga = shape.geomAtom; + shape.mirrorShape3DLock.readUnlock(); + + return ga; + } + + final static void setGeomAtom(Shape3DRetained shape, GeometryAtom ga) { + shape.mirrorShape3DLock.writeLock(); + shape.geomAtom = ga; + shape.mirrorShape3DLock.writeUnlock(); + } + + + // Alpha is editable due to the appearance + boolean isAlphaEditable(GeometryRetained geo) { + + boolean alphaEditable = false; + + if (appearanceOverrideEnable) { + alphaEditable = true; + } else if (geo != null && + appearance != null) { + + AppearanceRetained app = appearance; + + if (source.getCapability( + Shape3D.ALLOW_APPEARANCE_WRITE) || + source.getCapability( + Shape3D.ALLOW_APPEARANCE_OVERRIDE_WRITE) || + + app.source.getCapability( + Appearance.ALLOW_RENDERING_ATTRIBUTES_WRITE) || + + app.source.getCapability( + Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE) || + + (app.renderingAttributes != null && + (app.renderingAttributes.source.getCapability( + RenderingAttributes.ALLOW_ALPHA_TEST_FUNCTION_WRITE) || + app.renderingAttributes.source.getCapability( + RenderingAttributes.ALLOW_IGNORE_VERTEX_COLORS_WRITE))) || + + (app.transparencyAttributes != null && + (app.transparencyAttributes.source.getCapability( + TransparencyAttributes.ALLOW_MODE_WRITE) || + app.transparencyAttributes.source.getCapability( + TransparencyAttributes.ALLOW_VALUE_WRITE)))) { + + alphaEditable = true; + + } else if (geo instanceof GeometryArrayRetained && + (app.source.getCapability( + Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE) || + + (app.textureAttributes != null && + app.textureAttributes.source.getCapability( + TextureAttributes.ALLOW_MODE_WRITE)))) { + + alphaEditable = true; + + } else if (geo instanceof RasterRetained) { + if ((((RasterRetained)geo).type & Raster.RASTER_COLOR) != +0 + && ((RasterRetained)geo).source.getCapability( + Raster.ALLOW_IMAGE_WRITE)) { + + alphaEditable = true; + } + } + } + return alphaEditable; + } + + // getCombineBounds is faster than computeCombineBounds since it + // does not recompute the geometry.geoBounds + void getCombineBounds(BoundingBox bounds) { + + if(geometryList != null) { + BoundingBox bbox = null; + GeometryRetained geometry; + + if (staticTransform != null) { + bbox = new BoundingBox((BoundingBox) null); + } + + synchronized(bounds) { + bounds.setLower( 1.0, 1.0, 1.0); + bounds.setUpper(-1.0,-1.0,-1.0); + for(int i=0; i<geometryList.size(); i++) { + geometry = (GeometryRetained) geometryList.get(i); + if ((geometry != null) && + (geometry.geoType != GeometryRetained.GEO_TYPE_NONE)) { + synchronized(geometry.geoBounds) { + if (staticTransform != null) { + bbox.set(geometry.geoBounds); + bbox.transform(staticTransform.transform); + bounds.combine((Bounds)bbox); + } else { + bounds.combine((Bounds)geometry.geoBounds); + } + } + } + } + } + + // System.out.println("Shape3DRetained - getCombineBounds"); + // Enlarge boundingBox to the "minmium bounds" that encompasses all possible + // orientation. + if (this instanceof OrientedShape3DRetained) { + double maxVal = Math.abs(bounds.lower.x); + double tempVal = Math.abs(bounds.upper.x); + if(tempVal > maxVal) + maxVal = tempVal; + tempVal = Math.abs(bounds.lower.y); + if(tempVal > maxVal) + maxVal = tempVal; + tempVal = Math.abs(bounds.upper.y); + if(tempVal > maxVal) + maxVal = tempVal; + tempVal = Math.abs(bounds.lower.z); + if(tempVal > maxVal) + maxVal = tempVal; + tempVal = Math.abs(bounds.upper.z); + if(tempVal > maxVal) + maxVal = tempVal; + + // System.out.println("Shape3DRetained - bounds (Before) " + bounds); + bounds.setLower(-maxVal, -maxVal, -maxVal); + bounds.setUpper(maxVal, maxVal, maxVal); + // System.out.println("Shape3DRetained - bounds (After) " + bounds); + } + + } + } + + + boolean isEquivalent(Shape3DRetained shape) { + if (this.appearance != shape.appearance || + // Scoping info should be same since they are under same group + this.appearanceOverrideEnable != shape.appearanceOverrideEnable || + this.isPickable != shape.isPickable || + this.isCollidable != shape.isCollidable) { + + return false; + } + if (this.boundsAutoCompute) { + if (!shape.boundsAutoCompute) + return false; + } + else { + // If bounds autoCompute is false + // Then check if both bounds are equal + if (this.localBounds != null) { + if (shape.localBounds != null) { + return this.localBounds.equals(shape.localBounds); + } + } + else if (shape.localBounds != null) { + return false; + } + } + if (collisionBound != null) { + if (shape.collisionBound == null) + return false; + else + return collisionBound.equals(shape.collisionBound); + } + else if (shape.collisionBound != null) + return false; + + return true; + } + + // Bounds can only be set after the geometry is setLived, so has to be done + // here, if we are not using switchVwcBounds + void initializeGAtom(Shape3DRetained ms) { + int i, gaCnt; + int geometryCnt = 0; + int gSize = geometryList.size(); + GeometryRetained geometry = null; + + ms.bounds = localBounds; + ms.vwcBounds = new BoundingBox((BoundingBox) null); + ms.vwcBounds.transform(ms.bounds, ms.getCurrentLocalToVworld(0)); + + if (collisionBound == null) { + ms.collisionBound = null; + ms.collisionVwcBound = ms.vwcBounds; + } else { + ms.collisionBound = collisionBound; + ms.collisionVwcBound = (Bounds)ms.collisionBound.clone(); + ms.collisionVwcBound.transform(ms.getCurrentLocalToVworld(0)); + } + GeometryAtom gAtom = new GeometryAtom(); + for(gaCnt=0; gaCnt<gSize; gaCnt++) { + geometry = (GeometryRetained) geometryList.get(gaCnt); + if(geometry != null) { + gAtom.geoType = geometry.geoType; + gAtom.alphaEditable = ms.isAlphaEditable(geometry); + break; + } + } + if((geometry != null) && + (geometry.geoType == GeometryRetained.GEO_TYPE_TEXT3D)) { + + for(gaCnt = 0; gaCnt<gSize; gaCnt++) { + geometry = (GeometryRetained) geometryList.get(gaCnt); + if(geometry != null) { + Text3DRetained tempT3d = (Text3DRetained)geometry; + geometryCnt += tempT3d.numChars; + } + else { + // This is slightly wasteful, but not quite worth to optimize yet. + geometryCnt++; + } + } + gAtom.geometryArray = new GeometryRetained[geometryCnt]; + gAtom.lastLocalTransformArray = new Transform3D[geometryCnt]; + // Reset geometryCnt; + geometryCnt = 0; + + } + else { + gAtom.geometryArray = new GeometryRetained[gSize]; + } + + + for(gaCnt = 0; gaCnt<geometryList.size(); gaCnt++) { + geometry = (GeometryRetained) geometryList.get(gaCnt); + if(geometry == null) { + gAtom.geometryArray[gaCnt] = null; + } + else { + if (geometry.geoType == GeometryRetained.GEO_TYPE_TEXT3D) { + Text3DRetained t = (Text3DRetained)geometry; + GeometryRetained geo; + for (i=0; i<t.numChars; i++, geometryCnt++) { + geo = t.geometryList[i]; + if (geo != null) { + gAtom.geometryArray[geometryCnt] = geo; + gAtom.lastLocalTransformArray[geometryCnt] = + t.charTransforms[i]; + } else { + gAtom.geometryArray[geometryCnt] = null; + gAtom.lastLocalTransformArray[geometryCnt] = null; + } + + } + + } else { + gAtom.geometryArray[gaCnt] = geometry; + } + } + } + gAtom.locale = ms.locale; + gAtom.visible = visible; + gAtom.source = ms; + ms.geomAtom = gAtom; + } + + // Check if geomRetained's class is equivalence with the geometry class. + void checkEquivalenceClass(Geometry geometry, int index) { + + if (geometry != null) { + for (int i=geometryList.size()-1; i >= 0; i--) { + GeometryRetained geomRetained = (GeometryRetained) geometryList.get(i); + if ((geomRetained != null) && + (index != i)) { // this geometry will replace + // current one so there is no need to check + if (!geomRetained.isEquivalenceClass((GeometryRetained)geometry.retained)) { + throw new IllegalArgumentException(J3dI18N.getString("Shape3DRetained5")); + } + break; + } + } + } + } + + int indexOfGeometry(Geometry geometry) { + if(geometry != null) + return geometryList.indexOf(geometry.retained); + else + return geometryList.indexOf(null); + } + + + // Removes the specified geometry from this Shape3DRetained's list of geometries + void removeGeometry(Geometry geometry) { + int ind = indexOfGeometry(geometry); + if(ind >= 0) + removeGeometry(ind); + } + + // Removes all the geometries from this node + void removeAllGeometries() { + int n = geometryList.size(); + + int i; + Shape3DRetained mShape; + GeometryRetained oldGeom = null; + + if (((Shape3D)this.source).isLive()) { + for(int index = n-1; index >= 0; index--) { + oldGeom = (GeometryRetained) (geometryList.get(index)); + if (oldGeom != null) { + oldGeom.clearLive(refCount); + oldGeom.decRefCnt(); + for (i=0; i<mirrorShape3D.size(); i++) { + mShape = (Shape3DRetained)mirrorShape3D.get(i); + oldGeom.removeUser(mShape); + } + } + geometryList.remove(index); + } + sendDataChangedMessage(null); + } else { + for(int index = n-1; index >= 0; index--) { + oldGeom = (GeometryRetained) (geometryList.get(index)); + if (oldGeom != null) { + oldGeom.decRefCnt(); + } + geometryList.remove(index); + } + } + } + + boolean willRemainOpaque(int geoType) { + if (appearance == null || + (appearance.isStatic() && + appearance.isOpaque(geoType))) { + return true; + } + else { + return false; + } + + } + + static Point3d getPoint3d() { + return (Point3d)FreeListManager.getObject(FreeListManager.POINT3D); + } + + static void freePoint3d(Point3d p) { + FreeListManager.freeObject(FreeListManager.POINT3D, p); + } + + void handleFrequencyChange(int bit) { + int mask = 0; + if (bit == Shape3D.ALLOW_GEOMETRY_WRITE) { + mask = GEOMETRY_CHANGED; + } + else if (bit == Shape3D.ALLOW_APPEARANCE_WRITE) { + mask = APPEARANCE_CHANGED; + } + else if (bit == Shape3D.ALLOW_APPEARANCE_OVERRIDE_WRITE) { + mask = APPEARANCEOVERRIDE_CHANGED; + } + if (mask != 0) { + if (source.getCapabilityIsFrequent(bit)) + changedFrequent |= mask; + else if (!source.isLive()) { + changedFrequent &= ~mask; + } + } + } + + + // Alpha is editable due to the appearance(Called on the MirrorShape3D) + boolean isAlphaFrequentlyEditable(GeometryRetained geo) { + + boolean alphaFrequentlyEditable = false; + if (appearanceOverrideEnable) { + alphaFrequentlyEditable = true; + } else if (geo != null && + appearance != null) { + AppearanceRetained app = appearance; + + if (((changedFrequent &(APPEARANCE_CHANGED|APPEARANCEOVERRIDE_CHANGED)) != 0)|| + ((app.changedFrequent &(AppearanceRetained.RENDERING|AppearanceRetained.TRANSPARENCY)) != 0) || + (app.renderingAttributes != null && + (((app.renderingAttributes.changedFrequent & (RenderingAttributesRetained.IGNORE_VCOLOR |RenderingAttributesRetained.ALPHA_TEST_FUNC)) != 0))) || + + (app.transparencyAttributes != null && + ((app.transparencyAttributes.changedFrequent != 0)))) { + + alphaFrequentlyEditable = true; + + } else if (geo instanceof GeometryArrayRetained && + ((app.changedFrequent & AppearanceRetained.TEXTURE_ATTR) != 0) || + (app.textureAttributes != null && + ((app.textureAttributes.changedFrequent & TextureAttributes.ALLOW_MODE_WRITE) != 0))) { + alphaFrequentlyEditable = true; + + } else if (geo instanceof RasterRetained) { + if (((((RasterRetained)geo).type & Raster.RASTER_COLOR) != +0) + && (((RasterRetained)geo).cachedChangedFrequent != 0)) { + + alphaFrequentlyEditable = true; + } + } + } + // System.out.println("changedFrequent="+changedFrequent+" sourceNode = "+sourceNode+" isAlphaFrequentlyEditable, = "+alphaFrequentlyEditable); + return alphaFrequentlyEditable; + } + + + int getPrimaryViewIdx() { + // To avoid MT-safe issues when using View, just clone it. + UnorderList viewList = VirtualUniverse.mc.cloneView(); + View views[] = (View []) viewList.toArray(false); + int size = viewList.arraySize(); + + for (int i=0; i < size; i++) { + if (views[i].primaryView) { + return views[i].viewIndex; + } + } + return 0; + } + + void searchGeometryAtoms(UnorderList list) { + list.add(getGeomAtom(getMirrorShape(key))); + } +} + diff --git a/src/classes/share/javax/media/j3d/SharedGroup.java b/src/classes/share/javax/media/j3d/SharedGroup.java new file mode 100644 index 0000000..832cec7 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SharedGroup.java @@ -0,0 +1,144 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The SharedGroup provides the ability to manipulate an + * instanced scene graph. + * A SharedGroup node allows multiple Link leaf nodes to share its + * subgraph according to the following semantics: + * <P><UL> + * <LI>A SharedGroup may be referenced by one or more Link leaf + * nodes. Any runtime changes to a node or component object in this + * shared subgraph affect all graphs that refer to this subgraph.</LI><P> + * + * <LI>A SharedGroup may be compiled by calling its compile method + * prior to being referenced by any Link leaf nodes.</LI><P> + * + * <LI>Only Link leaf nodes may refer to SharedGroup nodes. A + * SharedGroup node cannot have parents or be attached to a Locale.</LI><P> + * </UL> + * + * A shared subgraph may contain any group node, except an embedded + * SharedGroup node (SharedGroup nodes cannot have parents). However, + * only the following leaf nodes may appear in a shared subgraph: + * <P><UL> + * <LI>Light</LI> + * <LI>Link</LI> + * <LI>Morph</LI> + * <LI>Shape</LI> + * <LI>Sound</LI></UL><P> + * + * An IllegalSharingException is thrown if any of the following leaf nodes + * appear in a shared subgraph:<P> + * <UL> + * <LI>AlternateAppearance</LI> + * <LI>Background</LI> + * <LI>Behavior</LI> + * <LI>BoundingLeaf</LI> + * <LI>Clip</LI> + * <LI>Fog</LI> + * <LI>ModelClip</LI> + * <LI>Soundscape</LI> + * <LI>ViewPlatform</LI></UL> + * <P> + * + * @see IllegalSharingException + */ + +public class SharedGroup extends Group { + + /** + * Specifies that this SharedGroup node allows reading the + * list of links that refer to this node. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_LINK_READ = CapabilityBits.SHARED_GROUP_ALLOW_LINK_READ; + + + /** + * Constructs and initializes a new SharedGroup node object. + */ + public SharedGroup() { + } + + + /** + * Returns the list of Link nodes that refer to this SharedGroup node. + * @return An array of Link nodes that refer to this SharedGroup node. + * + * @since Java 3D 1.3 + */ + public Link[] getLinks() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_LINK_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("SharedGroup1")); + return ((SharedGroupRetained)retained).getLinks(); + } + + + /** + * Creates the retained mode SharedGroupRetained object that this + * SharedGroup component object will point to. + */ + void createRetained() { + this.retained = new SharedGroupRetained(); + this.retained.setSource(this); + } + + + /** + * Compiles the source SharedGroup associated with this object and + * creates and caches a compiled scene graph. + * @exception SceneGraphCycleException if there is a cycle in the + * scene graph + * @exception RestrictedAccessException if the method is called + * when this object is part of a live scene graph. + */ + public void compile() { + if (isLive()) { + throw new RestrictedAccessException(J3dI18N.getString("SharedGroup0")); + } + + if (isCompiled() == false) { + // will throw SceneGraphCycleException if there is a cycle + // in the scene graph + checkForCycle(); + + ((SharedGroupRetained)this.retained).compile(); + } + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + SharedGroup sg = new SharedGroup(); + sg.duplicateNode(this, forceDuplicate); + return sg; + } +} diff --git a/src/classes/share/javax/media/j3d/SharedGroupRetained.java b/src/classes/share/javax/media/j3d/SharedGroupRetained.java new file mode 100644 index 0000000..3651bc0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SharedGroupRetained.java @@ -0,0 +1,878 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +/** + * The SharedGroup node provides the ability to share a scene graph from + * multiple other scene graphs through the use of a Link node. + */ + +class SharedGroupRetained extends GroupRetained implements TargetsInterface { + + /* + static final int ILLEGAL_LEAF_MASK = + 1 << NodeRetained.BACKGROUND | + 1 << NodeRetained.BEHAVIOR | + 1 << NodeRetained.CLIP | + 1 << NodeRetained.LINEARFOG | + 1 << NodeRetained.EXPONENTIALFOG | + 1 << NodeRetained.SOUNDSCAPE | + 1 << NodeRetained.VIEWPLATFORM | + 1 << NodeRetained.BOUNDINGLEAF; + */ + + // The current list of child transform group nodes or link nodes + // under a transform group + ArrayList childTransformLinks = new ArrayList(1); + + // key which identifies a unique path from a + // locale to this transform group + HashKey currentKey = new HashKey(); + + // key which identifies a unique path from a locale to this switch link + HashKey switchKey = new HashKey(); + + /** + * The Shared Group Node's parent vector. + */ + Vector parents = new Vector(1); + + // J3d copy. + CachedTargets[] j3dCTs = null; + + // User copy. + CachedTargets[] cachedTargets = null; + + // A bitmask of the types in targets for transform targets + int localTargetThreads = 0; + // combined localTargetThreads and decendants' localTargetThreads + int targetThreads = 0; + + ArrayList switchStates = null; + + SharedGroupRetained() { + this.nodeType = NodeRetained.SHAREDGROUP; + } + + // SharedGroup specific data at SetLive. + void setAuxData(SetLiveState s, int index, int hkIndex) { + int i, size; + + // Group's setAuxData() + super.setAuxData(s, index, hkIndex); + + branchGroupPaths.add(hkIndex, s.branchGroupPaths.get(index)); + + if (orderedPaths == null) { + orderedPaths = new ArrayList(1); + } + orderedPaths.add(hkIndex, s.orderedPaths.get(index)); + + if (switchStates == null) { + switchStates = new ArrayList(1); + } + switchStates.add(hkIndex, s.switchStates.get(index)); + + if (viewLists == null) { + viewLists = new ArrayList(1); + } + // If there are some ViewSpecificGroups in the path above this SharedGroup + // System.out.println("====> hkIndex = "+hkIndex+" s.viewLists = "+s.viewLists); + if (s.viewLists != null) { + viewLists.add(hkIndex, s.viewLists.get(index)); + } + else { + viewLists.add(hkIndex, null); + } + + if (lights == null) { + lights = new ArrayList(1); + } + if (s.lights != null) { + lights.add(hkIndex, s.lights.get(index)); + } + else { + lights.add(hkIndex, null); + } + + if (fogs == null) { + fogs = new ArrayList(1); + } + if (s.fogs != null) { + fogs.add(hkIndex, s.fogs.get(index)); + } + else { + fogs.add(hkIndex, null); + } + + + if (modelClips == null) { + modelClips = new ArrayList(1); + } + if (s.modelClips != null) { + modelClips.add(hkIndex, s.modelClips.get(index)); + } + else { + modelClips.add(hkIndex, null); + } + + + if (altAppearances == null) { + altAppearances = new ArrayList(1); + } + if (s.altAppearances != null) { + altAppearances.add(hkIndex, s.altAppearances.get(index)); + } + else { + altAppearances.add(hkIndex, null); + } + } + + + void setNodeData(SetLiveState s) { + + // For inSharedGroup case. + int i, j, len; + + if (localToVworld == null) { + localToVworld = new Transform3D[s.keys.length][]; + localToVworldIndex = new int[s.keys.length][]; + localToVworldKeys = new HashKey[s.keys.length]; + cachedTargets = new CachedTargets[s.keys.length]; + len=0; + } + else { + + int newLen = localToVworld.length + s.keys.length; + + Transform3D newTList[][] = new Transform3D[newLen][]; + HashKey newHList[] = new HashKey[newLen]; + int newIndexList[][] = new int[newLen][]; + CachedTargets newTargets[] = new CachedTargets[newLen]; + + len = localToVworld.length; + + // Copy the existing data into the newly created data objects. + System.arraycopy(localToVworld, 0, newTList, 0, localToVworld.length); + System.arraycopy(localToVworldIndex, 0, newIndexList, 0, + localToVworldIndex.length); + System.arraycopy(localToVworldKeys, 0, newHList, 0, + localToVworldKeys.length); + System.arraycopy(cachedTargets, 0, newTargets, 0, + cachedTargets.length); + + localToVworld = newTList; + localToVworldIndex = newIndexList; + localToVworldKeys = newHList; + cachedTargets = newTargets; + } + + int[] hkIndex = new int[1]; + int hkIndexPlus1, blkSize; + + s.hashkeyIndex = new int[s.keys.length]; + + // This should appear before super.setNodeData() if it exists + s.parentBranchGroupPaths = branchGroupPaths; + + for(i=len, j=0; i<localToVworld.length; i++, j++) { + + if(s.keys[j].equals(localToVworldKeys, hkIndex, 0, i)) { + System.out.println("Found matching hashKey in setNodeData."); + System.out.println("We're in TROUBLE!!!"); + } + s.hashkeyIndex[j] = hkIndex[0]; + + + if(hkIndex[0] == i) { // Append to last. + localToVworldKeys[i] = s.keys[j]; + localToVworld[i] = s.currentTransforms[j]; + localToVworldIndex[i] = s.currentTransformsIndex[j]; + } + else { // Insert in between array elements. + hkIndexPlus1 = hkIndex[0] + 1; + blkSize = i - hkIndex[0]; + + // Shift the later portion of array elements by one position. + // This is the make room for the new data entry. + System.arraycopy(localToVworldKeys, hkIndex[0], localToVworldKeys, + hkIndexPlus1, blkSize); + System.arraycopy(localToVworld, hkIndex[0], localToVworld, + hkIndexPlus1, blkSize); + System.arraycopy(localToVworldIndex, hkIndex[0], localToVworldIndex, + hkIndexPlus1, blkSize); + System.arraycopy(cachedTargets, hkIndex[0], cachedTargets, + hkIndexPlus1, blkSize); + + localToVworldKeys[hkIndex[0]] = s.keys[j]; + localToVworld[hkIndex[0]] = s.currentTransforms[j]; + localToVworldIndex[hkIndex[0]] = s.currentTransformsIndex[j]; + } + + // System.out.println("SG: j = "+j+" hkIndex[0] = "+hkIndex[0]+" s.keys[j] = "+s.keys[j]); + // For now (1.2.1beta2) only. We cleanup setLive, and clearLive in + // next release. + setAuxData(s, j, hkIndex[0]); + } + + // The SetLiveState need the reflect the new state of this SharedGroup. + // The SetLiveState will get reset back in SetLive, after all children of this + // node have been set live. + s.localToVworld = localToVworld; + s.localToVworldIndex = localToVworldIndex; + s.localToVworldKeys = localToVworldKeys; + s.orderedPaths = orderedPaths; + s.switchStates = switchStates; + + // Note that s.childSwitchLinks is updated in super.setLive + s.childTransformLinks = childTransformLinks; + s.parentTransformLink = this; + s.parentSwitchLink = this; + s.viewLists = viewLists; + s.lights = lights; + s.fogs = fogs; + s.altAppearances = altAppearances; + s.modelClips = modelClips; + } + + void setLive(SetLiveState s) { + + int i,j; + Targets[] newTargets = null; + + // save setLiveState + Transform3D savedLocalToVworld[][] = s.localToVworld; + int savedLocalToVworldIndex[][] = s.localToVworldIndex; + HashKey savedLocalToVworldKeys[] = s.localToVworldKeys; + ArrayList savedOrderedPaths = s.orderedPaths; + ArrayList savedViewList = s.viewLists; + ArrayList savedLights = s.lights; + ArrayList savedFogs = s.fogs; + ArrayList savedMclips = s.modelClips; + ArrayList savedAltApps = s.altAppearances; + + SharedGroupRetained savedLastSharedGroup = s.lastSharedGroup; + Targets[] savedSwitchTargets = s.switchTargets; + ArrayList savedSwitchStates = s.switchStates; + ArrayList savedChildSwitchLinks = s.childSwitchLinks; + GroupRetained savedParentSwitchLink = s.parentSwitchLink; + ArrayList savedChildTransformLinks = s.childTransformLinks; + GroupRetained savedParentTransformLink = s.parentTransformLink; + int[] savedHashkeyIndex = s.hashkeyIndex; + + // update setLiveState for this node + // Note that s.containsNodesList is updated in super.setLive + s.lastSharedGroup = this; + + Targets[] savedTransformTargets = s.transformTargets; + + int numPaths = s.keys.length; + newTargets = new Targets[numPaths]; + for(i=0; i<numPaths; i++) { + if (s.transformLevels[i] >= 0) { + newTargets[i] = new Targets(); + } else { + newTargets[i] = null; + } + } + s.transformTargets = newTargets; + + super.setLive(s); + + int hkIndex; + for(i=0; i<numPaths; i++) { + if (s.transformTargets[i] != null) { + hkIndex = s.hashkeyIndex[i]; + cachedTargets[hkIndex] = s.transformTargets[i].snapShotInit(); + } + } + // Assign data in cachedTargets to j3dCTs. + j3dCTs = new CachedTargets[cachedTargets.length]; + copyCachedTargets(TargetsInterface.TRANSFORM_TARGETS, j3dCTs); + + computeTargetThreads(TargetsInterface.TRANSFORM_TARGETS, cachedTargets); + + + // restore setLiveState + s.localToVworld = savedLocalToVworld; + s.localToVworldIndex = savedLocalToVworldIndex; + s.localToVworldKeys = savedLocalToVworldKeys; + s.orderedPaths = savedOrderedPaths; + s.viewLists = savedViewList; + + s.lights = savedLights; + s.fogs = savedFogs; + s.modelClips = savedMclips; + s.altAppearances = savedAltApps; + + s.lastSharedGroup = savedLastSharedGroup; + s.switchTargets = savedSwitchTargets; + s.switchStates = savedSwitchStates; + + s.childSwitchLinks = savedChildSwitchLinks; + s.parentSwitchLink = savedParentSwitchLink; + s.childTransformLinks = savedChildTransformLinks; + s.parentTransformLink = savedParentTransformLink; + + s.transformTargets = savedTransformTargets; + s.hashkeyIndex = savedHashkeyIndex; +/* +// TODO : port this + for (int i=0; i < children.size(); i++) { + if ((childContains[i][0] & ILLEGAL_LEAF_MASK) != 0) { + throw new IllegalSharingException(J3dI18N.getString("SharedGroupRetained0")); } + } +*/ + } + + + /** + * remove the localToVworld transform for a node. + */ + void removeNodeData(SetLiveState s) { + + int numChildren = children.size(); + ArrayList switchTargets; + int i,j; + + if (refCount <= 0) { + localToVworld = null; + localToVworldIndex = null; + localToVworldKeys = null; + // restore to default and avoid calling clear() + // that may clear parent reference branchGroupPaths + // Note that this function did not invoke super.removeNodeData() + branchGroupPaths = new ArrayList(1); + orderedPaths = null; + switchStates = null; + cachedTargets = null; + targetThreads = 0; + lights.clear(); + fogs.clear(); + modelClips.clear(); + altAppearances.clear(); + } + else { + int index, len; + + // Remove the localToVworld key + int newLen = localToVworld.length - s.keys.length; + + Transform3D[][] newTList = new Transform3D[newLen][]; + HashKey[] newHList = new HashKey[newLen]; + Transform3D newChildTList[][] = null; + int[][] newIndexList = new int[newLen][]; + CachedTargets[] newTargets = new CachedTargets[newLen]; + + int[] tempIndex = new int[s.keys.length]; + int curStart =0, newStart =0; + boolean found = false; + + for(i=0;i<s.keys.length;i++) { + index = s.keys[i].equals(localToVworldKeys, 0, localToVworldKeys.length); + + tempIndex[i] = index; + + if(index >= 0) { + found = true; + if(index == curStart) { + curStart++; + } + else { + len = index - curStart; + System.arraycopy(localToVworld, curStart, newTList, newStart, len); + System.arraycopy(localToVworldIndex, curStart, newIndexList, + newStart, len); + System.arraycopy(localToVworldKeys, curStart, newHList, newStart, len); + System.arraycopy(cachedTargets, curStart, newTargets, + newStart, len); + + curStart = index+1; + newStart = newStart + len; + } + } + else { + found = false; + System.out.println("Can't Find matching hashKey in SG.removeNodeData."); + System.out.println("We're in TROUBLE!!!"); + } + } + + if((found == true) && (curStart < localToVworld.length)) { + len = localToVworld.length - curStart; + System.arraycopy(localToVworld, curStart, newTList, newStart, len); + System.arraycopy(localToVworldIndex, curStart, newIndexList, + newStart, len); + System.arraycopy(localToVworldKeys, curStart, newHList, newStart, len); + System.arraycopy(cachedTargets, curStart, newTargets, + newStart, len); + } + + // Must be in reverse, to preserve right indexing. + for (i = tempIndex.length-1; i >= 0 ; i--) { + if(tempIndex[i] >= 0) { + branchGroupPaths.remove(tempIndex[i]); + orderedPaths.remove(tempIndex[i]); + switchStates.remove(tempIndex[i]); + lights.remove(tempIndex[i]); + fogs.remove(tempIndex[i]); + modelClips.remove(tempIndex[i]); + altAppearances.remove(tempIndex[i]); + } + } + + localToVworld = newTList; + localToVworldIndex = newIndexList; + localToVworldKeys = newHList; + cachedTargets = newTargets; + } + s.localToVworld = localToVworld; + s.localToVworldIndex = localToVworldIndex; + s.localToVworldKeys = localToVworldKeys; + s.orderedPaths = orderedPaths; + s.switchStates = switchStates; + s.viewLists = viewLists; + s.lights = lights; + s.fogs = fogs; + s.modelClips = modelClips; + s.altAppearances = altAppearances; + } + + void clearLive(SetLiveState s) { + + int i,j,k, index; + + Transform3D savedLocalToVworld[][] = s.localToVworld; + int savedLocalToVworldIndex[][] = s.localToVworldIndex; + HashKey savedLocalToVworldKeys[] = s.localToVworldKeys; + ArrayList savedOrderedPaths = s.orderedPaths; + ArrayList savedViewLists = s.viewLists; + + ArrayList savedLights = s.lights; + ArrayList savedFogs = s.fogs; + ArrayList savedMclips = s.modelClips; + ArrayList savedAltApps = s.altAppearances; + + Targets[] savedSwitchTargets = s.switchTargets; + Targets[] savedTransformTargets = s.transformTargets; + // no need to gather targets from sg in clear live + s.transformTargets = null; + s.switchTargets = null; + + + // TODO: This is a hack since removeNodeData is called before + // children are clearLives + int[] tempIndex = null; + // Don't keep the indices if everything will be cleared + if (s.keys.length != localToVworld.length) { + tempIndex = new int[s.keys.length]; + for (i = s.keys.length-1; i >= 0; i--) { + tempIndex[i] = s.keys[i].equals(localToVworldKeys, 0, localToVworldKeys.length); + } + } + + super.clearLive(s); + // Do this after children clearlive since part of the viewLists may get cleared + // during removeNodeData + if(refCount <= 0) { + viewLists.clear(); + } + else { + // Must be in reverse, to preserve right indexing. + for (i = tempIndex.length-1; i >= 0 ; i--) { + if(tempIndex[i] >= 0) { + viewLists.remove(tempIndex[i]); + } + } + } + + // restore setLiveState from it's local variables. + // removeNodeData has altered these variables. + s.localToVworld = savedLocalToVworld; + s.localToVworldIndex = savedLocalToVworldIndex; + s.localToVworldKeys = savedLocalToVworldKeys; + s.orderedPaths = savedOrderedPaths; + s.viewLists = savedViewLists; + s.lights = savedLights; + s.fogs = savedFogs; + s.modelClips = savedMclips; + s.altAppearances = savedAltApps; + s.transformTargets = savedTransformTargets; + s.switchTargets = savedSwitchTargets; + } + + void updateChildLocalToVworld(HashKey key, int index, + ArrayList dirtyTransformGroups, + ArrayList keySet, + UpdateTargets targets, + ArrayList blUsers) { + + LinkRetained ln; + TransformGroupRetained tg; + int i,j; + Object obj; + + CachedTargets ct = j3dCTs[index]; + if (ct != null) { + targets.addCachedTargets(ct); + if (ct.targetArr[Targets.BLN_TARGETS] != null) { + gatherBlUsers(blUsers, ct.targetArr[Targets.BLN_TARGETS]); + } + } + + synchronized(childTransformLinks) { + for (i=0; i<childTransformLinks.size(); i++) { + obj = childTransformLinks.get(i); + + if (obj instanceof TransformGroupRetained) { + tg = (TransformGroupRetained)obj; + tg.updateChildLocalToVworld( + tg.localToVworldKeys[index], index, + dirtyTransformGroups, keySet, + targets, blUsers); + + + } else { // LinkRetained + ln = (LinkRetained)obj; + currentKey.set(key); + currentKey.append(LinkRetained.plus).append(ln.nodeId); + if (ln.sharedGroup.localToVworldKeys != null) { + j = currentKey.equals(ln.sharedGroup.localToVworldKeys,0, + ln.sharedGroup.localToVworldKeys.length); + if(j < 0) { + System.out.println("SharedGroupRetained : Can't find hashKey"); + } + + if (j < ln.sharedGroup.localToVworldKeys.length) { + ln.sharedGroup.updateChildLocalToVworld( + ln.sharedGroup.localToVworldKeys[j], j, + dirtyTransformGroups, keySet, + targets, blUsers); + } + } + } + } + } + } + + void traverseSwitchChild(int child, HashKey key, + int index, SwitchRetained switchRoot, + boolean init, boolean swChanged, + boolean switchOn, int switchLevel, + ArrayList updateList) { + + SwitchRetained sw; + LinkRetained ln; + Object obj; + ArrayList childSwitchLinks; + int i,j,k; + + childSwitchLinks = (ArrayList)childrenSwitchLinks.get(child); + for (i=0; i<childSwitchLinks.size(); i++) { + obj = childSwitchLinks.get(i); + + if (obj instanceof SwitchRetained) { + sw = (SwitchRetained)obj; + for(j=0; j<sw.children.size(); j++) { + sw.traverseSwitchChild(j, key, index, switchRoot, + init, swChanged, switchOn, switchLevel, updateList); + } + } else { // LinkRetained + ln = (LinkRetained)obj; + switchKey.set(key); + switchKey.append(LinkRetained.plus).append(ln.nodeId); + + if (ln.sharedGroup.localToVworldKeys != null) { + + j = switchKey.equals(ln.sharedGroup.localToVworldKeys,0, + ln.sharedGroup.localToVworldKeys.length); + if(j < 0) { + System.out.println("SharedGroupRetained : Can't find hashKey"); + } + + if (j < ln.sharedGroup.localToVworldKeys.length) { + for(k=0; k<ln.sharedGroup.children.size(); k++) { + ln.sharedGroup.traverseSwitchChild(k, + ln.sharedGroup. + localToVworldKeys[j], + j, switchRoot, init, + swChanged, switchOn, + switchLevel, updateList); + } + } + } + } + } + } + + void traverseSwitchParent() { + int i; + NodeRetained ln; + + for(i=0; i<parents.size(); i++) { + ln = (NodeRetained) parents.elementAt(i); + if (ln.parentSwitchLink != null) { + if (parentSwitchLink instanceof SwitchRetained) { + ((SwitchRetained)parentSwitchLink).traverseSwitchParent(); + } else if (parentSwitchLink instanceof SharedGroupRetained) { + ((SharedGroupRetained)parentSwitchLink).traverseSwitchParent(); + } + } + } + } + + // Top level compile call, same as BranchGroup.compile() + void compile() { + + if (source.isCompiled() || VirtualUniverse.mc.disableCompile) + return; + + if (J3dDebug.devPhase && J3dDebug.debug) { + J3dDebug.doDebug(J3dDebug.compileState, J3dDebug.LEVEL_3, + "SharedGroupRetained.compile()....\n"); + } + + CompileState compState = new CompileState(); + + isRoot = true; + + compile(compState); + merge(compState); + + if (J3dDebug.devPhase && J3dDebug.debug) { + if (J3dDebug.doDebug(J3dDebug.compileState, J3dDebug.LEVEL_3)) { + compState.printStats(); + } + if (J3dDebug.doDebug(J3dDebug.compileState, J3dDebug.LEVEL_5)) { + this.traverse(false, 1); + System.out.println(); + } + } + + } + + /** + * Returns the Link nodes that refer to this SharedGroup node + * @return An array of Link nodes + */ + Link[] getLinks() { + Link[] links; + // make sure this method is MT-safe + synchronized(parents) { + int n = parents.size(); + // allocate new array + links = new Link[n]; + for(int i = 0; i < n; i++) { + // copy Link nodes from this node's list of parents + links[i] = (Link)((LinkRetained)parents.elementAt(i)).source; + } + } + return links; + } + + void insertChildrenData(int index) { + if (childrenSwitchLinks == null) { + childrenSwitchLinks = new ArrayList(1); + } + childrenSwitchLinks.add(index, new ArrayList(1)); + } + + void appendChildrenData() { + if (childrenSwitchLinks == null) { + childrenSwitchLinks = new ArrayList(1); + } + childrenSwitchLinks.add(new ArrayList(1)); + } + + void removeChildrenData(int index) { + ArrayList oldSwitchLinks = (ArrayList)childrenSwitchLinks.get(index); + oldSwitchLinks.clear(); + childrenSwitchLinks.remove(index); + } + + + // *************************** + // TargetsInterface methods + // *************************** + + public int getTargetThreads(int type) { + if (type == TargetsInterface.TRANSFORM_TARGETS) { + return targetThreads; + } else { + System.out.println("getTargetThreads: wrong arguments"); + return -1; + } + } + + TargetsInterface getClosestTargetsInterface(int type) { + return this; + } + + // re-evalute localTargetThreads using newCachedTargets and + // re-evaluate targetThreads + public void computeTargetThreads(int type, + CachedTargets[] newCachedTargets) { + + localTargetThreads = 0; + if (type == TargetsInterface.TRANSFORM_TARGETS) { + for(int i=0; i<newCachedTargets.length; i++) { + if (newCachedTargets[i] != null) { + localTargetThreads |= newCachedTargets[i].computeTargetThreads(); + } + } + targetThreads = localTargetThreads; + + int numLinks = childTransformLinks.size(); + TargetsInterface childLink; + NodeRetained node; + + for(int i=0; i<numLinks; i++) { + node = (NodeRetained)childTransformLinks.get(i); + if (node.nodeType == NodeRetained.LINK) { + childLink = (TargetsInterface) + ((LinkRetained)node).sharedGroup; + } else { + childLink = (TargetsInterface) node; + } + if (childLink != null) { + targetThreads |= + childLink.getTargetThreads(TargetsInterface.TRANSFORM_TARGETS); + } + } + + } else { + System.out.println("computeTargetsThreads: wrong arguments"); + } + } + + // re-compute localTargetThread, targetThreads and + // propagate changes to ancestors + public void updateTargetThreads(int type, CachedTargets[] newCachedTargets) { + // type is ignored here, only need for SharedGroup + if (type == TargetsInterface.TRANSFORM_TARGETS) { + computeTargetThreads(type, newCachedTargets); + if (parentTransformLink != null) { + TargetsInterface pti = (TargetsInterface)parentTransformLink; + pti.propagateTargetThreads(TargetsInterface.TRANSFORM_TARGETS, + targetThreads); + } + } else { + System.out.println("updateTargetThreads: wrong arguments"); + } + } + + // re-evaluate targetThreads using childTargetThreads and + // propagate changes to ancestors + public void propagateTargetThreads(int type, int childTargetThreads) { + if (type == TargetsInterface.TRANSFORM_TARGETS) { + LinkRetained ln; + // TODO : For now we'll OR more than exact. + //targetThreads = localTargetThreads | childTargetThreads; + targetThreads = targetThreads | childTargetThreads; + for(int i=0; i<parents.size(); i++) { + ln = (LinkRetained) parents.elementAt(i); + if (ln.parentTransformLink != null) { + TargetsInterface pti = + (TargetsInterface)ln.parentTransformLink; + pti.propagateTargetThreads(type, targetThreads); + } + } + } else { + System.out.println("propagateTargetThreads: wrong arguments"); + } + } + + public void updateCachedTargets(int type, CachedTargets[] newCt) { + if (type == TargetsInterface.TRANSFORM_TARGETS) { + j3dCTs = newCt; + } else { + System.out.println("updateCachedTargets: wrong arguments"); + } + } + + public void copyCachedTargets(int type, CachedTargets[] newCt) { + if (type == TargetsInterface.TRANSFORM_TARGETS) { + int size = cachedTargets.length; + for (int i=0; i<size; i++) { + newCt[i] = cachedTargets[i]; + } + } else { + System.out.println("copyCachedTargets: wrong arguments"); + } + } + + public CachedTargets getCachedTargets(int type, int index, int child) { + if (type == TargetsInterface.SWITCH_TARGETS) { + // child info is not used, SG does not have per child states + if (index < switchStates.size()) { + SwitchState switchState = (SwitchState)switchStates.get(index); + return switchState.cachedTargets; + } else { + return null; + } + } else { + // type == TargetsInterface.TRANSFORM_TARGETS + return cachedTargets[index]; + } + } + + public void resetCachedTargets(int type, + CachedTargets[] newCtArr,int child) { + if (type == TargetsInterface.SWITCH_TARGETS) { + // child info is not used, SG does not have per child states + SwitchState switchState; + if (newCtArr.length != switchStates.size()) { + System.out.println("resetCachedTargets: unmatched length!" + + newCtArr.length + " " + switchStates.size()); + System.out.println(" resetCachedTargets: " + this); + } + for (int i=0; i<newCtArr.length; i++) { + switchState = (SwitchState)switchStates.get(i); + switchState.cachedTargets = newCtArr[i]; + } + + } else { + // type == TargetsInterface.TRANSFORM_TARGETS + cachedTargets = newCtArr; + } + } + + public ArrayList getTargetsData(int type, int index) { + // index is ignores for SharedGroup + if (type == TargetsInterface.SWITCH_TARGETS) { + return switchStates; + } else { + System.out.println("getTargetsData: wrong arguments"); + return null; + } + } + + + void childDoSetLive(NodeRetained child, int childIndex, SetLiveState s) { + + int i; + s.childSwitchLinks = (ArrayList)childrenSwitchLinks.get(childIndex); + s.switchStates = switchStates; + + if(child!=null) + child.setLive(s); + } + + void childCheckSetLive(NodeRetained child, int childIndex, SetLiveState s) { + s.childTransformLinks = childTransformLinks; + s.parentTransformLink = this; + child.setLive(s); + } +} diff --git a/src/classes/share/javax/media/j3d/Sound.java b/src/classes/share/javax/media/j3d/Sound.java new file mode 100644 index 0000000..eff2786 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Sound.java @@ -0,0 +1,1151 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * Sound node is an abstract class that defines the properties common to all + * sound sources. A scene graph can contain multiple sounds. Associated with each + * sound source are: a reference to sound data, an amplitude scale factor, a release + * flag denoting that the sound associated with this node is to play to end when + * it is disabled, the number of times sound is to be repeated, the sound's state + * (on or off), a scheduling region, and a flag denoting if the sound is to + * continue playing "silently" even while it is inactive. Whenever the listener + * is within a sound node's scheduling bounds this sound is potentially audible. + *<P> + * Sound Data + * + * <UL>Associated with each Sound node is a MediaContainer + * which includes audio data and information about this data. + * This data can be cached (buffered) or non-cached (unbuffered or streaming). + * If an AudioDevice has been attached to the PhysicalEnvironment, the sound + * data is made ready to begin playing. + * Certain functionality can not be applied to true streaming sound data:<p> + * 1) querying the sound's duration (Sound.DURATION_UNKNOWN will be returned),<br> + * 2) looping over a range of the streaming data; and<br> + * 3) restart a previously played portion of the data.<p> + * Depending on the implementation of the AudioDevice used, streamed, non- + * cached data may not be fully spatialized.</UL> + *<P> + * Initial Gain + * + * <UL>This gain is a scale factor applied to the sound data associated + * with this sound source to increase or decrease its overall amplitude.</UL> + *<P> + * Loop + * + * <UL>Data for non-streaming sound (such as a sound sample) can contain two + * loop points marking a section of the data that is to be looped a specific + * number of times. Thus sound data can be divided into three segments: + * the attack (before the begin loop point), the sustain (between the begin + * and end loop points), and the release (after the end loop point). If + * there are no loop begin and end points defined as part of the sound data, + * the begin loop point is set at the beginning of the sound data, + * and the end loop point at the end of the sound data. + * If this is the case, looping the sound would mean repeating the whole + * sound. However, these allow a portion in the middle of the sound to + * be looped. + *<P> + * A sound can be looped a specified number of times after it is activated + * before it is completed. The loop count value explicitly sets the number + * of times the sound is looped. Any non-negative number is a valid value. + * A value of zero denotes that the looped section is not repeated, but is + * played only once. A value of -1 denotes that the loop is repeated + * indefinitely. + *<P> + * Changing loop count of a sound after the sound has been started will not + * dynamically affect the loop count currently used by the sound playing. + * The new loop count will be used the next time the sound is enabled.</UL> + * <P> + * Release Flag + * + * <UL>When a sound is disabled, its playback would normally stop immediately + * no matter what part of the sound data was currently being played. By + * setting the Release Flag to true for nodes with non-streaming sound data, + * the sound is allowed to play from its current position in the sound data + * to the end of the data (without repeats), thus playing the release portion + * of the sound before stopping.</UL> + *<P> + * Continuous Flag + * + * <UL>For some applications, it's useful to turn a sound source "off" but to + * continue "silently" playing the sound so that when it is turned back "on" + * the sound picks up playing in the same location (over time) as it would + * have been if the sound had never been disabled (turned off). Setting the + * Continuous flag true causes the sound renderer to keep track of where + * (over time) the sound would be playing even when the sound is disabled.</UL> + *<P> + * Enable Sound + * + * <UL>When enabled, the sound source is started + * playing and thus can potentially be heard, depending on its activation + * state, gain control parameters, continuation state, and spatialization + * parameters. If the continuous state is true, even if the sound is not + * active, enabling the sound starts the sound silently "playing," so that + * when the sound is activated, the sound is (potentially) heard from + * somewhere in the middle of the sound data. Activation state can change + * from active to inactive any number of times without stopping or starting + * the sound. To re-start a sound at the beginning of its data, re-enable + * the sound by calling setEnable with true. + *<P> + * Setting the enable flag to true during construction acts as a request + * to start the sound playing "as soon as it can" be started. + * This could be close to immediately in limited cases, but several conditions, + * detailed below, must be met for a sound to be ready to be played.</UL> + *<P> + * Mute Sound + * + * <UL>When the mute state is set true, a playing sound is made to play silently. + *</UL><P> + * Pause Sound + * + * <UL>When the pause state is set true, a playing sound is paused. + *<P> + * Setting the enable flag to true during construction acts as a request + * to start the sound playing "as soon as it can" be started. + * This could be close to immediately in limited cases, but several conditions, + * detailed below, must be met for a sound to be ready to be played.</UL> + * <P> + * Scheduling Bounds + * + * <UL> + * A Sound is scheduled for activation when its scheduling region intersects + * the ViewPlatform's activation volume. This is used when the scheduling + * bounding leaf is set to null.</UL> + *<P> + * Scheduling Bounding Leaf + * + * <UL>When set to a value other than null, the scheduling bounding leaf + * region overrides the scheduling bounds + * object.</UL> + *<P> + * Prioritize Sound + * + * <UL>Sound Priority is used + * to rank concurrently playing sounds in order of importance during playback. + * When more sounds are started than the AudioDevice + * can handle, the sound node with the lowest priority ranking is + * deactivated (but continues playing silently). If a sound is deactivated + * (due to a sound with a higher + * priority being started), it is automatically re-activated when + * resources become available (e.g., when a sound with a higher priority + * finishes playing), or when the ordering of sound nodes are changed due to + * a change in a sound node's priority. + * <P> + * Sounds with a lower priority than sound that can + * not be played due to lack of channels will be played. + * For example, assume we have eight channels available for playing sounds. + * After ordering four sounds, we begin playing them in order, checking if + * the number of channels required to play a given sound are actually available + * before the sound is played. Furthermore, say the first sound needs three + * channels + * to play, the second sound needs four channels, the third sound needs three + * channels + * and the fourth sound needs only one channel. The first and second sounds + * can be started because they require seven of the eight available channels. The + * third sound can not be audibly started because it requires three channels and + * only one is still available. Consequently, the third sound starts playing + * 'silently.' The fourth sound can and will be started since it only requires + * one channel. The third sound will be made audible when three channels become + * available (i.e., when the first or second sound finishes playing). + * <P> + * Sounds given the same priority are ordered randomly. If the application + * wants a specific ordering, it must assign unique priorities to each sound. + * <P> + * Methods to determine what audio output resources are required for playing + * a Sound node on a particular AudioDevice and to determine the currently + * available audio output resources are described in the AudioDevice class.</UL> + * <P> + * Duration + * + * <UL>Each sound has a length of time in milliseconds that it + * can run (including repeating loop section) + * if it plays to completion. If the sound + * media type is streaming, or if the sound is looped indefinitely, then a + * value of -1 (implying infinite length) is returned.</UL> + *<P> + * Number of Channels used on Audio Device to Play Sound + * + * <UL>When a sound is started, it could use more than one channel on the + * selected AudioDevice it is to be played on. The number of Audio Device + * channels currently used for a sound can be queried using + * getNumberOfChannelsUsed().</UL> + *<P> + * Preparing a Sound to be Played + * + * <UL>Sound data associated with a Sound node, either during construction + * (when the MediaContainer is passed into the constructor as a parameter) + * or by calling setSoundData(), it can be prepared to begin playing + * only after the following conditions are satisfied:<p> + * 1) the Sound node has non-null sound data associated with it<br> + * 2) the Sound node is live<br> + * 3) there is an active View in the Universe and<br> + * 4) there is an initialized AudioDevice associated with the + * PhysicalEnvironment.<p> + * Depending on the type of MediaContainer the sound data is and on the + * implementation of the AudioDevice used, sound data preparation could consist + * of opening, attaching, loading, or copying into memory the associated sound data. + * The query method, isReady() returns true when the sound is fully preprocessed + * so that it is playable (audibly if active, silently if not active).</UL> + *<P> + * Playing Status + * + * <UL>A sound source will not be heard unless it is:<p> + * 1) enabled/started<br> + * 2) activated<br> + * 3) not muted<br> + * 4) not paused<p> + * While these conditions are meet, the sound is potentially audible + * and the method isPlaying() will return a status of true. + *<P> + * isPlaying returns false but isPlayingSilently returns true if a sound:<p> + * 1) is enabled before it is activated; it is begun playing silently.<br> + * 2) is enabled then deactivated while playing; it continues playing silently<br> + * 3) is enabled while it mute state is true + *<P> + * When the sound finishes playing it's sound data (including all loops), it + * is implicitly disabled.</UL> + *<P> + * @see AudioDevice + */ + +public abstract class Sound extends Leaf { + // Constants for Sound object. + // + // These flags, when enabled using the setCapability method, allow an + // application to invoke methods that respectively read and write the + // sound fields. + // These capability flags are enforced only when the node is part of + // a live or compiled scene graph. + + /** + * Specifies that this node allows access to its object's sound data + * information. + */ + public static final int + ALLOW_SOUND_DATA_READ = CapabilityBits.SOUND_ALLOW_SOUND_DATA_READ; + + /** + * Specifies that this node allows writing to its object's sound data + * information. + */ + public static final int + ALLOW_SOUND_DATA_WRITE = CapabilityBits.SOUND_ALLOW_SOUND_DATA_WRITE; + + /** + * Specifies that this node allows access to its object's initial gain + * information. + */ + public static final int + ALLOW_INITIAL_GAIN_READ = CapabilityBits.SOUND_ALLOW_INITIAL_GAIN_READ; + + /** + * Specifies that this node allows writing to its object's initial gain + * information. + */ + public static final int + ALLOW_INITIAL_GAIN_WRITE = CapabilityBits.SOUND_ALLOW_INITIAL_GAIN_WRITE; + + /** + * Specifies that this node allows access to its object's loop + * information. + */ + public static final int + ALLOW_LOOP_READ = CapabilityBits.SOUND_ALLOW_LOOP_READ; + + /** + * Specifies that this node allows writing to its object's loop + * information. + */ + public static final int + ALLOW_LOOP_WRITE = CapabilityBits.SOUND_ALLOW_LOOP_WRITE; + + /** + * Specifies that this node allows access to its object's release flag + * information. + */ + public static final int + ALLOW_RELEASE_READ = CapabilityBits.SOUND_ALLOW_RELEASE_READ; + + /** + * Specifies that this node allows writing to its object's release flag + * information. + */ + public static final int + ALLOW_RELEASE_WRITE = CapabilityBits.SOUND_ALLOW_RELEASE_WRITE; + + /** + * Specifies that this node allows access to its object's continuous + * play information. + */ + public static final int + ALLOW_CONT_PLAY_READ = CapabilityBits.SOUND_ALLOW_CONT_PLAY_READ; + + /** + * Specifies that this node allows writing to its object's continuous + * play information. + */ + public static final int + ALLOW_CONT_PLAY_WRITE = CapabilityBits.SOUND_ALLOW_CONT_PLAY_WRITE; + + /** + * Specifies that this node allows access to its object's sound on + * information. + */ + public static final int + ALLOW_ENABLE_READ = CapabilityBits.SOUND_ALLOW_ENABLE_READ; + + /** + * Specifies that this node allows writing to its object's sound on + * information. + */ + public static final int + ALLOW_ENABLE_WRITE = CapabilityBits.SOUND_ALLOW_ENABLE_WRITE; + + /** + * Specifies that this node allows read access to its scheduling bounds + * information. + */ + public static final int + ALLOW_SCHEDULING_BOUNDS_READ = CapabilityBits.SOUND_ALLOW_SCHEDULING_BOUNDS_READ; + + /** + * Specifies that this node allows write access to its scheduling bounds + * information. + */ + public static final int + ALLOW_SCHEDULING_BOUNDS_WRITE = CapabilityBits.SOUND_ALLOW_SCHEDULING_BOUNDS_WRITE; + + /** + * Specifies that this node allows read access to its priority order + * value. + */ + public static final int + ALLOW_PRIORITY_READ = CapabilityBits.SOUND_ALLOW_PRIORITY_READ; + + /** + * Specifies that this node allows write access to its priority order + * value. + */ + public static final int + ALLOW_PRIORITY_WRITE = CapabilityBits.SOUND_ALLOW_PRIORITY_WRITE; + + /** + * Specifies that this node allows access to its object's sound duration + * information. + */ + public static final int + ALLOW_DURATION_READ = CapabilityBits.SOUND_ALLOW_DURATION_READ; + + /** + * Specifies that this node allows access to its object's sound status + * denoting if it is ready to be played 'immediately'. + */ + public static final int + ALLOW_IS_READY_READ = CapabilityBits.SOUND_ALLOW_IS_READY_READ; + + /** + * Specifies that this node allows access to its object's sound audibly + * playing or playing silently status. + */ + public static final int + ALLOW_IS_PLAYING_READ = CapabilityBits.SOUND_ALLOW_IS_PLAYING_READ; + + /** + * Specifies that this node allows access to its number of channels + * used by this sound. + */ + public static final int + ALLOW_CHANNELS_USED_READ = CapabilityBits.SOUND_ALLOW_CHANNELS_USED_READ; + + /** + * Specifies that this node allows access to its object's mute flag + * information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_MUTE_READ = CapabilityBits.SOUND_ALLOW_MUTE_READ; + + /** + * Specifies that this node allows writing to its object's mute flag + * information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_MUTE_WRITE = CapabilityBits.SOUND_ALLOW_MUTE_WRITE; + + /** + * Specifies that this node allows access to its object's pause flag + * information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_PAUSE_READ = CapabilityBits.SOUND_ALLOW_PAUSE_READ; + + /** + * Specifies that this node allows writing to its object's pause flag + * information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_PAUSE_WRITE = CapabilityBits.SOUND_ALLOW_PAUSE_WRITE; + + /** + * Specifies that this node allows access to its object's sample rate scale + * factor information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_RATE_SCALE_FACTOR_READ = CapabilityBits.SOUND_ALLOW_RATE_SCALE_FACTOR_READ; + + /** + * Specifies that this node allows writing to its object's sample rate scale + * factor information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_RATE_SCALE_FACTOR_WRITE = CapabilityBits.SOUND_ALLOW_RATE_SCALE_FACTOR_WRITE; + + /** + * Denotes that there is no filter value associated with object's distance + * or angular attenuation array. + */ + public static final float NO_FILTER = -1.0f; + + /** + * Denotes that the sound's duration could not be calculated. + * A fall back for getDuration of a non-cached sound. + */ + public static final int DURATION_UNKNOWN = -1; + + /** + * When used as a loop count sound will loop an infinite number of time + * until explicitly stopped (setEnabled(false)). + */ + public static final int INFINITE_LOOPS = -1; + + + /** + * Constructs and initializes a new Sound node using default + * parameters. The following defaults values are used: + * <ul> + * sound data: null<br> + * initial gain: 1.0<br> + * loop: 0<br> + * release flag: false<br> + * continuous flag: false<br> + * enable flag: false<br> + * scheduling bounds : null<br> + * scheduling bounding leaf : null<br> + * priority: 1.0<br> + * rate scale factor: 1.0<br> + * mute state: false<br> + * pause state: false<br> + * </ul> + */ + public Sound() { + } + + /** + * Constructs and initializes a new Sound node object using the provided + * data and gain parameter values, and defaults for all other fields. This + * constructor implicitly loads the sound data associated with this node if + * the implementation uses sound caching. + * @param soundData description of JMF source data used by this sound source + * @param initialGain overall amplitude scale factor applied to sound source + */ + public Sound(MediaContainer soundData, float initialGain) { + ((SoundRetained)this.retained).setSoundData(soundData); + ((SoundRetained)this.retained).setInitialGain(initialGain); + } + + + /** + * Constructs and initializes a new Sound node using provided parameter + * values. + * @param soundData description of JMF source data used by this sound source + * @param initialGain overall amplitude scale factor applied to sound source + * @param loopCount number of times sound is looped when played + * @param release flag specifying whether the sound is to be played + * to end when stopped + * @param continuous flag specifying whether the sound silently plays + * when disabled + * @param enable flag specifying whether the sound is enabled + * @param region scheduling bounds + * @param priority defines playback priority if too many sounds started + */ + public Sound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority ) { + ((SoundRetained)this.retained).setSoundData(soundData); + ((SoundRetained)this.retained).setInitialGain(initialGain); + ((SoundRetained)this.retained).setLoop(loopCount); + ((SoundRetained)this.retained).setReleaseEnable(release); + ((SoundRetained)this.retained).setContinuousEnable(continuous); + ((SoundRetained)this.retained).setEnable(enable); + ((SoundRetained)this.retained).setSchedulingBounds(region); + ((SoundRetained)this.retained).setPriority(priority); + } + + /** + * Constructs and initializes a new Sound node using provided parameter + * values. + * @param soundData description of JMF source data used by this sound source + * @param initialGain overall amplitude scale factor applied to sound source + * @param loopCount number of times sound is looped when played + * @param release flag specifying whether the sound is to be played + * to end when stopped + * @param continuous flag specifying whether the sound silently plays + * when disabled + * @param enable flag specifying whether the sound is enabled + * @param region scheduling bounds + * @param priority defines playback priority if too many sounds started + * @param rateFactor defines playback sample rate scale factor + * @since Java 3D 1.3 + */ + public Sound(MediaContainer soundData, + float initialGain, + int loopCount, + boolean release, + boolean continuous, + boolean enable, + Bounds region, + float priority, + float rateFactor ) { + ((SoundRetained)this.retained).setSoundData(soundData); + ((SoundRetained)this.retained).setInitialGain(initialGain); + ((SoundRetained)this.retained).setLoop(loopCount); + ((SoundRetained)this.retained).setReleaseEnable(release); + ((SoundRetained)this.retained).setContinuousEnable(continuous); + ((SoundRetained)this.retained).setEnable(enable); + ((SoundRetained)this.retained).setSchedulingBounds(region); + ((SoundRetained)this.retained).setPriority(priority); + ((SoundRetained)this.retained).setRateScaleFactor(rateFactor); + } + + /** + * Sets fields that define the sound source data of this node. + * @param soundData description of JMF source data used by this sound source + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setSoundData(MediaContainer soundData) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SOUND_DATA_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound0")); + + if (this instanceof BackgroundSound) + ((SoundRetained)this.retained).setSoundData(soundData); + else // instanceof PointSound or ConeSound + ((PointSoundRetained)this.retained).setSoundData(soundData); + } + + /** + * Retrieves description/data associated with this sound source. + * @return soundData description of JMF source data used by this sound source + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public MediaContainer getSoundData() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SOUND_DATA_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound1")); + + return ((SoundRetained)this.retained).getSoundData(); + } + + /** + * Set the overall gain scale factor applied to data associated with this + * source to increase or decrease its overall amplitude. + * @param amplitude (gain) scale factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setInitialGain(float amplitude) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INITIAL_GAIN_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound2")); + + ((SoundRetained)this.retained).setInitialGain(amplitude); + } + + /** + * Get the overall gain applied to the sound data associated with source. + * @return overall gain scale factor applied to sound source data. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getInitialGain() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_INITIAL_GAIN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound3")); + + return ((SoundRetained)this.retained).getInitialGain(); + } + + /** + * Sets a sound's loop count. + * @param loopCount number of times sound is looped during play + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setLoop(int loopCount) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_LOOP_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound4")); + + ((SoundRetained)this.retained).setLoop(loopCount); + } + + /** + * Retrieves loop count for this sound + * @return loop count + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getLoop() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_LOOP_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound5")); + + return ((SoundRetained)this.retained).getLoop(); + } + + /** + * Enables or disables the release flag for the sound associated with + * this sound. + * @param state release flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setReleaseEnable(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_RELEASE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound6")); + + ((SoundRetained)this.retained).setReleaseEnable(state); + } + + /** + * Retrieves the release flag for sound associated with sound. + * @return sound's release flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getReleaseEnable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_RELEASE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound7")); + + return ((SoundRetained)this.retained).getReleaseEnable(); + } + + /** + * Enables or disables continuous play flag. + * @param state denotes if deactivated sound silently continues playing + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setContinuousEnable(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CONT_PLAY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound8")); + + ((SoundRetained)this.retained).setContinuousEnable(state); + } + + /** + * Retrieves sound's continuous play flag. + * @return flag denoting if deactivated sound silently continues playing + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getContinuousEnable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CONT_PLAY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound9")); + + return ((SoundRetained)this.retained).getContinuousEnable(); + } + + /** + * Enable or disable sound. + * @param state enable (on/off) flag denotes if active sound is heard + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setEnable(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ENABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound10")); + + if (this instanceof BackgroundSound) + ((SoundRetained)this.retained).setEnable(state); + else // instanceof PointSound or ConeSound + ((PointSoundRetained)this.retained).setEnable(state); + } + + /** + * Retrieves sound's enabled flag. + * @return sound enabled flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getEnable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ENABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound21")); + + return ((SoundRetained)this.retained).getEnable(); + } + + + /** + * Set the Sound's scheduling region to the specified bounds. + * This is used when the scheduling bounding leaf is set to null. + * @param region the bounds that contains the Sound's new scheduling + * region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setSchedulingBounds(Bounds region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCHEDULING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound11")); + + ((SoundRetained)this.retained).setSchedulingBounds(region); + } + + /** + * Retrieves the Sound node's scheduling bounds. + * @return this Sound's scheduling bounds information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getSchedulingBounds() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCHEDULING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound12")); + + return ((SoundRetained)this.retained).getSchedulingBounds(); + } + + + /** + * Set the Sound's scheduling region to the specified bounding leaf. + * When set to a value other than null, this overrides the scheduling + * bounds object. + * @param region the bounding leaf node used to specify the Sound + * node's new scheduling region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setSchedulingBoundingLeaf(BoundingLeaf region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCHEDULING_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound11")); + + ((SoundRetained)this.retained).setSchedulingBoundingLeaf(region); + } + + /** + * Retrieves the Sound node's scheduling bounding leaf. + * @return this Sound's scheduling bounding leaf information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BoundingLeaf getSchedulingBoundingLeaf() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SCHEDULING_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound12")); + + return ((SoundRetained)this.retained).getSchedulingBoundingLeaf(); + } + + + /** + * Set sound's priority value. + * @param priority value used to order sound's importance for playback. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPriority(float priority) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PRIORITY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound15")); + + ((SoundRetained)this.retained).setPriority(priority); + } + + /** + * Retrieves sound's priority value. + * @return sound priority value + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getPriority() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PRIORITY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound16")); + + return ((SoundRetained)this.retained).getPriority(); + } + + + /** + * Get the Sound's duration + * @return this Sound's duration in milliseconds including repeated + * loops + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public long getDuration() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_DURATION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound17")); + + return ((SoundRetained)this.retained).getDuration(); + } + + + /** + * Retrieves sound's 'ready' status. If this sound is fully + * prepared to begin playing (audibly or silently) on all + * initialized audio devices, this method returns true. + * @return flag denoting if sound is immediate playable or not + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean isReady() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_IS_READY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound22")); + + return ((SoundRetained)this.retained).isReady(); + } + + /** + * Retrieves sound's 'ready' status. If this sound is fully + * prepared to begin playing (audibly or silently) on the audio + * device associated with this view, this method returns true. + * @param view the view on which to query the ready status. + * @return flag denoting if sound is immediate playable or not + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public boolean isReady(View view) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_IS_READY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound22")); + + return ((SoundRetained)this.retained).isReady(view); + } + + + /** + * Retrieves sound's play status. If this sound is audibly playing on any + * initialized audio device, this method returns true. + * @return flag denoting if sound is playing (potentially audible) or not + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean isPlaying() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_IS_PLAYING_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound18")); + + return ((SoundRetained)this.retained).isPlaying(); + } + + /** + * Retrieves sound's play status. If this sound is audibly playing on the + * audio device associated with the given view, this method returns + * true. + * @param view the view on which to query the isPlaying status. + * @return flag denoting if sound is playing (potentially audible) or not + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public boolean isPlaying(View view) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_IS_PLAYING_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound18")); + + return ((SoundRetained)this.retained).isPlaying(view); + } + + /** + * Retrieves sound's silent status. If this sound is silently playing on + * any initialized audio device, this method returns true. + * @return flag denoting if sound is silently playing (enabled but not active) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean isPlayingSilently() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_IS_PLAYING_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound18")); + + return ((SoundRetained)this.retained).isPlayingSilently(); + } + + /** + * Retrieves sound's silent status. If this sound is silently playing on + * the audio device associated with the given view, this method returns + * true. + * The isPlayingSilently state is affected by enable, mute, and continuous + * states as well as active status of sound. + * @param view the view on which to query the isPlayingSilently status. + * @return flag denoting if sound is silently playing (enabled but not active) + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public boolean isPlayingSilently(View view) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_IS_PLAYING_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound18")); + + return ((SoundRetained)this.retained).isPlayingSilently(view); + } + + + /** + * Retrieves number of channels that are being used to render this sound + * on the audio device associated with the Virtual Universe's primary view. + * @return number of channels used by sound; returns 0 if not playing + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getNumberOfChannelsUsed() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHANNELS_USED_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound20")); + + return ((SoundRetained)this.retained).getNumberOfChannelsUsed(); + } + + /** + * Retrieves number of channels that are being used to render this sound + * on the audio device associated with given view. + * @param view the view on which to query the number of channels used. + * @return number of channels used by sound; returns 0 if not playing + * @exception CapabilityNotSetException if appropriate capability is + * @since Java 3D 1.3 + * not set and this object is part of live or compiled scene graph + */ + public int getNumberOfChannelsUsed(View view) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHANNELS_USED_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound20")); + + return ((SoundRetained)this.retained).getNumberOfChannelsUsed(view); + } + + /** + * Set mute state flag. If the sound is playing it will be set to + * play silently + * @param state flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setMute(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_MUTE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound23")); + + ((SoundRetained)this.retained).setMute(state); + } + + /** + * Retrieves sound Mute state. + * A return value of true does not imply that the sound has + * been started playing or is still playing silently. + * @return mute state flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public boolean getMute() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_MUTE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound24")); + + return ((SoundRetained)this.retained).getMute(); + } + + /** + * Pauses or resumes (paused) playing sound. + * @param state pause flag + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setPause(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PAUSE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound25")); + + ((SoundRetained)this.retained).setPause(state); + } + + /** + * Retrieves the value of the Pause state flag. + * A return value of true does not imply that the sound was + * started playing and then paused. + * @return pause state + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public boolean getPause() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PAUSE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound26")); + + return ((SoundRetained)this.retained).getPause(); + } + + /** + * Sets Sample Rate. + * Changes (scales) the playback rate of a sound independent of + * Doppler rate changes - applied to ALL sound types. + * Affects device sample rate playback and thus affects both pitch and speed + * @param scaleFactor %%% describe this. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public void setRateScaleFactor(float scaleFactor) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_RATE_SCALE_FACTOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound27")); + + ((SoundRetained)this.retained).setRateScaleFactor(scaleFactor); + } + + /** + * Retrieves Sample Rate. + * @return sample rate scale factor + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @since Java 3D 1.3 + */ + public float getRateScaleFactor() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_RATE_SCALE_FACTOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Sound28")); + + return ((SoundRetained)this.retained).getRateScaleFactor(); + } + + /** + * Copies all Sound information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + SoundRetained orgRetained = (SoundRetained)originalNode.retained; + + SoundRetained thisRetained = (SoundRetained)this.retained; + + thisRetained.setSoundData((MediaContainer) getNodeComponent( + orgRetained.getSoundData(), + forceDuplicate, + originalNode.nodeHashtable)); + thisRetained.setInitialGain(orgRetained.getInitialGain()); + thisRetained.setLoop(orgRetained.getLoop()); + thisRetained.setReleaseEnable(orgRetained.getReleaseEnable()); + thisRetained.setContinuousEnable(orgRetained.getContinuousEnable()); + thisRetained.setSchedulingBounds(orgRetained.getSchedulingBounds()); + thisRetained.setPriority(orgRetained.getPriority()); + thisRetained.setEnable(orgRetained.getEnable()); + + // updateNodeReferences will set the following correctly + thisRetained.setSchedulingBoundingLeaf(orgRetained.getSchedulingBoundingLeaf()); + } + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + super.updateNodeReferences(referenceTable); + + SoundRetained rt = (SoundRetained) retained; + BoundingLeaf bl = rt.getSchedulingBoundingLeaf(); + + if (bl != null) { + Object o = referenceTable.getNewObjectReference(bl); + rt.setSchedulingBoundingLeaf((BoundingLeaf)o); + } + MediaContainer sd = rt.getSoundData(); + if (sd != null) { + rt.setSoundData(sd); + } + } +} diff --git a/src/classes/share/javax/media/j3d/SoundException.java b/src/classes/share/javax/media/j3d/SoundException.java new file mode 100644 index 0000000..645de0c --- /dev/null +++ b/src/classes/share/javax/media/j3d/SoundException.java @@ -0,0 +1,35 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Indicates a problem in loading or playing a sound sample. + */ +public class SoundException extends RuntimeException{ + +/** + * Create the exception object with default values. + */ + public SoundException(){ + } + +/** + * Create the exception object that outputs message. + * @param str the message string to be output. + */ + public SoundException(String str){ + + super(str); + } + +} diff --git a/src/classes/share/javax/media/j3d/SoundRenderer.java b/src/classes/share/javax/media/j3d/SoundRenderer.java new file mode 100644 index 0000000..bb47217 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SoundRenderer.java @@ -0,0 +1,75 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Vector; + +class SoundRenderer extends Object { + + SoundRenderer() { + } + + void activate(SoundRetained sound, SoundscapeRetained ss) { + AuralAttributesRetained aa = ss.attributes.mirrorAa; + + if (sound instanceof BackgroundSoundRetained) { + System.out.println("Activating BackgroundSoundRetained"); + } else if (sound instanceof ConeSoundRetained) { + System.out.println("Activating ConeSoundRetained"); + } else if (sound instanceof PointSoundRetained) { + System.out.println("Activating PointSoundRetained"); + } + if (ss != null) + System.out.println("Soundscape is " + ss); + else + System.out.println("Soundscape is null"); + + if (aa != null) + System.out.println("AuralAttributes is " + aa); + else + System.out.println("AuralAttributes is null"); + } + + void update(SoundRetained sound, SoundscapeRetained ss) { + AuralAttributesRetained aa = ss.attributes.mirrorAa; + + if (false) { + if (sound instanceof BackgroundSoundRetained) { + System.out.println("Updating BackgroundSoundRetained"); + } else if (sound instanceof ConeSoundRetained) { + System.out.println("Updating ConeSoundRetained"); + } else if (sound instanceof PointSoundRetained) { + System.out.println("Updating PointSoundRetained"); + } + System.out.println("Soundscape is " + ss); + } + } + + void deactivate(SoundRetained sound) { + if (false) { + if (sound instanceof BackgroundSoundRetained) { + System.out.println("Deactivating BackgroundSoundRetained"); + } else if (sound instanceof ConeSoundRetained) { + System.out.println("Deactivating ConeSoundRetained"); + } else if (sound instanceof PointSoundRetained) { + System.out.println("Deactivating PointSoundRetained"); + } + } + } + + public String toString() { + return ""; + } + +} diff --git a/src/classes/share/javax/media/j3d/SoundRetained.java b/src/classes/share/javax/media/j3d/SoundRetained.java new file mode 100644 index 0000000..096fcae --- /dev/null +++ b/src/classes/share/javax/media/j3d/SoundRetained.java @@ -0,0 +1,1297 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; +import java.util.ArrayList; + + + +/** + * SoundRetained is an abstract class that contains instance varables common + * to all retained sounds. + */ + +abstract class SoundRetained extends LeafRetained +{ + + /** + * Null Sound identifier denotes sound is not created or initialized + */ + static final int NULL_SOUND = -1; + + /** + * sound data associated with sound source + */ + MediaContainer soundData = null; + + /** + * Overall Scale Factor applied to sound. + */ + float initialGain = 1.0f; // Valid values are >= 0.0. + + /** + * Number of times sound is looped/repeated during play + */ + int loopCount = 0; // Range from 0 to POSITIVE_INFINITY(-1) + + /** + * Switch for turning sound on or off while the sound is "active" + */ + boolean enable = false; + + /** + * Type of release when sound is disabled. + * If true, sound plays thru to end of sample before disabled + * Otherwise, sound is disabled immediately. + */ + boolean release = false; + + /** + * Flag denoting if sound silently continues playing when it's deactivated. + */ + boolean continuous = false; + + /** + * Flag denoting if sound is explicitly muted, so that if begins playing + * it will be played silently. + */ + boolean mute = false; + + /** + * Flag denoting if sound is paused from playing - waiting to be resumed + */ + boolean pause = false; + + /** + * Sound priority ranking value. + * Valid values are 0.0 to 1.0 + */ + float priority = 1.0f; + + /** + * Rate Scale Factor applied to sounds playback sample rate in Hertz. + * Valid values are 0.0 to 1.0 + */ + float rate = 1.0f; + + /** + * The Boundary object defining the sound's scheduling region. + */ + Bounds schedulingRegion = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * The transformed bounds from either schedulingRegion or boundingLeaf + */ + Bounds transformedRegion = null; + + // Dirty bit flags used to pass change as part of message, and are + // acclummuated/stored in SoundSchedulerAtoms. + // These flags are grouped into two catagories: + // attribsDirty for sound node fields + // stateDirty for changes to sound state not reflected by sound fields. + + // Attributes Dirty bit flags + // This bitmask is set when sound node attribute is changed by the user. + static final int SOUND_DATA_DIRTY_BIT = 0x0001; + static final int INITIAL_GAIN_DIRTY_BIT = 0x0002; + static final int LOOP_COUNT_DIRTY_BIT = 0x0004; + static final int BOUNDS_DIRTY_BIT = 0x0008; + static final int BOUNDING_LEAF_DIRTY_BIT = 0x0010; + static final int PRIORITY_DIRTY_BIT = 0x0020; + static final int POSITION_DIRTY_BIT = 0x0040; + static final int DISTANCE_GAIN_DIRTY_BIT = 0x0080; + static final int BACK_DISTANCE_GAIN_DIRTY_BIT = 0x0100; + static final int DIRECTION_DIRTY_BIT = 0x0200; + static final int ANGULAR_ATTENUATION_DIRTY_BIT = 0x0400; + static final int RATE_DIRTY_BIT = 0x0800; + + static final int BOUNDS_CHANGED = + BOUNDS_DIRTY_BIT | BOUNDING_LEAF_DIRTY_BIT; + + static final int ATTRIBUTE_DIRTY_BITS = + SOUND_DATA_DIRTY_BIT | INITIAL_GAIN_DIRTY_BIT | + LOOP_COUNT_DIRTY_BIT | PRIORITY_DIRTY_BIT | + RATE_DIRTY_BIT; + + static final int POSITIONAL_DIRTY_BITS = + ATTRIBUTE_DIRTY_BITS | + POSITION_DIRTY_BIT | DISTANCE_GAIN_DIRTY_BIT; + + static final int DIRECTIONAL_DIRTY_BITS = + POSITIONAL_DIRTY_BITS | BACK_DISTANCE_GAIN_DIRTY_BIT | + DIRECTION_DIRTY_BIT | ANGULAR_ATTENUATION_DIRTY_BIT; + + // All attribute bits that are specifically set or cleared for any node */ + static final int ALL_ATTIBS_DIRTY_BITS = 0x0FFF; + + // State Dirty bit flags + // This bitmask is set when scene graph state is changed. + static final int LIVE_DIRTY_BIT = 0x0001; + static final int IMMEDIATE_MODE_DIRTY_BIT = 0x0002; + static final int LOAD_SOUND_DIRTY_BIT = 0x0004; + static final int RELEASE_DIRTY_BIT = 0x0008; + static final int CONTINUOUS_DIRTY_BIT = 0x0010; + static final int ENABLE_DIRTY_BIT = 0x0020; + static final int MUTE_DIRTY_BIT = 0x0040; + static final int PAUSE_DIRTY_BIT = 0x0080; + static final int XFORM_DIRTY_BIT = 0x8000; + + // All attribute bits that are specifically set or cleared for any node */ + static final int ALL_STATE_DIRTY_BITS = 0x80FF; + + // The type of sound node: Background, Point, Cone + int soundType = NULL_SOUND; + + // A back reference to the scene graph sound, when this is a mirror sound + SoundRetained sgSound = null; + + // A HashKey for sounds in a shared group + HashKey key = null; + + // An array of mirror sounds, one for each instance of this sound in a + // shared group. Entry 0 is the only one valid if we are not in a shared + // group. + SoundRetained[] mirrorSounds = new SoundRetained[1]; + + // The number of valid sounds in mirrorSounds + int numMirrorSounds = 0; + + /** + * Array of references to sound scheduler atoms associated with this node. + * For each view that a sound node is associated with a sound scheduler + * atom is created and maintained + */ + // for a particular view that are playing either audibly or silently. + private SoundSchedulerAtom[] loadedAtoms = new SoundSchedulerAtom[1]; + private int atomCount = 0; + + /** + * This is true when this sound is referenced in an immediate mode context + */ + boolean inImmCtx = false; + + /** + * Load Sound Data Status + */ + static final int LOAD_COMPLETE = 2; + // load requested but could not be performed due because sound not live + static final int LOAD_PENDING = 1; + static final int LOAD_NULL = 0; + static final int LOAD_FAILED = -1; + int loadStatus = LOAD_NULL; + long duration = Sound.DURATION_UNKNOWN; + + // Static initializer for SoundRetained class + static { + VirtualUniverse.loadLibraries(); + } + + // Target threads to be notified when sound changes + static final int targetThreads = J3dThread.UPDATE_SOUND | + J3dThread.SOUND_SCHEDULER; + + // Is true, if the mirror light is viewScoped + boolean isViewScoped = false; + + + /** + * Dispatch a message about a sound attribute change + */ + void dispatchAttribChange(int dirtyBit, Object argument) { + // Send message including a integer argument + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_SOUND | + J3dThread.SOUND_SCHEDULER; + createMessage.type = J3dMessage.SOUND_ATTRIB_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(dirtyBit); + if (inSharedGroup) + createMessage.args[2] = new Integer(numMirrorSounds); + else + createMessage.args[2] = new Integer(1); + createMessage.args[3] = mirrorSounds.clone(); + createMessage.args[4] = argument; + if (debugFlag) + debugPrint("dispatchAttribChange with " + dirtyBit); + VirtualUniverse.mc.processMessage(createMessage); + } + + /** + * Dispatch a message about a sound state change + */ + void dispatchStateChange(int dirtyBit, Object argument) { + // Send message including a integer argument + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_SOUND | + J3dThread.SOUND_SCHEDULER; + createMessage.type = J3dMessage.SOUND_STATE_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(dirtyBit); + if (inSharedGroup) + createMessage.args[2] = new Integer(numMirrorSounds); + else + createMessage.args[2] = new Integer(1); + createMessage.args[3] = mirrorSounds.clone(); + createMessage.args[4] = argument; + if (debugFlag) + debugPrint("dispatchStateChange with " + dirtyBit); + VirtualUniverse.mc.processMessage(createMessage); + } + + /** + * Assign value into sound data field + * @param soundData description of sound source data + */ + void setSoundDataState(MediaContainer soundData) { + this.soundData = soundData; + } + + /** + * Associates sound data with this sound source node + * Attempt to load sound + * @param soundData descrition of sound source data + */ + void setSoundData(MediaContainer soundData) { + // if resetting soundData to the same value don't bother doing anything + if (this.soundData == soundData) { + return; + } + + if (this.soundData != null) { + // this sound node had older sound data; clear it out + ((MediaContainerRetained)this.soundData.retained).removeUser(this); + } + + if (source != null && source.isLive()) { + if (this.soundData != null) { + ((MediaContainerRetained)this.soundData.retained).clearLive(refCount); + } + + if (soundData != null) { + ((MediaContainerRetained)soundData.retained).setLive(inBackgroundGroup, refCount); + ((MediaContainerRetained)soundData.retained).addUser(this); + } + } + + this.soundData = soundData; + dispatchAttribChange(SOUND_DATA_DIRTY_BIT, soundData); + + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves sound data associated with this sound source node + * @return sound source data container + */ + MediaContainer getSoundData() { + return ( this.soundData ); + } + + + /** + * Set the gain scale factor applied to this sound + * @param amplitude gain scale factor + */ + void setInitialGain(float scaleFactor) { + if (scaleFactor < 0.0f) + this.initialGain = 0.0f; + else + this.initialGain = scaleFactor; + + dispatchAttribChange(INITIAL_GAIN_DIRTY_BIT, (new Float(scaleFactor))); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + /** + * Get the overall gain (applied to the sound data associated with source). + * @return overall gain of sound source + */ + float getInitialGain() { + return (float) this.initialGain; + } + + + /** + * Sets the sound's loop count + * @param loopCount number of times sound is looped during play + */ + void setLoop(int loopCount) { + if (loopCount < -1) + this.loopCount = -1; + else + this.loopCount = (int) loopCount; + if (debugFlag) + debugPrint("setLoopCount called with " + this.loopCount); + + dispatchAttribChange(LOOP_COUNT_DIRTY_BIT, (new Integer(loopCount))); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves the loop count + * @return loop count for data associated with sound + */ + int getLoop() { + return (int) this.loopCount; + } + + /** + * Enable or disable the release flag for this sound source + * @param state flag denoting release sound before stopping + */ + void setReleaseEnable(boolean state) { + this.release = state; + dispatchAttribChange(RELEASE_DIRTY_BIT, (state ? Boolean.TRUE: Boolean.FALSE)); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves release flag for sound associated with this source node + * @return sound's release flag + */ + boolean getReleaseEnable() { + return (boolean) this.release; + } + + /** + * Enable or disable continuous play flag + * @param state denotes if sound continues playing silently when deactivated + */ + void setContinuousEnable(boolean state) { + this.continuous = state; + dispatchAttribChange(CONTINUOUS_DIRTY_BIT, (state ? Boolean.TRUE: Boolean.FALSE)); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves sound's continuous play flag + * @return flag denoting if deactivated sound silently continues playing + */ + boolean getContinuousEnable() { + return (boolean) this.continuous; + } + + /** + * Sets the flag denotine sound enabled/disabled and sends a message + * for the following to be done: + * If state is true: + * if sound is not playing, sound is started. + * if sound is playing, sound is stopped, then re-started. + * If state is false: + * if sound is playing, sound is stopped + * @param state true or false to enable or disable the sound + */ + void setEnable(boolean state) { + enable = state; + // QUESTION: Is this still valid code? + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + dispatchStateChange(ENABLE_DIRTY_BIT, (new Boolean(enable))); + } + + /** + * Retrieves sound's enabled flag + * @return sound enabled flag + */ + boolean getEnable() { + return enable; + } + + /** + * Set the Sound's scheduling region. + * @param region a region that contains the Sound's new scheduling region + */ + void setSchedulingBounds(Bounds region) { + if (region != null) { + schedulingRegion = (Bounds) region.clone(); + if (staticTransform != null) { + schedulingRegion.transform(staticTransform.transform); + } + // QUESTION: Clone into transformedRegion IS required. Why? + transformedRegion = (Bounds) schedulingRegion.clone(); + if (debugFlag) + debugPrint("setSchedulingBounds for a non-null region"); + } + else { + schedulingRegion = null; + // QUESTION: Is transformedRegion of node (not mirror node) + // even looked at??? + transformedRegion = null; + if (debugFlag) + debugPrint("setSchedulingBounds for a NULL region"); + } + // TODO: test that this works - could not new Bounds() since + // Bounds is an abstract class and can't be instantiated + dispatchAttribChange(BOUNDS_DIRTY_BIT, region); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Get the Sound's scheduling region. + * @return this Sound's scheduling region information + */ + Bounds getSchedulingBounds() { + Bounds b = null; + + if (this.schedulingRegion != null) { + b = (Bounds) schedulingRegion.clone(); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + b.transform(invTransform); + } + } + return b; + } + + /** + * Set the Sound's scheduling region to the specified Leaf node. + */ + void setSchedulingBoundingLeaf(BoundingLeaf region) { + int i; + int numSnds = numMirrorSounds; + if (numMirrorSounds == 0) + numSnds = 1; + + if ((boundingLeaf != null) && + (source != null && source.isLive())) { + // Remove the mirror lights as users of the original bounding leaf + for (i = 0; i < numSnds; i++) { + boundingLeaf.mirrorBoundingLeaf.removeUser(mirrorSounds[i]); + } + } + + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + // Add all mirror sounds as user of this bounding leaf + if (source != null && source.isLive()) { + for (i = 0; i < numSnds; i++) { + boundingLeaf.mirrorBoundingLeaf.addUser(mirrorSounds[i]); + } + } + } else { + boundingLeaf = null; + } + // TODO: since BoundingLeaf constructor only takes Bounds + // test if region passed into dispatchAttribChange correctly. + dispatchAttribChange(BOUNDING_LEAF_DIRTY_BIT, region); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Get the Sound's scheduling region + */ + BoundingLeaf getSchedulingBoundingLeaf() { + if (boundingLeaf != null) { + return((BoundingLeaf)boundingLeaf.source); + } else { + return null; + } + } + + // The update Object function. + synchronized void updateMirrorObject(Object[] objs) { + Transform3D trans = null; + int component = ((Integer)objs[1]).intValue(); + if (component == -1) { // update everything + // object 2 contains the mirror object that needs to be + // updated + initMirrorObject(((SoundRetained)objs[2])); + } + + // call the parent's mirror object update routine + super.updateMirrorObject(objs); + + } + + void updateBoundingLeaf(long refTime) { + // This is necessary, if for example, the region + // changes from sphere to box. + if (boundingLeaf != null && boundingLeaf.switchState.currentSwitchOn) { + transformedRegion = boundingLeaf.transformedRegion; + } else { // evaluate schedulingRegion if not null + if (schedulingRegion != null) { + transformedRegion = schedulingRegion.copy(transformedRegion); + transformedRegion.transform(schedulingRegion, + getLastLocalToVworld()); + } else { + transformedRegion = null; + } + } + } + + + /** + * Set sound's proirity value. + * @param priority value used to order sound's importance for playback. + */ + void setPriority(float rank) { + if (rank == this.priority) + // changing priority is expensive in the sound scheduler(s) + // so only dispatch a message if 'new' priority value is really + // different + return; + + this.priority = rank; + dispatchAttribChange(PRIORITY_DIRTY_BIT, (new Float(rank))); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves sound's priority value. + * @return sound priority value + */ + float getPriority() { + return (this.priority); + } + + + /** + * Retrieves sound's duration in milliseconds + * @return sound's duration, returns DURATION_UNKNOWN if duration could + * not be queried from the audio device + */ + long getDuration() { + return (duration); + } + + + /** + * Set scale factor + * @param scaleFactor applied to sound playback rate + */ + void setRateScaleFactor(float scaleFactor) { + this.rate = scaleFactor; + dispatchAttribChange(RATE_DIRTY_BIT, (new Float(scaleFactor))); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves sound's rate scale factor + * @return sound rate scale factor + */ + float getRateScaleFactor() { + return (this.rate); + } + + void changeAtomList(SoundSchedulerAtom atom, int loadStatus) { + if (atom == null) + return; + if (loadStatus == SoundRetained.LOAD_COMPLETE) { + // atom is successfully loaded, so add this atom to array of atoms + // associated with this sound, if not already in list + for (int i=0; i<atomCount; i++) { + if (atom == loadedAtoms[i]) + return; + } + // add atom to list + atomCount++; + int currentArrayLength = loadedAtoms.length; + if (atomCount > currentArrayLength) { + // expand array - replace with a larger array + loadedAtoms = new SoundSchedulerAtom[2*currentArrayLength]; + } + loadedAtoms[atomCount-1] = atom; // store reference to new atom + // all atoms sample durations SHOULD be the same so store it in node + this.duration = atom.sampleLength; // TODO: refine later? in ms + } + else { // atom is NOT loaded or has been unloaded; remove from list + if (atomCount == 0) + return; + + // remove atom from array of playing atoms if it is in list + boolean atomFound = false; + int i; + for (i=0; i<atomCount; i++) { + if (atom == loadedAtoms[i]) { + atomFound = true; + continue; + } + } + if (!atomFound) + return; + + // otherwise remove atom from list by close up list + for (int j=i; j<atomCount; j++) { + loadedAtoms[j] = loadedAtoms[j+1]; + } + atomCount--; + if (atomCount == 0) + this.duration = Sound.DURATION_UNKNOWN; // clear sound duration + } + } + + /** + * Retrieves sound's ready state for ALL active views. + * For this node, the list of sound scheduler atoms associated with + * each view is maintained. The 'loaded' (=is ready) state is + * true only if the following are true for all views/sound schedulers: + * + * <ul> + * 1) the Sound node has a non-null sound data and this data has + * sucessfully been loaded/opened/copied/attached;<br> + * 2) the Sound node is live;<br> + * 3) there is at least one active View in the Universe; and<br> + * 4) an instance of an AudioDevice is attached to the current + * PhysicalEnvironment. + * </ul> + * + * @return true if potentially playable (audibly or silently); false otherwise + */ + boolean isReady() { + // all the atoms in the atom list must be are ready for this + // method to return true + // if any non-null atoms are found NOT ready, return false. + boolean atomFoundReady = true; + for (int i=0; i<atomCount; i++) { + SoundSchedulerAtom atom = loadedAtoms[i]; + if (atom == null || atom.soundScheduler == null) + continue; + else + if (atom.loadStatus == SoundRetained.LOAD_COMPLETE) { + atomFoundReady = true; + continue; + } + else + return false; + } + if (atomFoundReady) // at least on atom found ready + return true; + else + // not even one atom is associated with node so none are loaded + return false; + } + + /** + * Retrieves sound's ready state for a particular view. + * For this node, the list of sound scheduler atoms associated with + * each view is maintained. The 'loaded' (=is ready) state is + * true only if the following are true for the given view: + * + * <ul> + * 1) the Sound node has a non-null sound data and this data has + * sucessfully been loaded/opened/copied/attached;<br> + * 2) the Sound node is live;<br> + * 3) the given View is active in the Universe; and<br> + * 4) an instance of an AudioDevice is attached to the current + * PhysicalEnvironment. + * </ul> + * + * @param viewRef view to test sound readiness for + * @return true if potentially playable (audibly or silently); false otherwise + */ + boolean isReady(View viewRef) { + // if an atom in the atom list that is associated with the + // given view is found and has been loaded than return true, + // otherwise return false. + if (viewRef == null) + return false; + for (int i=0; i<atomCount; i++) { + SoundSchedulerAtom atom = loadedAtoms[i]; + if (atom == null || atom.soundScheduler == null) + continue; + if (atom.soundScheduler.view == viewRef) + if (atom.loadStatus != SoundRetained.LOAD_COMPLETE) + return false; + else + return true; + else // atom is not associated with given referenced view + continue; + } + return false; // sound scheduler atom for given view not found + + } + + // ******************************* + // Play Status - isPlaying states + // ******************************* + + /** + * Retrieves sound's playing status + * true if potentially audible (enabled and active) on ANY audio device + * false otherwise + * @return sound playing flag + */ + boolean isPlaying() { + for (int i=0; i<atomCount; i++) { + SoundSchedulerAtom atom = loadedAtoms[i]; + if (atom == null || atom.soundScheduler == null) + continue; + if (atom.status == SoundSchedulerAtom.SOUND_AUDIBLE) + return true; + else + continue; // look for at lease one atom that is playing + } + // not even one atom is associated with this node so none are playing + return false; + } + + /** + * Retrieves sound's playing status for a particular view + * true if potentially audible (enabled and active) on audio device + * associated with the given view + * false otherwise + * @param viewRef view to test sound playing state for + * @return sound playing flag + */ + boolean isPlaying(View viewRef) { + if (viewRef == null) + return false; + for (int i=0; i<atomCount; i++) { + SoundSchedulerAtom atom = loadedAtoms[i]; + if (atom == null || atom.soundScheduler == null) + continue; + if (atom.soundScheduler.view == viewRef) { + if (atom.status == SoundSchedulerAtom.SOUND_AUDIBLE) + return true; + else + return false; + } + else // atom is not associated with given referenced view + continue; + } + return false; // atom associated with this view not found in list + } + + /** + * Retrieves sound's playing silently status + * true if enabled but not active (on any device) + * false otherwise + * @return sound playing flag + */ + boolean isPlayingSilently() { + for (int i=0; i<atomCount; i++) { + SoundSchedulerAtom atom = loadedAtoms[i]; + if (atom == null || atom.soundScheduler == null) + continue; + if (atom.status == SoundSchedulerAtom.SOUND_SILENT) + return true; + else + return false; + } + return false; // atom not found in list or not playing audibilly + } + + /** + * Retrieves sound's playing silently status for a particular view + * true if potentially audible (enabled and active) on audio device + * associated with the given view + * false otherwise + * @param viewRef view to test sound playing silently state for + * @return sound playing flag + */ + boolean isPlayingSilently(View viewRef) { + if (viewRef == null) + return false; + for (int i=0; i<atomCount; i++) { + SoundSchedulerAtom atom = loadedAtoms[i]; + if (atom == null || atom.soundScheduler == null) + continue; + if (atom.soundScheduler.view == viewRef) { + if (atom.status == SoundSchedulerAtom.SOUND_SILENT) + return true; + else + return false; + } + else // atom is not associated with given referenced view + continue; + } + return false; // atom associated with this view not found in list + } + + /** + * Retrieves number of channels allocated for this sound on the primary + * view's audio device. + * @return number of channels used by sound across all devices + */ + int getNumberOfChannelsUsed() { + // retrieves the number of channels used by the atom that is: + // loaded, and + // playing either audibily or silently + // on the device associated with the primary view. + View primaryView = this.universe.getCurrentView(); + if (primaryView == null) + return 0; + + // find atom associated with primary view (VirtualUniverse currentView) + // then return the number of channels associated with that atom + SoundSchedulerAtom atom; + for (int i=0; i<atomCount; i++) { + atom = loadedAtoms[i]; + if (atom == null || atom.soundScheduler == null) + continue; + if (atom.soundScheduler.view == primaryView) { + return atom.numberChannels; + } + } + return 0; // atom associated with primary view not found + } + + /** + * Retrieves number of channels allocated for this sound on the audio + * devices associated with a given view. + * @param viewRef view to test sound playing silently state for + * @return number of channels used by this sound on a particular device + */ + int getNumberOfChannelsUsed(View viewRef) { + // retrieves the number of channels used by the atom that is: + // loaded, and + // playing either audibily or silently + // on the device associated with the given view. + if (viewRef == null) + return 0; + SoundSchedulerAtom atom; + for (int i=0; i<atomCount; i++) { + atom = loadedAtoms[i]; + if (atom == null || atom.soundScheduler == null) + continue; + if (atom.soundScheduler.view == viewRef) { + return atom.numberChannels; + } + } + return 0; // atom associated with primary view not found + } + + /** + * Set mute state flag. If the sound is playing it will be set to + * play silently + * @param state flag + * @since Java 3D 1.3 + */ + void setMute(boolean state) { + this.mute = state; + dispatchAttribChange(MUTE_DIRTY_BIT, (state ? Boolean.TRUE: Boolean.FALSE)); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves sound Mute state. + * A return value of true does not imply that the sound has + * been started playing or is still playing silently. + * @return mute state flag + * @since Java 3D 1.3 + */ + boolean getMute() { + return (boolean) this.mute; + } + + /** + * Set pause state flag. If the sound is playing it will be paused + * @param state flag + * @since Java 3D 1.3 + */ + void setPause(boolean state) { + this.pause = state; + dispatchAttribChange(PAUSE_DIRTY_BIT, (state ? Boolean.TRUE: Boolean.FALSE)); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieves sound Pause state. + * A return value of true does not imply that the sound has + * been started playing auditibly or silently. + * @return mute state flag + * @since Java 3D 1.3 + */ + boolean getPause() { + return (boolean) this.pause; + } + + + /** + * This sets the immedate mode context flag + */ + void setInImmCtx(boolean inCtx) { + inImmCtx = inCtx; + } + + /** + * This gets the immedate mode context flag + */ + boolean getInImmCtx() { + return (inImmCtx); + } + + /** + * This gets the mirror sound for this sound given the key. + */ + SoundRetained getMirrorSound(HashKey key) { + int i; + SoundRetained[] newSounds; + + if (inSharedGroup) { + for (i=0; i<numMirrorSounds; i++) { + if (mirrorSounds[i].key.equals(key)) { + return(mirrorSounds[i]); + } + } + if (numMirrorSounds == mirrorSounds.length) { + newSounds = new SoundRetained[numMirrorSounds*2]; + for (i=0; i<numMirrorSounds; i++) { + newSounds[i] = mirrorSounds[i]; + } + mirrorSounds = newSounds; + } + // mirrorSounds[numMirrorSounds] = (SoundRetained) this.clone(); + mirrorSounds[numMirrorSounds] = (SoundRetained) this.clone(); + //mirrorSounds[numMirrorSounds].key = new HashKey(key); + mirrorSounds[numMirrorSounds].key = key; + mirrorSounds[numMirrorSounds].sgSound = this; + return(mirrorSounds[numMirrorSounds++]); + } else { + if (mirrorSounds[0] == null) { + // mirrorSounds[0] = (SoundRetained) this.clone(true); + mirrorSounds[0] = (SoundRetained) this.clone(); + mirrorSounds[0].sgSound = this; + } + return(mirrorSounds[0]); + } + } + + synchronized void initMirrorObject(SoundRetained ms) { + GroupRetained group; + Transform3D trans; + Bounds region = null; + + ms.setSchedulingBounds(getSchedulingBounds()); + ms.setSchedulingBoundingLeaf(getSchedulingBoundingLeaf()); + ms.sgSound = sgSound; +/* +// QUESTION: these are not set in LightRetained??? + ms.key = null; + ms.mirrorSounds = new SoundRetained[1]; + ms.numMirrorSounds = 0; +*/ + ms.inImmCtx = inImmCtx; + ms.setSoundData(getSoundData()); + +// TODO: copy ms.atoms array from this.atoms + + ms.parent = parent; + ms.inSharedGroup = false; + ms.locale = locale; + ms.parent = parent; + ms.localBounds = (Bounds)localBounds.clone(); + + ms.transformedRegion = null; + if (boundingLeaf != null) { + if (ms.boundingLeaf != null) + ms.boundingLeaf.removeUser(ms); + ms.boundingLeaf = boundingLeaf.mirrorBoundingLeaf; + // Add this mirror object as user + ms.boundingLeaf.addUser(ms); + ms.transformedRegion = ms.boundingLeaf.transformedRegion; + } + else { + ms.boundingLeaf = null; + } + + if (schedulingRegion != null) { + ms.schedulingRegion = (Bounds) schedulingRegion.clone(); + // Assign region only if bounding leaf is null + if (ms.transformedRegion == null) { + ms.transformedRegion = (Bounds) ms.schedulingRegion.clone(); + ms.transformedRegion.transform(ms.schedulingRegion, + ms.getLastLocalToVworld()); + } + + } + else { + ms.schedulingRegion = null; + } + } + + void setLive(SetLiveState s) { + SoundRetained ms; + int i, j; + + if (debugFlag) + debugPrint("Sound.setLive"); + + if (inImmCtx) { + throw new + IllegalSharingException(J3dI18N.getString("SoundRetained2")); + } + super.setLive(s); + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("SoundRetained3")); + } + + if (this.loadStatus == LOAD_PENDING) { + if (debugFlag) + debugPrint("Sound.setLive load Sound"); + dispatchStateChange(LOAD_SOUND_DIRTY_BIT, soundData); + } + + if (this.soundData != null) { + ((MediaContainerRetained)this.soundData.retained).setLive(inBackgroundGroup, s.refCount); + } + + if (s.inSharedGroup) { + for (i=0; i<s.keys.length; i++) { + ms = this.getMirrorSound(s.keys[i]); + ms.localToVworld = new Transform3D[1][]; + ms.localToVworldIndex = new int[1][]; + + j = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + if(j < 0) { + System.out.println("SoundRetained : Can't find hashKey"); + } + + ms.localToVworld[0] = localToVworld[j]; + ms.localToVworldIndex[0] = localToVworldIndex[j]; + // If its view Scoped, then add this list + // to be sent to Sound Structure + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(ms); + s.scopedNodesViewList.add(s.viewLists.get(i)); + } else { + s.nodeList.add(ms); + } + // Initialization of the mirror object during the INSERT_NODE + // message (in updateMirrorObject) + if (s.switchTargets != null && s.switchTargets[i] != null) { + s.switchTargets[i].addNode(ms, Targets.SND_TARGETS); + } + ms.switchState = (SwitchState)s.switchStates.get(j); + if (s.transformTargets != null && + s.transformTargets[i] != null) { + s.transformTargets[i].addNode(ms, Targets.SND_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + } + } else { + ms = this.getMirrorSound(null); + ms.localToVworld = new Transform3D[1][]; + ms.localToVworldIndex = new int[1][]; + ms.localToVworld[0] = this.localToVworld[0]; + ms.localToVworldIndex[0] = this.localToVworldIndex[0]; + // If its view Scoped, then add this list + // to be sent to Sound Structure + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(ms); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(ms); + } + // Initialization of the mirror object during the INSERT_NODE + // message (in updateMirrorObject) + if (s.switchTargets != null && s.switchTargets[0] != null) { + s.switchTargets[0].addNode(ms, Targets.SND_TARGETS); + } + ms.switchState = (SwitchState)s.switchStates.get(0); + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(ms, Targets.SND_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + } + dispatchStateChange(LIVE_DIRTY_BIT, soundData); + s.notifyThreads |= targetThreads; + } + + void clearLive(SetLiveState s) { + SoundRetained ms; + + super.clearLive(s); + +// TODO: if (inSharedGroup) + + if (s.inSharedGroup) { + for (int i=0; i<s.keys.length; i++) { + ms = this.getMirrorSound(s.keys[i]); + if (s.switchTargets != null && + s.switchTargets[i] != null) { + s.switchTargets[i].addNode(ms, Targets.SND_TARGETS); + } + if (s.transformTargets != null && s.transformTargets[i] != null) { + s.transformTargets[i].addNode(ms, Targets.SND_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + // If its view Scoped, then add this list + // to be sent to Sound Structure + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(ms); + s.scopedNodesViewList.add(s.viewLists.get(i)); + } else { + s.nodeList.add(ms); + } + } + } else { + ms = this.getMirrorSound(null); + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(ms, Targets.SND_TARGETS); + } + if (s.transformTargets != null && + s.transformTargets[0] != null) { + s.transformTargets[0].addNode(ms, Targets.SND_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + // If its view Scoped, then add this list + // to be sent to Sound Structure + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(ms); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(ms); + } + } + s.notifyThreads |= targetThreads; + + if (this.soundData != null) { + ((MediaContainerRetained)this.soundData.retained).clearLive(s.refCount); + } + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + if (schedulingRegion != null) { + schedulingRegion.transform(xform.transform); + } + } + +/* + // This makes passed in sound look just like this sound +// QUESTION: DOesn't appread to be called +// TODO: ...if so, remove... + synchronized void update(SoundRetained sound) { + if (debugFlag) + debugPrint("Sound.update ******** entered ***** this = " + this + + ", and sound param = " + sound); + + sound.soundData = soundData; + sound.initialGain = initialGain; + sound.loopCount = loopCount; + sound.release = release; + sound.continuous = continuous; + sound.enable = enable; // used to be 'on' + sound.inImmCtx = inImmCtx; + +// QUESTION: +// This line removed from 1.1.1 version; why ??? + sound.currentSwitchOn = currentSwitchOn; + +// NEW: + sound.priority = priority; + +// QUESTION: With code below, no sound schedulingRegion found +// sound.schedulingRegion = schedulingRegion; +// sound.boundingLeaf = boundingLeaf; +// TODO: clone of region used in Traverse code, why not here??? +// if (schedulingRegion != null) +// sound.schedulingRegion = (Bounds)schedulingRegion.clone(); +// TODO: BoundingLeafRetained boundingLeaf ... +// WHAT ABOUT transformedRegion?? + +// TODO: Update ALL fields +// ALL THE BELOW USED TO COMMENTED OUT vvvvvvvvvvvvvvvvvvvvvvvvvvvvv + sound.sampleLength = sampleLength; + sound.loopStartOffset = loopStartOffset; + sound.loopLength = loopLength; + sound.attackLength = attackLength; + sound.releaseLength = releaseLength; + + sound.sgSound = sgSound; + sound.key = key; + sound.numMirrorSounds = numMirrorSounds; + for (int index=0; index<numMirrorSounds; index++) + sound.mirrorSounds = mirrorSounds; + sound.universe = universe; + if (universe.sounds.contains(sound) == false) { + universe.sounds.addElement(sound); + } + if (debugFlag) + debugPrint("update****************************** exited"); +^^^^^^^^^^^ COMMENTED OUT + } +*/ + + + // Called on mirror object +// QUESTION: doesn't transformed region need to be saved??? + void updateTransformChange() { + // If bounding leaf is null, tranform the bounds object + if (debugFlag) + debugPrint("SoundRetained.updateTransformChange()"); + if (boundingLeaf == null) { + if (schedulingRegion != null) { + transformedRegion = schedulingRegion.copy(transformedRegion); + transformedRegion.transform(schedulingRegion, + getLastLocalToVworld()); + } + } + dispatchStateChange(XFORM_DIRTY_BIT, null); + } + +// QUESTION: +// Clone method (from 1.1.1 version) removed!?!?!? yet LightRetained has it + + + + // Debug print mechanism for Sound nodes + static final boolean debugFlag = false; + static final boolean internalErrors = false; + + void debugPrint(String message) { + if (debugFlag) { + System.out.println(message); + } + } + void getMirrorObjects(ArrayList leafList, HashKey key) { + if (key == null) { + leafList.add(mirrorSounds[0]); + } + else { + for (int i=0; i<numMirrorSounds; i++) { + if (mirrorSounds[i].key.equals(key)) { + leafList.add(mirrorSounds[i]); + break; + } + } + + } + } + +} diff --git a/src/classes/share/javax/media/j3d/SoundScheduler.java b/src/classes/share/javax/media/j3d/SoundScheduler.java new file mode 100644 index 0000000..ba6cf25 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SoundScheduler.java @@ -0,0 +1,3241 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; +import java.util.Vector; +import java.util.ArrayList; +import java.net.URL; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.Arrays; + +import java.awt.*; +import java.awt.event.*; + +/** + * This structure parallels the RenderBin structure and + * is used for sounds + */ +class SoundScheduler extends J3dStructure { + + /** + * The View that owns this SoundScheduler + */ + View view = null; + + /** + * This boolean tells the thread to suspend itself. + * This is true ONLY when everythings ready to render using run loop + */ + boolean ready = false; + + /** + * The ViewPlatform that is associated with this SoundScheduler + */ + ViewPlatformRetained viewPlatform = null; + + /** + * The GraphicContext3D that we are currently unning in. + */ + GraphicsContext3D graphicsCtx = null; + + /** + * Maintain what reference to the last AuralAttributes found active + * was so that only if there was a change do we need to reset these + * parameters in the AudioDevice3D. + */ + AuralAttributesRetained lastAA = null; + + /** + * Since AuralAttribute gain scale factor is multipled with sound's + * initialGain scale factor, any change in AuralAttrib gain scale + * factor should force an update of all active sounds' gains + * Also, change in AuralAttributes should force a sound update + * even if no other sound field changes occurred. + */ + boolean resetAA = true; + + /** + * Audio Device + */ + AudioDevice audioDevice = null; + AudioDevice3D audioDevice3D = null; + AudioDevice3DL2 audioDevice3DL2 = null; + int totalChannels = 0; + + /** + * Array of SoundScapeRetained nodes that intersect the viewPlatform + * This list is a subset of the soundscapes array, and is used when + * selecting the closest Soundscape. + * Maintained as an expandable array. + */ + SoundscapeRetained[] intersectedSoundscapes = new SoundscapeRetained[32]; + + /** + * Array of Bounds nodes for the corresponding intersectedSoundscapes + * array. This array is used when selecting the closest Soundscape. + * This list is used when selecting the closest Soundscape. + * Maintained as an expandable array. + */ + Bounds[] intersectedRegions = new Bounds[32]; + + /** + * Reference to last processed region within run(). + * Maintained to avoid re-transforming this bounds. + */ + Bounds region = null; + + /** + * An array of prioritized sounds currently playing "live" sounds. + * This prioritized sound list is NO longer re-create instead sounds + * are insert, shuffled or removed as messages are processed. + */ + // TODO: (Enhancement) should have a seperate list for + // background sound and a list for positional sounds + ArrayList prioritizedSounds = new ArrayList(); + + /** + * Current number of scene graph sound nodes in the universe + */ + int nRetainedSounds = -1; // none calculated yet + + /** + * Current number of immediate mode sound nodes in the universe + */ + int nImmedSounds = -1; // none calculated yet + + /** + * Current active (selected) attribute node in the sceneGraph + */ + AuralAttributesRetained aaRetained = null; + + // variables for processing transform messages + boolean transformMsg = false; + UpdateTargets targets = null; + + + /** + * Current active (selected) attribute node in the sceneGraph + */ + AuralAttributesRetained aaImmed = null; + + // Dirty flags for fields and parameters that are unique to the + // Sound Scheduler or the Audio Device + // Any listener (body) and/or view transform changes processed in + // CanvasViewCache force one of these flags to be set. + static final int EAR_POSITIONS_CHANGED = 0x0001; + static final int EYE_POSITIONS_CHANGED = 0x0002; + static final int IMAGE_PLATE_TO_VWORLD_CHANGED = 0x0004; + static final int HEAD_TO_VWORLD_CHANGED = 0x0008; + static final int LISTENER_CHANGED = 0x000F;// all of the above + private int listenerUpdated = LISTENER_CHANGED; + + /** + * Temporary flag that's denotes that a positional sound was processed + * in the current loop of renderChange(). + */ + private boolean positionalSoundUpdated = false; + + /** + * Temporary flag that's denotes that some field auralAttribute was changed + */ + private boolean auralAttribsChanged = true; // force processing 1st x + + private boolean stallThread = false; + + int lastEventReceived = WindowEvent.WINDOW_CLOSED; + + /** + * Constructs a new SoundScheduler + */ + SoundScheduler(VirtualUniverse u, View v) { + super(u, J3dThread.SOUND_SCHEDULER); + // vworldToVpc.setIdentity(); + universe = u; + view = v; + reset(v); + } + + + // NOTE: processMessage only called with updatethread.active true + void processMessages(long referenceTime) { + J3dMessage[] messages = getMessages(referenceTime); + int nMsg = getNumMessage(); + J3dMessage m; + int nSounds; + + if (nMsg > 0) { + for (int i=0; i < nMsg; i++) { + m = messages[i]; + + switch (m.type) { + case J3dMessage.INSERT_NODES: + insertNodes(m); + break; + case J3dMessage.REMOVE_NODES: + removeNodes(m); + break; + case J3dMessage.SOUND_ATTRIB_CHANGED: + changeNodeAttrib(m); + break; + case J3dMessage.SOUND_STATE_CHANGED: + changeNodeState(m); + break; + case J3dMessage.BOUNDINGLEAF_CHANGED: + processBoundingLeafChanged(m); + break; + case J3dMessage.SOUNDSCAPE_CHANGED: + SoundscapeRetained ss = (SoundscapeRetained)m.args[0]; + if (universe.soundStructure.isSoundscapeScopedToView(ss, view)) { + auralAttribsChanged = true; + changeNodeAttrib(m); + } + break; + case J3dMessage.AURALATTRIBUTES_CHANGED: + auralAttribsChanged = true; + changeNodeAttrib(m); + break; + case J3dMessage.MEDIA_CONTAINER_CHANGED: + changeNodeAttrib(m); + break; + case J3dMessage.TRANSFORM_CHANGED: + transformMsg = true; + auralAttribsChanged = true; + break; + case J3dMessage.RENDER_IMMEDIATE: + processImmediateNodes(m.args, referenceTime); + break; + case J3dMessage.VIEWSPECIFICGROUP_CHANGED: + processViewSpecificGroupChanged(m); + break; + case J3dMessage.UPDATE_VIEW: + if (debugFlag) + debugPrint(".processMessage() UPDATE_VIEW"); + // NOTE: can only rely on seeing UPDATE_VIEW when canvas [re]Created + // AND when view deactivated... + // NOTE: + // temp work-around + // calling prioritizeSounds() wipes out old atom fields + // QUESTION: prioritizedSound is NEVER empty - why if size is 0 can + // .isEmpty return anything but TRUE??? + // + if (prioritizedSounds.isEmpty()) { + nSounds = prioritizeSounds(); + } + break; + case J3dMessage.SWITCH_CHANGED: + if (debugFlag) + debugPrint(".processMessage() " + + "SWITCH_CHANGED ignored"); + break; + } // switch + m.decRefcount(); + } // for + if (transformMsg) { + targets = universe.transformStructure.getTargetList(); + updateTransformChange(targets, referenceTime); + transformMsg = false; + targets = null; + } + Arrays.fill(messages, 0, nMsg, null); + } + + // Call renderChanges within try/catch so errors won't kill + // the SoundScheduler. + try { + renderChanges(); + } + catch (RuntimeException e) { + System.err.println("Exception occurred " + + "during Sound rendering:"); + e.printStackTrace(); + } + + // what if the user/app makes no change to scenegraph? + // must still re-render after retest for sound complete + // calculate which sound will finished first and set a + // wait time to this shortest time so that scheduler is + // re-entered to process sound complete. + + long waitTime = shortestTimeToFinish(); + + if (waitTime == 0L) { + // come right back + if (debugFlag) + debugPrint(".processMessage calls sendRunMessage " + + "for immediate processing"); + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.SOUND_SCHEDULER); + } + else if (waitTime > 0L) { + // Use TimerThread to send message with sounds complete. + // This uses waitForElapse time to sleep for at least the duration + // returned by shortestTimeToFinish method. + if (debugFlag) + debugPrint(".processMessage calls sendRunMessage " + + "with wait time = " + waitTime ); + // QUESTION (ISSUE): even when this is set to a large time + // processMessage is reentered immediately. + // Why is timer thread not waiting?? + VirtualUniverse.mc.sendRunMessage(waitTime, view, + J3dThread.SOUND_SCHEDULER); + } + } + + void insertNodes(J3dMessage m) { + Object[] nodes = (Object[])m.args[0]; + ArrayList viewScopedNodes = (ArrayList)m.args[3]; + ArrayList scopedNodesViewList = (ArrayList)m.args[4]; + Object node; + + for (int i=0; i<nodes.length; i++) { + node = (Object) nodes[i]; + if (node instanceof SoundRetained) { + nRetainedSounds++; + // insert sound node into sound scheduler's prioritized list + addSound((SoundRetained) node); + } + else if (node instanceof SoundscapeRetained) { + auralAttribsChanged = true; + } + else if (node instanceof AuralAttributesRetained) { + auralAttribsChanged = true; + } + else if (node instanceof ViewPlatformRetained) { + // TODO: don't support multiple viewPlatforms per scheduler + /* + // useful for resetting VP ?? + addViewPlatform((ViewPlatformRetained) node); + */ + if (debugFlag) { + debugPrint(".insertNodes() viewPlatformRetained not supported yet"); + } + } + } + + // Handle ViewScoped Nodes + if (viewScopedNodes != null) { + int size = viewScopedNodes.size(); + int vlsize; + for (int i = 0; i < size; i++) { + node = (NodeRetained)viewScopedNodes.get(i); + ArrayList vl = (ArrayList) scopedNodesViewList.get(i); + // If the node object is scoped to this view, then .. + if (vl.contains(view)) { + if (node instanceof SoundRetained) { + nRetainedSounds++; + // insert sound node into sound scheduler's prioritized list + addSound((SoundRetained) node); + } + else if (node instanceof SoundscapeRetained) { + auralAttribsChanged = true; + } + } + } + } + } + + /** + * Add sound to sounds list. + */ + void addSound(SoundRetained sound) { + if (sound == null) + return; + if (debugFlag) + debugPrint(".addSound()"); + synchronized (prioritizedSounds) { + addPrioritizedSound(sound); + } + } // end addSound + + + /** + * Node removed from tree + */ + void removeNodes(J3dMessage m) { + Object[] nodes = (Object[])m.args[0]; + ArrayList viewScopedNodes = (ArrayList)m.args[3]; + ArrayList scopedNodesViewList = (ArrayList)m.args[4]; + Object node; + + for (int i=0; i<nodes.length; i++) { + node = (Object) nodes[i]; + if (node instanceof SoundRetained) { + // sound is deactivated but NOT deleted + // incase sound is reattached + +// QUESTION: what's the difference in messages between really deleting +// a node and just deactivating it. +/* +// +// can't delete atom in case it's reactivitated +// + nRetainedSounds--; + deleteSound((SoundRetained) node); +// +// until there's a difference in messages between detaching and deleting +// a sound node, sound is stopped and atom enable state changed but atom +// is NOT deleted from list. +*/ + SoundSchedulerAtom soundAtom = null; + for (int arrIndx=1; ;arrIndx++) { + soundAtom = findSoundAtom((SoundRetained)node, + arrIndx); + if (soundAtom == null) + break; + stopSound(soundAtom, false); + } + } + else if (node instanceof SoundscapeRetained) { + auralAttribsChanged = true; + } + } + // Handle ViewScoped Nodes + if (viewScopedNodes != null) { + int size = viewScopedNodes.size(); + int vlsize; + for (int i = 0; i < size; i++) { + node = (NodeRetained)viewScopedNodes.get(i); + ArrayList vl = (ArrayList) scopedNodesViewList.get(i); + // If the node object is scoped to this view, then .. + if (vl.contains(view)) { + if (node instanceof SoundRetained) { + SoundSchedulerAtom soundAtom = null; + for (int arrIndx=1; ;arrIndx++) { + soundAtom = findSoundAtom((SoundRetained)node, + arrIndx); + if (soundAtom == null) + break; + stopSound(soundAtom, false); + } + } + else if (node instanceof SoundscapeRetained) { + auralAttribsChanged = true; + } + } + } + } + + } + + + // deletes all instances of the sound nodes from the priority list + void deleteSound(SoundRetained sound) { + if (sound != null) + return; + if (debugFlag) + debugPrint(".deleteSound()"); + synchronized (prioritizedSounds) { + if (!prioritizedSounds.isEmpty()) { + // find sound in list and remove it + int arrSize = prioritizedSounds.size(); + for (int index=0; index<arrSize; index++) { + SoundSchedulerAtom soundAtom = (SoundSchedulerAtom) + prioritizedSounds.get(index); + // QUESTION: which??? + if (soundAtom.sound == sound || + soundAtom.sound.sgSound == sound) { + stopSound(soundAtom, false); + prioritizedSounds.remove(index); + } + } + } + } + } + + + void changeNodeAttrib(J3dMessage m) { + Object node = m.args[0]; + Object value = m.args[1]; + int attribDirty = ((Integer)value).intValue(); + if (debugFlag) + debugPrint(".changeNodeAttrib:"); + + if (node instanceof SoundRetained && + universe.soundStructure.isSoundScopedToView(node, view)) { + + this.setAttribsDirtyFlag((SoundRetained)node, attribDirty); + if (debugFlag) + debugPrint(" Sound node dirty bit = " + attribDirty); + if ((attribDirty & SoundRetained.PRIORITY_DIRTY_BIT) > 0) { + shuffleSound((SoundRetained) node); + } + if ((attribDirty & SoundRetained.SOUND_DATA_DIRTY_BIT) >0) { + if (debugFlag) + debugPrint(".changeNodeAttrib " + + "SOUND_DATA_DIRTY_BIT calls loadSound"); + loadSound((SoundRetained) node, true); + } + if ((attribDirty & SoundRetained.MUTE_DIRTY_BIT) > 0) { + if (debugFlag) + debugPrint(" MuteDirtyBit is on"); + muteSound((SoundRetained) node); + } + if ((attribDirty & SoundRetained.PAUSE_DIRTY_BIT) > 0) { + if (debugFlag) + debugPrint(" PauseDirtyBit is on"); + pauseSound((SoundRetained) node); + } + } + else if (node instanceof SoundscapeRetained && + universe.soundStructure.isSoundscapeScopedToView(node, view)) { + auralAttribsChanged = true; + } + else if (node instanceof AuralAttributesRetained) { + auralAttribsChanged = true; + } + else if (node instanceof MediaContainerRetained) { + int listSize = ((Integer)m.args[2]).intValue(); + ArrayList userList = (ArrayList)m.args[3]; + for (int i = 0; i < listSize; i++) { + SoundRetained sound = (SoundRetained)userList.get(i); + if (sound != null) { + loadSound(sound, true); + if (debugFlag) + debugPrint(".changeNodeAttrib " + + "MEDIA_CONTAINER_CHANGE calls loadSound"); + } + } + } + } + + + void changeNodeState(J3dMessage m) { + Object node = m.args[0]; + Object value = m.args[1]; + if (debugFlag) + debugPrint(".changeNodeState:"); + if (node instanceof SoundRetained && universe.soundStructure.isSoundScopedToView(node, view)) { + int stateDirty = ((Integer)value).intValue(); + setStateDirtyFlag((SoundRetained)node, stateDirty); + if (debugFlag) + debugPrint(" Sound node dirty bit = "+stateDirty); + if ((stateDirty & SoundRetained.LIVE_DIRTY_BIT) > 0) { + if (debugFlag) + debugPrint(".changeNodeState LIVE_DIRTY_BIT " + + "calls loadSound"); + loadSound((SoundRetained) node, false); + } + if ((stateDirty & SoundRetained.ENABLE_DIRTY_BIT) > 0) { + if (debugFlag) + debugPrint(" EnableDirtyBit is on"); + if (((Boolean) m.args[4]).booleanValue()) { + enableSound((SoundRetained) node); + } else { + SoundSchedulerAtom soundAtom; + SoundRetained soundRetained = (SoundRetained) node; + for (int i=prioritizedSounds.size()-1; i >=0; i--) { + soundAtom = ((SoundSchedulerAtom)prioritizedSounds.get(i)); + if (soundAtom.sound.sgSound == soundRetained) { + // ignore soundRetained.release + // flag which is not implement + turnOff(soundAtom); + } + } + } + } + } + } + + void shuffleSound(SoundRetained sound) { + // Find sound atom that references this sound node and + // reinsert it into prioritized sound list by removing atom for + // this sound from priority list, then re-add it. + // Assumes priority has really changed since a message is not sent + // to the scheduler if the 'new' priority value isn't different. + deleteSound(sound); // remove atom for this sound + addSound(sound); // then re-insert it back into list in new position + } + + + void loadSound(SoundRetained sound, boolean forceReload) { + // find sound atom that references this sound node + // QUESTION: "node" probably not mirror node? + SoundSchedulerAtom soundAtom = null; + for (int i=1; ;i++) { + soundAtom = findSoundAtom(sound, i); + if (soundAtom == null) + break; + MediaContainer mediaContainer = sound.getSoundData(); + if (forceReload || + soundAtom.loadStatus != SoundRetained.LOAD_COMPLETE) { + if (debugFlag) + debugPrint(": not LOAD_COMPLETE - try attaching"); + attachSoundData(soundAtom, mediaContainer, forceReload); + } + } + } + + + void enableSound(SoundRetained sound) { + if (debugFlag) + debugPrint(".enableSound " + sound ); + // find sound atom that references this sound node + SoundSchedulerAtom soundAtom = null; + for (int i=1; ;i++) { + soundAtom = findSoundAtom(sound, i); + if (soundAtom == null) + break; + // Set atom enabled field based on current Sound node + // enable boolean flag + soundAtom.enable(sound.enable); + } + } + + + void muteSound(SoundRetained sound) { + // make mute pending + // mute -> MAKE-SILENT + // unmute -> MAKE-AUDIBLE + if (debugFlag) + debugPrint(".muteSound " + sound ); + // find sound atom that references this sound node + SoundSchedulerAtom soundAtom = null; + for (int i=1; ;i++) { + soundAtom = findSoundAtom(sound, i); + if (soundAtom == null) + break; + // Set atom mute field based on node current + // mute boolean flag + soundAtom.mute(sound.mute); + } + } + + void pauseSound(SoundRetained sound) { + // make pause pending + // Pause is a separate action + // When resumed it has to reset its state + // PAUSE_AUDIBLE + // PAUSE_SILENT + // RESUME_AUDIBLE + // RESUME_SILENT + // to whatever it was before + if (debugFlag) + debugPrint(".pauseSound " + sound ); + // find sound atom that references this sound node + SoundSchedulerAtom soundAtom = null; + for (int i=1; ;i++) { + soundAtom = findSoundAtom(sound, i); + if (soundAtom == null) + break; + // Set atom pause field based on node's current + // pause boolean flag + soundAtom.pause(sound.pause); + } + } + + void processImmediateNodes(Object[] args, long referenceTime) { + Object command = args[0]; + Object newNode = args[1]; + Object oldNode = args[2]; + Sound oldSound = (Sound)oldNode; + Sound newSound = (Sound)newNode; + int action = ((Integer)command).intValue(); + if (debugFlag) + debugPrint(".processImmediateNodes() - action = " + + action); + switch (action) { + case GraphicsContext3D.ADD_SOUND : + case GraphicsContext3D.INSERT_SOUND : + addSound((SoundRetained)newSound.retained); + nImmedSounds++; + break; + case GraphicsContext3D.REMOVE_SOUND : + deleteSound((SoundRetained)oldSound.retained); + nImmedSounds--; + break; + case GraphicsContext3D.SET_SOUND : + deleteSound((SoundRetained)oldSound.retained); + addSound((SoundRetained)newSound.retained); + break; + } + } + + + void updateTransformChange(UpdateTargets targets, long referenceTime) { + // node.updateTransformChange() called immediately rather than + // waiting for updateObject to be called and process xformChangeList + // which apprears to only happen when sound started... + + UnorderList arrList = targets.targetList[Targets.SND_TARGETS]; + if (arrList != null) { + int j,i; + Object nodes[], nodesArr[]; + int size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (j = 0; j<size; j++) { + nodes = (Object[])nodesArr[j]; + + + for (i = 0; i < nodes.length; i++) { + if (nodes[i] instanceof ConeSoundRetained && universe.soundStructure.isSoundScopedToView(nodes[i], view)) { + ConeSoundRetained cnSndNode = + (ConeSoundRetained)nodes[i]; + synchronized (cnSndNode) { + cnSndNode.updateTransformChange(); + } + // set XFORM_DIRTY_BIT in corresponding atom + setStateDirtyFlag((SoundRetained)nodes[i], + SoundRetained.XFORM_DIRTY_BIT); + } else if (nodes[i] instanceof PointSoundRetained && universe.soundStructure.isSoundScopedToView(nodes[i], view)) { + PointSoundRetained ptSndNode = + (PointSoundRetained)nodes[i]; + synchronized (ptSndNode) { + ptSndNode.updateTransformChange(); + } + // set XFORM_DIRTY_BIT in corresponding atom + setStateDirtyFlag((SoundRetained)nodes[i], + SoundRetained.XFORM_DIRTY_BIT); + } else if (nodes[i] instanceof SoundscapeRetained && universe.soundStructure.isSoundscapeScopedToView(nodes[i], view)) { + SoundscapeRetained sndScapeNode = + (SoundscapeRetained)nodes[i]; + synchronized (sndScapeNode) { + sndScapeNode.updateTransformChange(); + } + } + } + } + } + } + + + void updateTransformedFields(SoundRetained mirSound) { + if (mirSound instanceof ConeSoundRetained && universe.soundStructure.isSoundScopedToView(mirSound, view)) { + ConeSoundRetained cnSndNode = (ConeSoundRetained)mirSound; + synchronized (cnSndNode) { + cnSndNode.updateTransformChange(); + } + } + else if (mirSound instanceof PointSoundRetained && universe.soundStructure.isSoundScopedToView(mirSound, view)) { + PointSoundRetained ptSndNode = (PointSoundRetained)mirSound; + synchronized (ptSndNode) { + ptSndNode.updateTransformChange(); + } + } + } + + + void activate() { + updateThread.active = true; + if (debugFlag) + debugPrint(".activate(): calls sendRunMessage for immediate processing"); + VirtualUniverse.mc.sendRunMessage(universe, + J3dThread.SOUND_SCHEDULER); + // renderChanges() called indirectly thru processMessage now + } + + + // deactivate scheduler only if it state has such that it can not perform + // sound processing + void deactivate() { + if (debugFlag) + debugPrint(".deactivate()"); + // + + // TODO: The following code is clearly erroneous. + // The indendation, along with the 2nd test of + // "if (debugFlag)" in the else clause, suggests that + // the intent was to make the else clause apply to + // "if (checkState())". However, the else clause + // actually applies to the first "if (debugFlag)". + // This is a textbook example of why one should + // *ALWAYS* enclose the target of an "if", "while", or + // "else" in curly braces -- even when the target + // consists of a single statement. + // + // The upshot of this, is that the else clause is + // unconditionally executed, since "debugFlag" is a + // static final constant that is set to false for + // production builds. We won't fix it now, because + // The SoundScheduler may actually be relying on the + // fact that all sounds are unconditionally + // deactivated when this method is called, and we + // don't want to introduce a new bug. + // + if (checkState()) + if (debugFlag) + debugPrint(" checkState returns true"); + else { + if (debugFlag) + debugPrint(" checkState returns false; deactive scheduler"); + // Call deactivateAllSounds within try/catch so + // errors won't kill the SoundScheduler. + try { + deactivateAllSounds(); + } + catch (RuntimeException e) { + System.err.println("Exception occurred " + + "during sound deactivation:"); + e.printStackTrace(); + } + updateThread.active = false; + } + } + + + // Check the ready state and return true if ready + boolean checkState() { + boolean runState = false; + if (stallThread) { + if (debugFlag) + debugPrint(" checkState stallThread true"); + runState = false; + } + + if (ready) { + if (debugFlag) + debugPrint(" checkState ready to run"); + runState = true; + } + else { + // previous not ready, force reset call to see if everything + // ready now or not + reset(view); + if (ready) { + if (debugFlag) + debugPrint(" checkState Now ready to run"); + runState = true; + } + else { + if (debugFlag) { + debugPrint(" checkState NOT ready to run"); + } + runState = false; + } + } + return runState; + } + + synchronized void reset(View v) { + int fieldsNotSet = 0; + if (v == null) { + if (debugFlag) + debugPrint(".reset() called with null view"); + view = null; + viewPlatform = null; + universe = null; + // clearly not ready to run + // QUESTION: doesn't this leave the possibility that + // the SoundScheduler may never be woken up??? + ready = false; + return; + } + else { + if (debugFlag) + debugPrint(".reset() called with view = " + v); + view = v; + // TODO: Does not support multiple canvases per view, thus + // multiple GraphicsContext3Ds + // QUESTION: what does that mean for sound - + // being applied to only ONE graphics context? + // GET FIRST Canvas + Canvas3D canvas = view.getFirstCanvas(); + if (canvas != null) { + graphicsCtx = canvas.getGraphicsContext3D(); + } + } + + // Set AudioDevice if possible + if (v.physicalEnvironment == null) { + if (debugFlag) + debugPrint(".reset: physicalEnvironment null"); + audioDevice = null; + fieldsNotSet++; + } + else if (v.physicalEnvironment.audioDevice == null) { + if (audioDevice == null) { + if (debugFlag) + debugPrint(".reset: audioDevice null"); + fieldsNotSet++; + } + // otherwise audioDevice set before so leave it unchanged + } + else { + audioDevice = v.physicalEnvironment.audioDevice; + } + + // Get viewPlatform and Universe + // If any of these are null at anytime, we can't render + ViewPlatform vp = v.getViewPlatform(); + + if (vp == null || vp.retained == null) { + if (debugFlag) + debugPrint(".reset: viewPlatform null"); + viewPlatform = null; + fieldsNotSet++; + } + else { + viewPlatform = (ViewPlatformRetained)vp.retained; + if (viewPlatform.universe == null) { + if (debugFlag) + debugPrint(".reset: vP.retained.univ null"); + universe = null; + fieldsNotSet++; + } + else { + // If we've reached this block then viewPlatform and univ + // as set below will NOT be null. + universe = viewPlatform.universe; + } + } + if (fieldsNotSet > 0 || audioDevice == null) { + // still not ready to run + ready = false; + return; + } + // now the render loop can be run successfully + audioDevice3DL2 = null; + audioDevice3D = null; + if (audioDevice instanceof AudioDevice3DL2) { + audioDevice3DL2 = (AudioDevice3DL2)audioDevice; + } + if (audioDevice instanceof AudioDevice3D) { + audioDevice3D = (AudioDevice3D)audioDevice; + if (debugFlag) + debugPrint(".reset: audioDevice3D.setView"); + audioDevice3D.setView(view); + totalChannels = audioDevice.getTotalChannels(); + if (debugFlag) + debugPrint(" audioDevice3D.getTotalChannels returned " + + totalChannels); + } + else { + if (internalErrors) + debugPrint(": AudioDevice implementation not supported"); + totalChannels = 0; + } + + if (totalChannels == 0) { + ready = false; + return; + } + + ready = true; + // since audio device is ready; set enable flag for continuous + // calculating userHead-to-VirtualWorld transform + view.setUserHeadToVworldEnable(true); + return; + } + + + void receiveAWTEvent(AWTEvent evt) { + int eventId = evt.getID(); + if (debugFlag) + debugPrint(".receiveAWTEvent " + eventId); + if (ready && eventId == WindowEvent.WINDOW_ICONIFIED) { + lastEventReceived = eventId; + } + else if (ready && + (lastEventReceived == WindowEvent.WINDOW_ICONIFIED && + eventId == WindowEvent.WINDOW_DEICONIFIED) ) { + lastEventReceived = eventId; + // used to notify + } + } + + + /** + * The main loop for the Sound Scheduler. + */ + void renderChanges() { + int nSounds = 0; + int totalChannelsUsed = 0; + int nPrioritizedSound = 0; + int numActiveSounds = 0; + if (debugFlag) + debugPrint(" renderChanges begun"); + // TODO: BUG?? should wait if audioDevice is NULL or nSounds = 0 + // when a new sound is added or deleted from list, or + // when the audioDevice is set into PhysicalEnvironment + + if (!checkState()) { + if (debugFlag) + debugPrint(".workToDo() checkState failed"); + return; + } + + /* + synchronized (prioritizedSounds) { + */ + nPrioritizedSound = prioritizedSounds.size(); + if (debugFlag) + debugPrint(" nPrioritizedSound = " + nPrioritizedSound); + if (nPrioritizedSound == 0) + return; + + if (auralAttribsChanged) { + // Find closest active aural attributes in scene graph + int nIntersected = findActiveSoundscapes(); + if (nIntersected > 0) { + if (debugFlag) + debugPrint(" "+ nIntersected + + " active SoundScapes found"); + // TODO: (Performance) calling clone everytime, even + // though closest AA has NOT changed, is expensive + aaRetained = (AuralAttributesRetained) + (findClosestAAttribs(nIntersected)).clone(); + } + else { + if (debugFlag) debugPrint(" NO active SoundScapes found"); + } + } + if (nPrioritizedSound > 0) { + calcSchedulingAction(); + muteSilentSounds(); + + // short term flag set within performActions->update() + positionalSoundUpdated = false; + + // if listener parameters changed re-set View parameters + if (testListenerFlag()) { + if (debugFlag) + debugPrint(" audioDevice3D.setView"); + audioDevice3D.setView(view); + } + + numActiveSounds = performActions(); + + if (positionalSoundUpdated) { + // if performActions updated at least one positional sound + // was processed so the listener/view changes were processed, + // thus we can clear the SoundScheduler dirtyFlag, otherwise + // leave the flag dirty until a positional sound is updated + clearListenerFlag(); // clears listenerUpdated flag + } + } + /* + } + */ + } + + + /** + * Prioritize all sounds associated with SoundScheduler (view) + * This only need be done once when scheduler is initialized since + * the priority list is updated when: + * a) PRIORITY_DIRTY_BIT in soundDirty field set; or + * b) sound added or removed from live array list + */ + int prioritizeSounds() { + int size; + synchronized (prioritizedSounds) { + if (!prioritizedSounds.isEmpty()) { + prioritizedSounds.clear(); + } + // TODO: sync soundStructure sound list + UnorderList retainedSounds = universe.soundStructure.getSoundList(view); + // QUESTION: what is in this sound list?? + // mirror node or actual node??? + nRetainedSounds = 0; + nImmedSounds = 0; + if (debugFlag) + debugPrint(" prioritizeSound , num retained sounds" + + retainedSounds.size()); + for (int i=0; i<retainedSounds.size(); i++) { + addPrioritizedSound((SoundRetained)retainedSounds.get(i)); + nRetainedSounds++; + } + // TODO: sync canvases + Enumeration canvases = view.getAllCanvas3Ds(); + while (canvases.hasMoreElements()) { + Canvas3D canvas = (Canvas3D)canvases.nextElement(); + GraphicsContext3D graphicsContext = canvas.getGraphicsContext3D(); + Enumeration nonretainedSounds = graphicsContext.getAllSounds(); + while (nonretainedSounds.hasMoreElements()) { + if (debugFlag) + debugPrint(" prioritizeSound , get non-retained sound"); + Sound sound = (Sound)nonretainedSounds.nextElement(); + if (sound == null) { + if (debugFlag) + debugPrint(" prioritizeSound , sound element is null"); + // QUESTION: why should I have to do this? + continue; + } + addPrioritizedSound((SoundRetained)sound.retained); + nImmedSounds++; + } + } + if (debugFlag) + debugPrint(" prioritizeSound , num of processed retained sounds" + + nRetainedSounds); + debugPrint(" prioritizeSound , num of processed non-retained sounds" + + nImmedSounds); + size = prioritizedSounds.size(); + } // sync + return size; + } + + + // methods that call this should synchronize prioritizedSounds + void addPrioritizedSound(SoundRetained mirSound) { + SoundRetained sound = mirSound.sgSound; + if (sound == null) { // this mirSound is a nonretained sound + // pad the "child" sg sound pointer with itself + mirSound.sgSound = mirSound; + sound = mirSound; + if (debugFlag) + debugPrint(":addPritorizedSound() sound NULL"); + } + boolean addAtom = false; + // see if this atom is in the list already + // covers the case where the node was detached or unswitched but NOT + // deleted (so sample already loaded + // QUESTION: is above logic correct??? + SoundSchedulerAtom atom = null; + atom = findSoundAtom(mirSound, 1); // look thru list for 1st instance + if (atom == null) { + atom = new SoundSchedulerAtom(); + atom.soundScheduler = this; // save scheduler atom is associated with + addAtom = true; + } + + // update fields in atom based on sound nodes state + atom.sound = mirSound; // new mirror sound + updateTransformedFields(mirSound); + + if ( !addAtom ) { + return; + } + + // if this atom being added then set the enable state + atom.enable(sound.enable); + + if (prioritizedSounds.isEmpty()) { + // List is currently empty, so just add it + // insert into empty list of prioritizedSounds + prioritizedSounds.add(atom); + if (debugFlag) + debugPrint(":addPritorizedSound() inset sound " + + mirSound + " into empty priority list"); + } + else { + // something's in the proirity list already + // Since list is not empty insert sound into list. + // + // Order is highest to lowest priority values, and + // for sounds with equal priority values, sound + // inserted first get in list given higher priority. + SoundRetained jSound; + SoundSchedulerAtom jAtom; + int j; + int jsounds = (prioritizedSounds.size() - 1); + float soundPriority = sound.priority; + for (j=jsounds; j>=0; j--) { + jAtom = (SoundSchedulerAtom)prioritizedSounds.get(j); + jSound = jAtom.sound; + if (debugFlag) + debugPrint(": priority of sound " + jSound.sgSound + + " element " + (j+1) + " of prioritized list"); + if (soundPriority <= jSound.sgSound.priority) { + if (j==jsounds) { + // last element's priority is larger than + // current sound's priority, so add this + // sound to the end of the list + prioritizedSounds.add(atom); + if (debugFlag) + debugPrint(": insert sound at list bottom"); + break; + } + else { + if (debugFlag) + debugPrint( + ": insert sound as list element " + + (j+1)); + prioritizedSounds.add(j+1, atom); + break; + } + } + } // for loop + if (j < 0) { // insert at the top of the list + if (debugFlag) + debugPrint(": insert sound at top of priority list"); + prioritizedSounds.add(0, atom); + } + } // else list not empty + } + + + /** + * Process active Soundscapes (if there are any) and intersect these + * soundscapes with the viewPlatform. + * + * Returns the number of soundscapes that intesect with + * view volume. + */ + int findActiveSoundscapes() { + int nSscapes = 0; + int nSelectedSScapes = 0; + SoundscapeRetained ss = null; + SoundscapeRetained lss = null; + boolean intersected = false; + int nUnivSscapes = 0; + UnorderList soundScapes = null; + + // Make a copy of references to the soundscapes in the universe + // that are both switch on and have non-null (transformed) regions, + // don't bother testing for intersection with view. + if (universe == null) { + if (debugFlag) + debugPrint(".findActiveSoundscapes() univ=null"); + return 0; + } + soundScapes = universe.soundStructure.getSoundscapeList(view); + if (soundScapes == null) { + if (debugFlag) + debugPrint(".findActiveSoundscapes() soundScapes null"); + return 0; + } + + synchronized (soundScapes) { + nUnivSscapes = soundScapes.size; + if (nUnivSscapes == 0) { + if (debugFlag) + debugPrint( + ".findActiveSoundscapes() soundScapes size=0"); + return 0; + } + + // increase arrays lengths by increments of 32 elements + if (intersectedRegions.length < nSscapes) { + intersectedRegions = new Bounds[nSscapes + 32]; + } + if (intersectedSoundscapes.length < nSscapes) { + intersectedSoundscapes = new SoundscapeRetained[nSscapes + 32]; + } + + // nSscapes is incremented for every Soundscape found + if (debugFlag) + debugPrint(".findActiveSoundscapes() nUnivSscapes="+ + nUnivSscapes); + nSelectedSScapes = 0; + for (int k=0; k<nUnivSscapes; k++) { + lss = (SoundscapeRetained)soundScapes.get(k); + + // Find soundscapes that intersect the view platform region + if (lss.transformedRegion == null ) { + continue; + } + if ((region instanceof BoundingSphere && + lss.transformedRegion instanceof BoundingSphere) || + (region instanceof BoundingBox && + lss.transformedRegion instanceof BoundingBox) || + (region instanceof BoundingPolytope && + lss.transformedRegion instanceof BoundingPolytope) ) { + lss.transformedRegion.getWithLock(region); + if (debugFlag) + debugPrint(" get tranformed region " + region ); + } else { + region = (Bounds)lss.transformedRegion.clone(); + if (debugFlag) + debugPrint(" clone tranformed region " + region ); + } + if (region!=null && viewPlatform.schedSphere.intersect(region)){ + intersectedRegions[nSelectedSScapes] = (Bounds)region.clone(); + // test View Platform intersection(lss)) + intersectedSoundscapes[nSelectedSScapes] = lss; + if (debugFlag) + debugPrint(" store sscape " + lss + + " and region " + region + " into array"); + nSelectedSScapes++; + if (debugFlag) + debugPrint(": region of intersection for "+ + "soundscape "+k+" found at "+System.currentTimeMillis()); + } else { + if (debugFlag) + debugPrint( + ": region of intersection for soundscape "+ + k + " not found at "+ System.currentTimeMillis()); + } + } + } + return(nSelectedSScapes); + } + + + AuralAttributesRetained findClosestAAttribs(int nSelectedSScapes) { + AuralAttributes aa = null; + SoundscapeRetained ss = null; + boolean intersected = false; + + // Get auralAttributes from closest (non-null) soundscape + ss = null; + if (nSelectedSScapes == 1) + ss = intersectedSoundscapes[0]; + else if (nSelectedSScapes > 1) { + Bounds closestRegions; + closestRegions = viewPlatform.schedSphere.closestIntersection( + intersectedRegions); + for (int j=0; j < intersectedRegions.length; j++) { + if (debugFlag) + debugPrint(" element " + j + + " in intersectedSoundsscapes is " + intersectedRegions[j]); + if (intersectedRegions[j] == closestRegions) { + ss = intersectedSoundscapes[j]; + if (debugFlag) + debugPrint(" element " + j + " is closest"); + break; + } + } + } + + if (ss != null) { + if (debugFlag) + debugPrint(" closest SoundScape found is " + ss); + aa = ss.getAuralAttributes(); + if (aa != null) { + if (debugFlag) + debugPrint(": AuralAttribute for " + + "soundscape is NOT null"); + } else { + if (debugFlag) + debugPrint(": AuralAttribute for " + + "soundscape " + ss + " is NULL"); + } + } + else { + if (debugFlag) + debugPrint(": AuralAttribute is null " + + "since soundscape is NULL"); + } + + if (debugFlag) + debugPrint( + " auralAttrib for closest SoundScape found is " + aa); + return ((AuralAttributesRetained)aa.retained); + } + + /** + * Send current aural attributes to audio device + * + * Note that a AA's dirtyFlag is clear only after parameters are sent to + * audio device. + */ + void updateAuralAttribs(AuralAttributesRetained attribs) { + if (auralAttribsChanged) { + if (attribs != null) { + synchronized (attribs) { +/* + // TODO: remove use of aaDirty from AuralAttrib node + if ((attribs != lastAA) || attribs.aaDirty) +*/ + if (debugFlag) { + debugPrint(" set real updateAuralAttribs because"); + } + + // Send current aural attributes to audio device + // Assumes that aural attribute parameter is NOT null. + audioDevice3D.setRolloff(attribs.rolloff); + if (debugFlag) + debugPrint(" rolloff " + attribs.rolloff); + + // Distance filter parameters + int arraySize = attribs.getDistanceFilterLength(); + if ((attribs.filterType == + AuralAttributesRetained.NO_FILTERING) || + arraySize == 0 ) { + audioDevice3D.setDistanceFilter( + attribs.NO_FILTERING, null, null); + if (debugFlag) + debugPrint(" no filtering"); + } + else { + Point2f[] attenuation = new Point2f[arraySize]; + for (int i=0; i< arraySize; i++) + attenuation[i] = new Point2f(); + attribs.getDistanceFilter(attenuation); + double[] distance = new double[arraySize]; + float[] cutoff = new float[arraySize]; + for (int i=0; i< arraySize; i++) { + distance[i] = attenuation[i].x; + cutoff[i] = attenuation[i].y; + } + audioDevice3D.setDistanceFilter(attribs.filterType, + distance, cutoff); + if (debugFlag) { + debugPrint(" filtering parameters: " + + " distance, cutoff arrays"); + for (int jj=0; jj<arraySize; jj++) + debugPrint( + " " + distance[jj]+", "+cutoff[jj]); + } + } + audioDevice3D.setFrequencyScaleFactor( + attribs.frequencyScaleFactor); + if (debugFlag) + debugPrint(" freq.scale " + + attribs.frequencyScaleFactor); + audioDevice3D.setVelocityScaleFactor( + attribs.velocityScaleFactor); + if (debugFlag) + debugPrint(" velocity scale " + + attribs.velocityScaleFactor); + audioDevice3D.setReflectionCoefficient( + attribs.reflectionCoefficient); + if (audioDevice3DL2 != null) { + audioDevice3DL2.setReverbCoefficient( + attribs.reverbCoefficient); + audioDevice3DL2.setDecayFilter(attribs.decayFilter); + audioDevice3DL2.setDiffusion(attribs.diffusion); + audioDevice3DL2.setDensity(attribs.density); + if (debugFlag) { + debugPrint(" reflect coeff " + + attribs.reflectionCoefficient); + debugPrint(" reverb coeff " + + attribs.reverbCoefficient); + debugPrint(" decay filter " + + attribs.decayFilter); + debugPrint(" diffusion " + + attribs.diffusion); + debugPrint(" density " + + attribs.density); + } + } + + // Precidence for determining Reverb Delay + // 1) If ReverbBounds set, bounds used to calculate delay. + // Default ReverbBounds is null meaning it's not defined. + // 2) If ReverbBounds is null, ReverbDelay used + // (implitic default value is defined as 40ms) + + // If Bounds null, use Reverb Delay + // else calculate Reverb Delay from Bounds + Bounds reverbVolume = attribs.reverbBounds; + if (reverbVolume == null) { + audioDevice3D.setReverbDelay(attribs.reverbDelay); + if (debugFlag) + debugPrint( + " reverbDelay " + attribs.reverbDelay); + } + else { + float reverbDelay; + if (reverbVolume instanceof BoundingSphere) { + // worst (slowest) case is if sound in center of + // bounding sphere + reverbDelay = (float) + ((2.0*((BoundingSphere)reverbVolume).radius)/ + AuralAttributesRetained.SPEED_OF_SOUND); + } + else { + // create a bounding sphere the surrounds the bounding + // object then calcalate the worst case as above + BoundingSphere tempSphere = + new BoundingSphere(reverbVolume); + reverbDelay = (float) + ((2.0 * tempSphere.radius)/ + AuralAttributesRetained.SPEED_OF_SOUND); + } + audioDevice3D.setReverbDelay(reverbDelay); + if (debugFlag) + debugPrint(" reverbDelay " + reverbDelay); + } + + // Audio device makes calculations for reverb decay + // using API's precidence where positive reverb order + // is used to clamp reverb decay time. + // Because of that the values are just passed along. + audioDevice3D.setReverbOrder(attribs.reverbOrder); + if (audioDevice3DL2 != null) + audioDevice3DL2.setDecayTime(attribs.decayTime); + } // sync attribs + resetAA = true; + } // attribs not null + + else if (lastAA != null) { + // Even when there are no active auralAttributes, still check + // if a set of auralAttributes was passed to the AudioDevice, + // thus is now inactive and must be cleared out (set to + // default values). + // Gain scale factor of 1.0 implicit by default - this value + // passed to AudioDevice3D as part of InitialScaleFactor value. + if (debugFlag) + debugPrint(": set updateDefaultAAs"); + audioDevice3D.setRolloff(1.0f); + audioDevice3D.setReflectionCoefficient(0.0f); + audioDevice3D.setReverbDelay(40.0f); + audioDevice3D.setReverbOrder(0); + // Distance filter parameters + audioDevice3D.setDistanceFilter( + AuralAttributesRetained.NO_FILTERING, null, null); + audioDevice3D.setFrequencyScaleFactor(1.0f); + audioDevice3D.setVelocityScaleFactor(0.0f); + if (audioDevice3DL2 != null) { + audioDevice3DL2.setReverbCoefficient(0.0f); + audioDevice3DL2.setReflectionDelay(20.0f); + audioDevice3DL2.setDecayTime(1000.0f); + audioDevice3DL2.setDecayFilter(5000.0f); + audioDevice3DL2.setDiffusion(1.0f); + audioDevice3DL2.setDensity(1.0f); + } + + // Any change in AuralAttrib should force an update of all + // active sounds as passed to AudioDevice3D. + resetAA = true; + } + else { + if (debugFlag) + debugPrint(" updateAuralAttribs: none set or cleared"); + } +/* + attribs.aaDirty = false; +*/ + lastAA = attribs; + auralAttribsChanged = false; + } + } + + + void processSoundAtom(SoundSchedulerAtom soundAtom) { + SoundRetained mirSound = soundAtom.sound; + SoundRetained sound = mirSound.sgSound; + + // Sounds that have finished playing are not put into list + if ((soundAtom.status == SoundSchedulerAtom.SOUND_COMPLETE) && + (soundAtom.enabled != SoundSchedulerAtom.PENDING_ON )) { + // TODO:/QUESTION test for immediate mode (?) + + // Unless the sound has been re-started, there's no need + // to process sound the finished playing the last time thru + // the run() loop + return; // don't need to process unless re-started + } + + // Sound must be loaded if it hasn't been successfully loaded already + if (soundAtom.loadStatus != SoundRetained.LOAD_COMPLETE) { + // should be OK for soundData to be NULL - this will unclear + if (debugFlag) + debugPrint(": LOAD_PENDING - try attaching"); + attachSoundData(soundAtom, sound.soundData, false); + } + + // if the loadStatus is STILL NOT COMPLETE, wait to schedule + if (soundAtom.loadStatus != SoundRetained.LOAD_COMPLETE) { + if (debugFlag) + debugPrint(": not LOAD_COMPLETE yet, so bail"); + // if sound is still not loaded, or loaded with null + return; // don't process this sound now + } + + if (resetAA) { // explicitly force update of gain for sound + soundAtom.setAttribsDirtyFlag(SoundRetained.INITIAL_GAIN_DIRTY_BIT); + } + + // Determine if sound is "active" + // Immediate mode sounds are always active. + // Sounds (both background and positional) are active only when their + // scheduling region intersects the viewPlatform. + boolean intersected = false; + if (!updateThread.active) { + region = null; + intersected = false; // force sound to be made inactive + if (debugFlag) + debugPrint(": updateThread NOT active"); + } + else if (sound.getInImmCtx()) { + region = null; + intersected = true; + if (debugFlag) + debugPrint(": sound.getInImmCtx TRUE"); + } + else { + if ( sound.schedulingRegion != null && + mirSound.transformedRegion != null ) { + // QUESTION: shouldn't mirror sound transformedRegion be + // set to null when sound node's region set null? + if ((region instanceof BoundingSphere && + mirSound.transformedRegion instanceof BoundingSphere) || + (region instanceof BoundingBox && + mirSound.transformedRegion instanceof BoundingBox) || + (region instanceof BoundingPolytope && + mirSound.transformedRegion instanceof BoundingPolytope)){ + mirSound.transformedRegion.getWithLock(region); + } else { + region = (Bounds)mirSound.transformedRegion.clone(); + } + } else { + region = null; + } + + if (region != null) { + if (debugFlag) + debugPrint(": region is " + region ); + intersected = viewPlatform.schedSphere.intersect(region); + if (debugFlag) + debugPrint(" intersection with viewPlatform is " + + intersected ); + } + else { + intersected = false; + if (debugFlag) + debugPrint(": region is null, " + + "so region does NOT intersect viewPlatform"); + } + } + + // Get scheduling action based on sound state (flags, status) + // Sound must be unmuted or pending unmuting and + // either immediate mode node, or if retained + // then intersecting active region and switch state must be on. + if (debugFlag) { + debugPrint(": intersected = " + intersected); + debugPrint(": switchState = " + mirSound.switchState); + if (mirSound.switchState != null) + debugPrint(": switchOn = " + mirSound.switchState.currentSwitchOn); + debugPrint(": soundAtom.muted = " + soundAtom.muted); + } + if ( (sound.getInImmCtx() || + (intersected && + mirSound.switchState != null && + mirSound.switchState.currentSwitchOn) ) && + (soundAtom.muted == soundAtom.UNMUTED || + soundAtom.muted == soundAtom.PENDING_UNMUTE) ) { + if (debugFlag) + debugPrint(": calcActiveSchedAction"); + soundAtom.schedulingAction = soundAtom.calcActiveSchedAction(); + } + else { + if (debugFlag) + debugPrint(": calcInactiveSchedAction"); + soundAtom.schedulingAction = soundAtom.calcInactiveSchedAction(); + } + + if (debugFlag) { + debugPrint(": scheduling action calculated " + + "as "+ soundAtom.schedulingAction); + debugPrint(" dirtyFlag test of LISTENER_CHANGED " + + testListenerFlag() ); + } + + // If state has not changed but parameters have, set + // action to UPDATE. + // This test includes checking that SoundScheduler dirtyFlag + // set when Eye, Ear or ImagePlate-to-Vworld Xform changed + // even for non-BackgroundSounds + if ((soundAtom.schedulingAction == SoundSchedulerAtom.LEAVE_AUDIBLE) && + (soundAtom.testDirtyFlags() || (testListenerFlag() && + !(sound instanceof BackgroundSoundRetained)))) { + if (debugFlag) { + if (testListenerFlag()) { + debugPrint(" testListenerFlag = " + + testListenerFlag()); + } + debugPrint(": action changed from " + + "as LEAVE_AUDIBLE to UPDATE"); + } + soundAtom.schedulingAction = SoundSchedulerAtom.UPDATE; + } + + // Update prioritized list of sounds whose scheduling action + // demands processing... + + // Ensure sound are not stopped while looping thru prioritized sounds + switch (soundAtom.schedulingAction) { + case SoundSchedulerAtom.TURN_OFF: + soundAtom.status = SoundSchedulerAtom.SOUND_OFF; + turnOff(soundAtom); // Stop sound that need to be turned off + if (debugFlag) + debugPrint(": sound " + soundAtom.sampleId + + " action OFF results in call to stop"); + soundAtom.schedulingAction = SoundSchedulerAtom.LEAVE_OFF; + break; + + case SoundSchedulerAtom.MAKE_AUDIBLE: + case SoundSchedulerAtom.MAKE_SILENT: + case SoundSchedulerAtom.LEAVE_AUDIBLE: + case SoundSchedulerAtom.LEAVE_SILENT: + case SoundSchedulerAtom.PAUSE_AUDIBLE: + case SoundSchedulerAtom.PAUSE_SILENT: + case SoundSchedulerAtom.RESUME_AUDIBLE: + case SoundSchedulerAtom.RESUME_SILENT: + case SoundSchedulerAtom.UPDATE: + + // Test for sound finishing playing since the last + // thru the run() loop. + // + // test current time against endTime of sound to determine + // if sound is Completely finished playing + long currentTime = System.currentTimeMillis(); + if (soundAtom.endTime>0 && soundAtom.endTime<=currentTime) { + // sound's completed playing, force action + soundAtom.schedulingAction = SoundSchedulerAtom.COMPLETE; + if (debugFlag) + debugPrint(": sample complete;"+ + " endTime = " + soundAtom.endTime + + ", currentTime = " + currentTime + + " so turned off"); + soundAtom.status = SoundSchedulerAtom.SOUND_COMPLETE; + turnOff(soundAtom); // Stop sound in device that are complete + if (debugFlag) + debugPrint(": sound "+soundAtom.sampleId+ + " action COMPLETE results in call to stop"); + } + break; + + case SoundSchedulerAtom.RESTART_AUDIBLE: + case SoundSchedulerAtom.START_AUDIBLE: + case SoundSchedulerAtom.RESTART_SILENT: + case SoundSchedulerAtom.START_SILENT: + break; + + default: // includes COMPLETE, DO_NOTHING + soundAtom.schedulingAction = SoundSchedulerAtom.DO_NOTHING; + break; + } // switch + + if (debugFlag) + debugPrint(": final scheduling action " + + "set to " + soundAtom.schedulingAction); + } + + + /** + * Determine scheduling action for each live sound + */ + int calcSchedulingAction() { + // Temp variables + SoundRetained sound; + SoundRetained mirSound; + SoundSchedulerAtom soundAtom; + SoundRetained jSound; + int nSounds = 0; + boolean processSound; + // number of sounds to process including scene graph and immediate nodes + int numSoundsToProcess = 0; + + if (universe == null) { + if (debugFlag) + debugPrint( + ": calcSchedulingAction: univ NULL"); + return 0; + } + if (universe.soundStructure == null) { + if (debugFlag) + debugPrint( + ": calcSchedulingAction: soundStructure NULL"); + return 0; + } + + // List of prioritized "live" sounds taken from universe list of sounds. + // Maintained as an expandable array - start out with a small number of + // elements for this array then grow the list larger if necessary... + synchronized (prioritizedSounds) { + nSounds = prioritizedSounds.size(); + if (debugFlag) + debugPrint( + ": calcSchedulingAction: soundsList size = " + + nSounds); + + // (Large) Loop over all switched on sounds and conditionally put + // these into a order prioritized list of sound. + // Try throw out as many sounds as we can: + // Sounds finished playing (reached end before stopped) + // Sounds still yet to be loaded + // Positional sounds whose regions don't intersect view + // Sound to be stopped + // Those sounds remaining are inserted into a prioritized list + + for (int i=0; i<nSounds; i++) { + soundAtom = ((SoundSchedulerAtom)prioritizedSounds.get(i)); + mirSound = soundAtom.sound; + sound = mirSound.sgSound; + if (debugFlag) { + debugPrint(" calcSchedulingAction: sound at " + sound); + printAtomState(soundAtom); + } + // First make a list of switched on live sounds + // make sure to process turned-off sounds even if they are + // NOT active + processSound = false; + if ( (!sound.source.isLive() && + !sound.getInImmCtx() ) ) { + if (debugFlag) { + debugPrint(" calcSchedulingAction sound " + + sound + " is NOT Live"); + if (mirSound.source != null) { + if (mirSound.source.isLive()!=sound.source.isLive()) + debugPrint( + " !=!=!= sound.isLive != mirSound"); + } + } + if (soundAtom.playing || + (soundAtom.enabled == SoundSchedulerAtom.ON)) { + soundAtom.setEnableState(SoundSchedulerAtom.PENDING_OFF); + processSound = true; + if (debugFlag) + debugPrint(" calcSchedulingAction !isLive: " + + "sound playing or ON, so set PENDING_OFF"); + } + else if (soundAtom.enabled==SoundSchedulerAtom.PENDING_OFF){ + processSound = true; + if (debugFlag) + debugPrint(" calcSchedulingAction !isLive: " + + "sound == PENDING_OFF so process"); + } + else if (soundAtom.enabled==SoundSchedulerAtom.PENDING_ON) { + soundAtom.setEnableState(SoundSchedulerAtom.OFF); + if (debugFlag) + debugPrint(" calcSchedulingAction !isLive: " + + "sound == PENDING_ON so set OFF"); + } + } + else { // live and switched on retained node or + // non-retained (immediate mode) node + processSound = true; + } + + if (processSound) { + numSoundsToProcess++; + if (debugFlag) { + debugPrint(".testListenerFlag = " + + testListenerFlag() + "...."); + debugPrint(" contents of live sound " + + soundAtom.sampleId + " before processing," ); + debugPrint(" >>>>>>sound using sgSound at " + sound); + printAtomState(soundAtom); + } + processSoundAtom(soundAtom); + } // end of process sound + else { + soundAtom.schedulingAction = SoundSchedulerAtom.DO_NOTHING; + } // end of not process sound + + } // end loop over all sound in soundList + } // sync + + if (debugFlag) { + if (numSoundsToProcess > 0) + debugPrint(": number of liveSounds = " + numSoundsToProcess); + else + debugPrint(": number of liveSounds <= 0"); + } + + return numSoundsToProcess; + } + + + /** + * Mute sounds that are to be played silently. + * + * Not all the sound in the prioritized enabled sound list + * may be able to be played. Due to low priority, some sounds + * must be muted/silenced (if such an action frees up channel + * resources) to make way for sounds with higher priority. + * For each sound in priority list: + * For sounds whose actions are X_SILENT: + * Mute sounds to be silenced + * Add the number of channels used by this muted sound to + * current total number of channels used + * For all remaining sounds (with actions other than above) + * The number of channels that 'would be used' to play + * potentially audible sounds is compared with + * the number left on the device: + * If this sound would use more channels than available + * Change it's X_AUDIBLE action to X_SILENT + * Mute sounds to be silenced + * Add the number of channels used by this sound, muted + * or not, to current total number of channels used + * + * NOTE: requests for sounds to play beyond channel capability of + * the audio device do NOT throw an exception when more sounds are + * started than can be played. Rather the unplayable sounds are + * muted. It is up to the AudioDevice3D implementation to determine + * how muted/silent sounds are implememted (playing with gain zero + * and thus using up channel resources, or stop and restarted with + * correct offset when inactivated then re-actived. + */ + void muteSilentSounds() { + // Temp variables + SoundRetained sound; + SoundRetained mirSound; + int totalChannelsUsed = 0; + SoundSchedulerAtom soundAtom; + int nAtoms; + synchronized (prioritizedSounds) { + nAtoms = prioritizedSounds.size(); + if (debugFlag) + debugPrint(".muteSilentSounds(): Loop over prioritizedSounds list, " + + "size = " + nAtoms); + for (int i=0; i<nAtoms; i++) { + soundAtom = (SoundSchedulerAtom)prioritizedSounds.get(i); + mirSound = (SoundRetained)soundAtom.sound; + sound = mirSound.sgSound; + int sampleId = soundAtom.sampleId; + int status = soundAtom.status; + + if (debugFlag) { + debugPrint(": contents of current sound " + + soundAtom.sampleId + " before switch on sAction" ); + printAtomState(soundAtom); + } + + if (soundAtom.status == SoundSchedulerAtom.SOUND_COMPLETE) { + continue; + } + if (soundAtom.schedulingAction == SoundSchedulerAtom.DO_NOTHING) { + continue; + } + if (sampleId == SoundRetained.NULL_SOUND) { + // skip it until next time thru calcSchedulingAction + continue; + } + if ( (soundAtom.schedulingAction == SoundSchedulerAtom.MAKE_SILENT) || + (soundAtom.schedulingAction == SoundSchedulerAtom.RESTART_SILENT) || + (soundAtom.schedulingAction == SoundSchedulerAtom.LEAVE_SILENT) || + (soundAtom.schedulingAction == SoundSchedulerAtom.START_SILENT) ) { + // Mute sounds that are not already silent + if (status != SoundSchedulerAtom.SOUND_SILENT) { + // old status is not already muted/silent + audioDevice3D.muteSample(sampleId); + if (debugFlag) + debugPrint(": sound " + sampleId + + " action is x_SILENT, sound muted"); + } + // now that the exact muting state is known get the actual + // number of channels used by this sound and add to total + int numberChannels = + audioDevice3D.getNumberOfChannelsUsed(sampleId); + soundAtom.numberChannels = numberChannels; // used in audio device + totalChannelsUsed += numberChannels; // could return zero + } // scheduling is for silent sound + + else { + // First, test to see if the sound can play as unmuted. + int numberChannels = + audioDevice3D.getNumberOfChannelsUsed(sampleId, false); + // Mute sounds that have too low priority + if ((totalChannelsUsed+numberChannels)>totalChannels) { + if ((soundAtom.schedulingAction == SoundSchedulerAtom.MAKE_AUDIBLE) || + (soundAtom.schedulingAction == SoundSchedulerAtom.LEAVE_AUDIBLE)) { + soundAtom.schedulingAction = SoundSchedulerAtom.MAKE_SILENT; + } + else if (soundAtom.schedulingAction == SoundSchedulerAtom.RESTART_AUDIBLE) + soundAtom.schedulingAction = SoundSchedulerAtom.RESTART_SILENT; + else if (soundAtom.schedulingAction == SoundSchedulerAtom.START_AUDIBLE) + soundAtom.schedulingAction = SoundSchedulerAtom.START_SILENT; + else if (soundAtom.schedulingAction == SoundSchedulerAtom.PAUSE_AUDIBLE) + soundAtom.schedulingAction = SoundSchedulerAtom.PAUSE_SILENT; + else if (soundAtom.schedulingAction == SoundSchedulerAtom.RESUME_AUDIBLE) + soundAtom.schedulingAction = SoundSchedulerAtom.RESUME_SILENT; + audioDevice3D.muteSample(sampleId); + if (debugFlag) { + debugPrint(": sound " + sampleId + + "number of channels needed is " + + numberChannels); + debugPrint(": sound " + sampleId + + " action is x_AUDIBLE but " + + "not enough channels free (" + + (totalChannels - totalChannelsUsed) + + ") so, sound muted"); + } + } + // sound has enough channels to play + else if (status != SoundSchedulerAtom.SOUND_AUDIBLE) { + // old status is not already unmuted/audible + audioDevice3D.unmuteSample(sampleId); + if (debugFlag) + debugPrint(": sound " + sampleId + + " action is x_AUDIBLE and channels free so, " + + "sound unmuted"); + } + // now that the exact muting state is known (re-)get actual + // number of channels used by this sound and add to total + numberChannels = + audioDevice3D.getNumberOfChannelsUsed(sampleId); + soundAtom.numberChannels = numberChannels; // used in audio device + totalChannelsUsed += numberChannels; + } // otherwise, scheduling is for potentally audible sound + // No sound in list should have action TURN_ or LEAVE_OFF + } // of for loop over sounds in list + } + } + + + void muteSilentSound(SoundSchedulerAtom soundAtom) { + // Temp variables + SoundRetained sound; + SoundRetained mirSound; + mirSound = (SoundRetained)soundAtom.sound; + sound = mirSound.sgSound; + int sampleId = soundAtom.sampleId; + int status = soundAtom.status; + + if (status == SoundSchedulerAtom.SOUND_COMPLETE) { + return; + } + if (sampleId == SoundRetained.NULL_SOUND) { + return; + } + if (debugFlag) { + debugPrint(": contents of current sound " + + soundAtom.sampleId + " before switch on sAction" ); + printAtomState(soundAtom); + } + + if ( (soundAtom.schedulingAction == SoundSchedulerAtom.MAKE_SILENT) || + (soundAtom.schedulingAction == SoundSchedulerAtom.RESTART_SILENT) || + (soundAtom.schedulingAction == SoundSchedulerAtom.LEAVE_SILENT) || + (soundAtom.schedulingAction == SoundSchedulerAtom.START_SILENT) ) { + // Mute sounds that are not already silent + if (status != SoundSchedulerAtom.SOUND_SILENT) { + // old status is not already muted/silent + audioDevice3D.muteSample(sampleId); + if (debugFlag) + debugPrint(": sound " + sampleId + + " action is x_SILENT, sound muted"); + } + } // scheduling is for silent sound + } + + /** + * Determine amount of time before next playing sound will be + * is complete. + * + * find the atom that has the least amount of time before is + * finished playing and return this time + * @return length of time in millisecond until the next active sound + * will be complete. Returns -1 if no sounds are playing (or all are + * complete). + */ + long shortestTimeToFinish() { + long currentTime = System.currentTimeMillis(); + long shortestTime = -1L; + SoundSchedulerAtom soundAtom; + synchronized (prioritizedSounds) { + int nAtoms = prioritizedSounds.size(); + for (int i=0; i<nAtoms; i++) { + soundAtom = (SoundSchedulerAtom)prioritizedSounds.get(i); + if (soundAtom.status == SoundSchedulerAtom.SOUND_OFF || + soundAtom.status == SoundSchedulerAtom.SOUND_COMPLETE ) + continue; + long endTime = soundAtom.endTime; + if (endTime < 0) + // skip sounds that are to play infinitely (until stopped) + continue; + if (debugFlag) { + if (endTime == 0) + debugPrint(".shortestTimeToFinish: " + + "Internal Error - endTime 0 while sound playing"); + } + // for all playing sounds (audible and silent) find how much + // time is left before the sound completed playing + long timeLeft = endTime - currentTime; + if (debugFlag) + debugPrint( + " shortestTimeToFinish timeLeft = " + + timeLeft); + if (timeLeft < 0L) { + // normalize completed sounds; force to zero + // so no waiting occurs before scheduler re-entered + timeLeft = 0L; + } + if (shortestTime < 0L) { + // save first atom's time as shortest + shortestTime = timeLeft; + } + else if (timeLeft < shortestTime) { + shortestTime = timeLeft; + } + } + } + if (debugFlag) + debugPrint(".shortestTimeToFinish returns " + shortestTime ); + return shortestTime; + } + + + /** + * Perform the scheduling action for each prioritized sound. + * + * Now, finally, the scheduling action value reflects what is + * requested and what is physically posible to perform. + * So, for each sound in list of prioritized enabled sounds, + * start/update sounds that are REALLY supposed to be either + * playing audibly or playing silently. + * @return number of active (audible and silent) sounds + */ + int performActions() { + // Temp variables + SoundRetained sound; + SoundRetained mirSound; + int nAtoms; + SoundSchedulerAtom soundAtom; + AuralAttributesRetained attribs; + int numActiveSounds = 0; + int sampleId; + + synchronized (prioritizedSounds) { + nAtoms = prioritizedSounds.size(); + for (int i=0; i<nAtoms; i++) { + // TODO: (Enhancement) Get all sound node fields here + // and store locally for performance + soundAtom = (SoundSchedulerAtom)prioritizedSounds.get(i); + mirSound = soundAtom.sound; + sound = mirSound.sgSound; + sampleId = soundAtom.sampleId; + + if (sampleId == SoundRetained.NULL_SOUND) { + // skip it until next time thru calcSchedulingAction + continue; + } + + // Two flags denoting that AuralAttributes have be changed and thus + // sounds have to potentially be rendered are maintained and set in + // updateAuralAttribs(). + resetAA = false; + + // check to see if aural attributes changed and have to be updated + // must be done before list of sound processed so that Aural Attributes + // that affect Sound fields can be set in AudioDevice + // TODO: this is not effient if auralAttribs always the same + if (sound.getInImmCtx()) { + if (graphicsCtx !=null && graphicsCtx.auralAttributes !=null) { + aaImmed = (AuralAttributesRetained) + (graphicsCtx.auralAttributes.retained); + attribs = aaImmed; + } + else { + attribs = null; + } + } + else { + attribs = aaRetained; + } + updateAuralAttribs(attribs); + + if (debugFlag) { + debugPrint(": contents of current sound " + + sampleId + " before start/update " ); + printAtomState(soundAtom); + } + + switch (soundAtom.schedulingAction) { + case SoundSchedulerAtom.RESTART_AUDIBLE: + // stop sound first then fall thru to re-start + turnOff(soundAtom); + case SoundSchedulerAtom.START_AUDIBLE: + // Pause and Resume related actions are checked when sound + // is to be started or restarted + if (soundAtom.paused == soundAtom.PENDING_PAUSE) + pause(soundAtom); + if (soundAtom.paused == soundAtom.PENDING_UNPAUSE) + unpause(soundAtom); + if (soundAtom.paused == soundAtom.UNPAUSED) { + // if its unpaused, start audible sound + soundAtom.status = SoundSchedulerAtom.SOUND_AUDIBLE; + render(true, soundAtom, attribs); + } + else { // sound paused + soundAtom.status = SoundSchedulerAtom.SOUND_PAUSED; + // start it after when the sound is not paused + soundAtom.setEnableState(SoundSchedulerAtom.PENDING_ON); + } + numActiveSounds++; + break; + + case SoundSchedulerAtom.RESTART_SILENT: + // stop sound first then fall thru to re-start + turnOff(soundAtom); + case SoundSchedulerAtom.START_SILENT: + // Pause and Resume related actions are checked when sound + // is to be started or restarted + if (soundAtom.paused == soundAtom.PENDING_PAUSE) + pause(soundAtom); + if (soundAtom.paused == soundAtom.PENDING_UNPAUSE) + unpause(soundAtom); + if (soundAtom.paused == soundAtom.UNPAUSED) { + // if the sound is unpaused, start silent sound + soundAtom.status = SoundSchedulerAtom.SOUND_SILENT; + render(true, soundAtom, attribs); + } + else { // sound paused + soundAtom.status = SoundSchedulerAtom.SOUND_PAUSED; + // start it after when the sound is not paused + soundAtom.setEnableState(SoundSchedulerAtom.PENDING_ON); + } + numActiveSounds++; + break; + + case SoundSchedulerAtom.RESUME_AUDIBLE: + // pause then fall thru set make audible + unpause(soundAtom); + case SoundSchedulerAtom.MAKE_AUDIBLE: + // change status to audible then update sound + soundAtom.status = SoundSchedulerAtom.SOUND_AUDIBLE; + render(false, soundAtom, attribs); + numActiveSounds++; + break; + case SoundSchedulerAtom.RESUME_SILENT: + // pause then fall thru set make silent + unpause(soundAtom); + case SoundSchedulerAtom.MAKE_SILENT: + // change status to silent AFTER calling render so + // that a currently audible sound will be muted. + // TODO: why set status AFTER?? + render(false, soundAtom, attribs); + soundAtom.status = SoundSchedulerAtom.SOUND_SILENT; + numActiveSounds++; + break; + + case SoundSchedulerAtom.PAUSE_AUDIBLE: + case SoundSchedulerAtom.PAUSE_SILENT: + pause(soundAtom); + soundAtom.status = SoundSchedulerAtom.SOUND_PAUSED; + numActiveSounds++; + break; + + case SoundSchedulerAtom.UPDATE: + render(false, soundAtom, attribs); + numActiveSounds++; + break; + + case SoundSchedulerAtom.LEAVE_AUDIBLE: + case SoundSchedulerAtom.LEAVE_SILENT: + case SoundSchedulerAtom.LEAVE_PAUSED: + if (resetAA || soundAtom.testDirtyFlags()) + render(false, soundAtom, attribs); + if (debugFlag) + debugPrint(": LEAVE_AUDIBLE or _SILENT " + + "seen"); + numActiveSounds++; + break; + + case SoundSchedulerAtom.TURN_OFF: + turnOff(soundAtom); + break; + + case SoundSchedulerAtom.LEAVE_OFF: + case SoundSchedulerAtom.COMPLETE: + case SoundSchedulerAtom.DO_NOTHING: + break; + + default: + if (internalErrors) + debugPrint(": Internal Error"+ + " unknown action"); + break; + } + // Clear atom state and attrib dirty flags + soundAtom.clearStateDirtyFlag(); + soundAtom.clearAttribsDirtyFlag(); + + } // for sounds in priority list + } + + // Now that aural attribute change forced each processed sounds + // to be updated, clear this special reset aural attrubute flag + resetAA = false; + + return numActiveSounds; + } + + + /** + * render (start or update) the oscillator associated with this sound + */ + void render(boolean startFlag, + SoundSchedulerAtom soundAtom, + AuralAttributesRetained attribs) { + + SoundRetained mirrorSound = soundAtom.sound; + SoundRetained sound = mirrorSound.sgSound; + if (debugFlag) + debugPrint(".render " + sound); + + if ( soundAtom.sampleId == SoundRetained.NULL_SOUND || + soundAtom.soundData == null ) { + if (internalErrors) + debugPrint(".render - Internal Error: " + + "null sample data"); + return; + } + + int index = soundAtom.sampleId; + + // Depending on Mute and/or pause flags, set sound parameters + if (startFlag) { + if ( (sound instanceof PointSoundRetained) || + (sound instanceof ConeSoundRetained) ) { + updateXformedParams(true, soundAtom); + } + updateSoundParams(true, soundAtom, attribs); + start(soundAtom); + } + else { + if (soundAtom.status == SoundSchedulerAtom.SOUND_AUDIBLE) { + if ( (sound instanceof PointSoundRetained) || + (sound instanceof ConeSoundRetained) ) { + updateXformedParams(false, soundAtom); + } + updateSoundParams(false, soundAtom, attribs); + update(soundAtom); + } + } // if sound Audible + } // render + + + /** + * Start the sample associated with this sound + * Do everything necessary to start the sound: + * set start time + * the oscillator associated with this sound + */ + void start(SoundSchedulerAtom soundAtom) { + SoundRetained sound = soundAtom.sound.sgSound; + int index = soundAtom.sampleId; + int startStatus = -1; + if (index != SoundRetained.NULL_SOUND && + (startStatus = audioDevice3D.startSample(index)) >= 0) { + if (debugFlag) + debugPrint(".start: " + index ); + soundAtom.playing = true; + soundAtom.startTime = audioDevice3D.getStartTime(index); + soundAtom.calculateEndTime(); + if (debugFlag) + debugPrint(".start: begintime = " + + soundAtom.startTime + ", endtime " + soundAtom.endTime); + } + else { // error returned by audio device when trying to start + soundAtom.startTime = 0; + soundAtom.endTime = 0; + soundAtom.playing = false; + if (debugFlag) { + debugPrint(".start: error " + startStatus + + " returned by audioDevice3D.startSample(" + index + + ")" ); + debugPrint( + " start/endTime set to zero"); + } + } + } + + + /** + * Exlicitly update the sound parameters associated with a sample + */ + void update(SoundSchedulerAtom soundAtom) { + int index = soundAtom.sampleId; + + if (index == SoundRetained.NULL_SOUND) { + return; + } + SoundRetained sound = soundAtom.sound; + audioDevice3D.updateSample(index); + if (debugFlag) { + debugPrint(".update: " + index ); + } + soundAtom.calculateEndTime(); + if (sound instanceof PointSoundRetained || + sound instanceof ConeSoundRetained) { + positionalSoundUpdated = true; + } + } + + + /** + * stop playing one specific sound node + * + * If setPending flag true, sound is stopped but enable state + * is set to pending-on so that it is restarted. + */ + void stopSound(SoundSchedulerAtom soundAtom, boolean setPending) { + if (audioDevice3D == null) + return; + + if (debugFlag) + debugPrint(":stopSound(" + soundAtom + + "), enabled = " + soundAtom.enabled); + switch (soundAtom.enabled) { + case SoundSchedulerAtom.ON: + if (setPending) + soundAtom.setEnableState(SoundSchedulerAtom.PENDING_ON); + else + soundAtom.setEnableState(SoundSchedulerAtom.SOUND_OFF); + break; + case SoundSchedulerAtom.PENDING_OFF: + soundAtom.setEnableState(SoundSchedulerAtom.SOUND_OFF); + break; + case SoundSchedulerAtom.PENDING_ON: + if (!setPending) + // Pending sounds to be stop from playing later + soundAtom.setEnableState(SoundSchedulerAtom.SOUND_OFF); + break; + default: + break; + } + soundAtom.status = SoundSchedulerAtom.SOUND_OFF; + turnOff(soundAtom); + } + + /** + * Deactive all playing sounds + * If the sound is continuous thendSilence it but leave it playing + * otherwise stop sound + */ + synchronized void deactivateAllSounds() { + SoundRetained sound; + SoundRetained mirSound; + SoundSchedulerAtom soundAtom; + + if (audioDevice3D == null) + return; + + if (debugFlag) + debugPrint(".deactivateAllSounds"); + + // sync this method from interrupting run() while loop + synchronized (prioritizedSounds) { + if (prioritizedSounds != null) { + int nAtoms = prioritizedSounds.size(); + if (debugFlag) + debugPrint("silenceAll " + nAtoms + " Sounds"); + for (int i=0; i<nAtoms; i++) { + // TODO: (Enhancement) Get all sound node fields here + // and store locally for performance + soundAtom = (SoundSchedulerAtom)prioritizedSounds.get(i); + mirSound = soundAtom.sound; + sound = mirSound.sgSound; + if (sound.continuous) { + // make playing sound silent + if (debugFlag) + debugPrint("deactivateAll atomScheduling " + + "before calcInactiveSchedAction" + + soundAtom.schedulingAction); + soundAtom.schedulingAction = + soundAtom.calcInactiveSchedAction(); + if (debugFlag) + debugPrint("deactivateAll atomScheduling " + + "after calcInactiveSchedAction" + + soundAtom.schedulingAction); + // perform muting of sound + muteSilentSound(soundAtom); // mark sound as silence + } + else { + // stop playing sound but make pending on + stopSound(soundAtom, true); // force pendingOn TRUE + soundAtom.schedulingAction = soundAtom.LEAVE_OFF; + if (debugFlag) + debugPrint("deactivateAll atomScheduling " + + "forced to TURN_OFF, set pending On"); + } + + } // for sounds in priority list + } + } + performActions(); + } + + + /** + * Pause all activity playing sounds + */ + synchronized void pauseAllSounds() { + SoundRetained sound; + SoundRetained mirSound; + + if (audioDevice3D == null) + return; + + stallThread = true; + if (debugFlag) + debugPrint(".pauseAll stallThread set to true"); + + // sync this method from interrupting run() while loop + synchronized (prioritizedSounds) { + if (prioritizedSounds != null) { + int nAtoms = prioritizedSounds.size(); + if (debugFlag) + debugPrint(":pauseAll " + nAtoms + " Sounds"); + for (int i=0; i<nAtoms; i++) { + // TODO: (Enhancement) Get all sound node fields here + // and store locally for performance + SoundSchedulerAtom soundAtom = + (SoundSchedulerAtom)prioritizedSounds.get(i); + mirSound = soundAtom.sound; + sound = mirSound.sgSound; + + switch (soundAtom.enabled) { + case SoundSchedulerAtom.ON: + case SoundSchedulerAtom.PENDING_OFF: + pause(soundAtom); + if (debugFlag) + debugPrint( + ".pauseAllSounds PAUSE sound " + sound); + break; + default: + break; + } + } // for sounds in priority list + } + } + } + + + /** + * Resume playing all paused active sounds + */ + synchronized void resumeAllSounds() { + SoundRetained sound; + SoundRetained mirSound; + + if (audioDevice3D == null) + return; + + if (debugFlag) + debugPrint(".resumeAll stallThread set to true"); + + // sync this method from interrupting run() while loop + synchronized (prioritizedSounds) { + if (prioritizedSounds != null) { + int nAtoms = prioritizedSounds.size(); + if (debugFlag) + debugPrint(": resumeAll " + nAtoms + " Sounds "); + + for (int i=0; i<nAtoms; i++) { + // TODO: (Enhancement) Get all sound node fields here + // and store locally for performance + SoundSchedulerAtom soundAtom = + (SoundSchedulerAtom)prioritizedSounds.get(i); + mirSound = soundAtom.sound; + sound = mirSound.sgSound; + + switch (soundAtom.enabled) { + case SoundSchedulerAtom.ON: + case SoundSchedulerAtom.PENDING_OFF: + unpause(soundAtom); + if (debugFlag) + debugPrint(".resumeAll - sound = " + sound); + break; + default: + break; + } + } // for sounds in priority list + } + } + stallThread = false; + } + + /** + * Stop all activity playing sounds + */ + synchronized void stopAllSounds() { + stopAllSounds(false); + } + + synchronized void stopAllSounds(boolean setPlayingSoundsPending) { + // QUESTION: how can I assure that all sounds on device + // are stopped before thread paused/shutdown + if (debugFlag) + debugPrint(".stopAllSounds entered"); + SoundRetained sound; + SoundRetained mirSound; + + if (audioDevice3D == null) + return; + + if (lastEventReceived == WindowEvent.WINDOW_ICONIFIED) { + return; // leave sounds playing + } + + // sync this method from interrupting run() while loop + synchronized (prioritizedSounds) { + if (prioritizedSounds != null) { + int nAtoms = prioritizedSounds.size(); + if (debugFlag) + debugPrint(": stopAll " + nAtoms + " Sounds "); + + for (int i=0; i<nAtoms; i++) { + // TODO: (Enhancement) Get all sound node fields here + // and store locally for performance + SoundSchedulerAtom soundAtom = + (SoundSchedulerAtom)prioritizedSounds.get(i); + if (debugFlag) + debugPrint(" stop(" + soundAtom + ")"); + // stop playing Sound - optionally set pending enabled + stopSound(soundAtom, setPlayingSoundsPending); + } // for sounds in priority list + + // QUESTION: - removed the code that empties out prioritized + // sound atom list. Are there cases when core calling + // StopAllSounds expects sounds to be cleared?? + } + } + if (debugFlag) + debugPrint(".stopAllSounds exited"); + } + + // TODO: Mute All Sounds, complementary to Stop All Sounds + // "should return from run loop - but simply WAIT until sounds + // are unmuted. " ??? + + + /** + * pause the sample associated with this sound + */ + void pause(SoundSchedulerAtom soundAtom) { + if (soundAtom.sampleId == SoundRetained.NULL_SOUND) + return; + // Ensure sound are not modified while looping thru prioritized sounds + if (debugFlag) + debugPrint(".pause"); + audioDevice3D.pauseSample(soundAtom.sampleId); + soundAtom.setPauseState(soundAtom.PAUSED); + } + + void unpause(SoundSchedulerAtom soundAtom) { + if (soundAtom.sampleId == SoundRetained.NULL_SOUND) + return; + if (debugFlag) + debugPrint(".unpause"); + audioDevice3D.unpauseSample(soundAtom.sampleId); + soundAtom.setPauseState(soundAtom.UNPAUSED); + } + + /** + * stop the sample associated with this sound + */ + void turnOff(SoundSchedulerAtom soundAtom) { + // Ensure sound are not stopped while looping thru prioritized sounds + if (soundAtom.sampleId == SoundRetained.NULL_SOUND) + return; + if (debugFlag) + debugPrint(".turnOff"); + if (audioDevice3D.stopSample(soundAtom.sampleId) < 0) { + if (internalErrors) { + debugPrint("Internal Error: stop sample error"); + } + } + soundAtom.playing = false; + soundAtom.startTime = 0; + soundAtom.endTime = 0; + + } + + + /** + * Update VirtualWorld local transform, sound position and direction. + * + * This is done dynamically from PointSoundRetained as these fields + * are updated (when soundAtom.status is AUDIBLE or SILENT), or by this. + * render() method when sound is started (sound.enabled is true). + * + * This method should only be called if mirror sound is a Point or + * ConeSound. + * + * Important: pre-transformed position and direction sent to AudioDevice. + */ + void updateXformedParams(boolean updateAll, SoundSchedulerAtom soundAtom) { + PointSoundRetained mirrorPtSound = (PointSoundRetained)soundAtom.sound; + PointSoundRetained ptSound = (PointSoundRetained)mirrorPtSound.sgSound; + int index = soundAtom.sampleId; + if (index == SoundRetained.NULL_SOUND) + return; + PointSoundRetained ps = (PointSoundRetained)mirrorPtSound; + + // Set Transform + + /* + // TODO: per sound tranforms can now be passed to AudioDevice + // modify and execute the code below + + // MoveAppBoundingLeaf > ~/Current/MoveAppBoundingLeaf.outted, + // instead transformed position and direction + // points/vectors will be passed to AudioDevice directly. + + // vvvvvvvvvvvvvvvvvvvvvvvvvvv + if (updateAll || soundAtom.testDirtyFlag(SoundRetained.XFORM_DIRTY_BIT){ + Transform3D xform = new Transform3D(); + ps.trans.getWithLock(xform); + if (debugFlag) { + debugPrint(".updateXformedParams " + + "setVworldXfrm for ps @ " + ps + ":"); + debugPrint(" xformPosition " + + ps.xformPosition.x + ", " + + ps.xformPosition.y + ", " + + ps.xformPosition.z ); + debugPrint(" column-major transform "); + debugPrint(" " + + xform.mat[0]+", " + xform.mat[1]+", "+ + xform.mat[2]+", " + xform.mat[3]); + debugPrint(" " + + xform.mat[4]+", " + xform.mat[5]+", "+ + xform.mat[6]+", " + xform.mat[7]); + debugPrint(" " + + xform.mat[8]+", " + xform.mat[9]+", "+ + xform.mat[10]+", " + xform.mat[11]); + debugPrint(" " + + xform.mat[12]+", " + xform.mat[13]+", "+ + xform.mat[14]+", " + xform.mat[15]); + } + audioDevice3D.setVworldXfrm(index, xform); + soundAtom.clearStateDirtyFlag( SoundRetained.XFORM_DIRTY_BIT); + // TODO: make sure position and direction are already transformed and stored + // into xformXxxxxxx fields. + } + // ^^^^^^^^^^^^^^^^^^^^^ + */ + + // Set Position + if (updateAll || testListenerFlag() || + soundAtom.testDirtyFlag(soundAtom.attribsDirty, + SoundRetained.POSITION_DIRTY_BIT) || + soundAtom.testDirtyFlag(soundAtom.stateDirty, + SoundRetained.XFORM_DIRTY_BIT) ) + { + Point3f xformLocation = new Point3f(); + mirrorPtSound.getXformPosition(xformLocation); + Point3d positionD = new Point3d(xformLocation); + if (debugFlag) + debugPrint("xform'd Position: ("+positionD.x+", "+ + positionD.y+", "+ positionD.z+")" ); + audioDevice3D.setPosition(index, positionD); + } + + // Set Direction + if (mirrorPtSound instanceof ConeSoundRetained) { + ConeSoundRetained cn = (ConeSoundRetained)mirrorPtSound; + ConeSoundRetained cnSound = (ConeSoundRetained)mirrorPtSound.sgSound; + if (updateAll || + // TODO: test for XFORM_DIRTY only in for 1.2 + soundAtom.testDirtyFlag(soundAtom.attribsDirty, + (SoundRetained.DIRECTION_DIRTY_BIT | + SoundRetained.XFORM_DIRTY_BIT) ) ) { + + Vector3f xformDirection = new Vector3f(); + cn.getXformDirection(xformDirection); + Vector3d directionD = new Vector3d(xformDirection); + audioDevice3D.setDirection(index, directionD); + } + } + } + + + void updateSoundParams(boolean updateAll, SoundSchedulerAtom soundAtom, + AuralAttributesRetained attribs) { + + SoundRetained mirrorSound = soundAtom.sound; + SoundRetained sound = mirrorSound.sgSound; + int index = soundAtom.sampleId; + int arraySize; + + if (index == SoundRetained.NULL_SOUND) + return; + if (debugFlag) + debugPrint(".updateSoundParams(dirytFlags=" + + soundAtom.attribsDirty + ", " + soundAtom.stateDirty + ")"); + + // since the sound is audible, make sure that the parameter for + // this sound are up-to-date. + if (updateAll || soundAtom.testDirtyFlag( + soundAtom.attribsDirty, SoundRetained.INITIAL_GAIN_DIRTY_BIT)) { + + if (attribs != null) { + audioDevice3D.setSampleGain(index, + (sound.initialGain * attribs.attributeGain)); + } + else { + audioDevice3D.setSampleGain(index, sound.initialGain); + } + } + + if (updateAll || soundAtom.testDirtyFlag( + soundAtom.attribsDirty, SoundRetained.LOOP_COUNT_DIRTY_BIT)) { + if (debugFlag) + debugPrint(" audioDevice.setLoop(" + sound.loopCount + + ") called"); + audioDevice3D.setLoop(index, sound.loopCount); + } + + if (updateAll || soundAtom.testDirtyFlag( + soundAtom.attribsDirty, SoundRetained.RATE_DIRTY_BIT)) { + if (audioDevice3DL2 != null) { + if (debugFlag) + debugPrint(" audioDevice.setRateScaleFactor(" + + sound.rate + ") called"); + audioDevice3DL2.setRateScaleFactor(index, sound.rate); + } + } + + if (updateAll || soundAtom.testDirtyFlag( + soundAtom.attribsDirty, SoundRetained.DISTANCE_GAIN_DIRTY_BIT)){ + if (sound instanceof ConeSoundRetained) { + ConeSoundRetained cnSound = (ConeSoundRetained)sound; + + // set distance attenuation + arraySize = cnSound.getDistanceGainLength(); + if (arraySize == 0) { + // send default + audioDevice3D.setDistanceGain(index, null, null, null, null); + } + else { + Point2f[] attenuation = new Point2f[arraySize]; + Point2f[] backAttenuation = new Point2f[arraySize]; + for (int i=0; i< arraySize; i++) { + attenuation[i] = new Point2f(); + backAttenuation[i] = new Point2f(); + } + cnSound.getDistanceGain(attenuation, backAttenuation); + double[] frontDistance = new double[arraySize]; + float[] frontGain = new float[arraySize]; + double[] backDistance = new double[arraySize]; + float[] backGain = new float[arraySize]; + for (int i=0; i< arraySize; i++) { + frontDistance[i] = attenuation[i].x; + frontGain[i] = attenuation[i].y; + backDistance[i] = backAttenuation[i].x; + backGain[i] = backAttenuation[i].y; + } + audioDevice3D.setDistanceGain(index, + frontDistance, frontGain, backDistance, backGain); + } + } // ConeSound distanceGain + else if (sound instanceof PointSoundRetained) { + PointSoundRetained ptSound = (PointSoundRetained)sound; + + // set distance attenuation + arraySize = ptSound.getDistanceGainLength(); + if (arraySize == 0) { + // send default + audioDevice3D.setDistanceGain(index, null, null, null, null); + } + else { + Point2f[] attenuation = new Point2f[arraySize]; + for (int i=0; i< arraySize; i++) + attenuation[i] = new Point2f(); + ptSound.getDistanceGain(attenuation); + double[] frontDistance = new double[arraySize]; + float[] frontGain = new float[arraySize]; + for (int i=0; i< arraySize; i++) { + frontDistance[i] = attenuation[i].x; + frontGain[i] = attenuation[i].y; + } + audioDevice3D.setDistanceGain(index, frontDistance, + frontGain, null, null); + } + } // PointSound distanceGain + } + + if ((sound instanceof ConeSoundRetained) && + (updateAll || soundAtom.testDirtyFlag(soundAtom.attribsDirty, + SoundRetained.ANGULAR_ATTENUATION_DIRTY_BIT)) ) { + + // set angular attenuation + ConeSoundRetained cnSound = (ConeSoundRetained)sound; + arraySize = cnSound.getAngularAttenuationLength(); + if (arraySize == 0) { + // send default + double[] angle = new double[2]; + float[] scaleFactor = new float[2]; + angle[0] = 0.0; + angle[1] = (Math.PI)/2.0; + scaleFactor[0] = 1.0f; + scaleFactor[1] = 0.0f; + audioDevice3D.setAngularAttenuation(index, + cnSound.NO_FILTERING, + angle, scaleFactor, null); + } + else { + Point3f[] attenuation = new Point3f[arraySize]; + for (int i=0; i< arraySize; i++) { + attenuation[i] = new Point3f(); + } + cnSound.getAngularAttenuation(attenuation); + double[] angle = new double[arraySize]; + float[] scaleFactor = new float[arraySize]; + float[] cutoff = new float[arraySize]; + for (int i=0; i< arraySize; i++) { + angle[i] = attenuation[i].x; + scaleFactor[i] = attenuation[i].y; + cutoff[i] = attenuation[i].z; + } + audioDevice3D.setAngularAttenuation(index, + cnSound.filterType, + angle, scaleFactor, cutoff); + } + } + } + + + /** + * Check (and set if necessary) AudioDevice3D field + */ + boolean checkAudioDevice3D() { + if (universe != null) { + if (universe.currentView != null) + if (universe.currentView.physicalEnvironment != null) { + audioDevice = universe.currentView.physicalEnvironment.audioDevice; + if (audioDevice != null) { + if (audioDevice instanceof AudioDevice3DL2) { + audioDevice3DL2 = (AudioDevice3DL2)audioDevice; + } + if (audioDevice instanceof AudioDevice3D) { + audioDevice3D = (AudioDevice3D)audioDevice; + } + else { // audioDevice is only an instance of AudioDevice + if (internalErrors) + debugPrint("AudioDevice implementation not supported"); + // audioDevice3D should already be null + } + } + else { + // if audioDevice is null, clear extended class fields + audioDevice3DL2 = null; + audioDevice3D = null; + } + } + } + if (audioDevice3D == null) + return false; + + if (audioDevice3D.getTotalChannels() == 0) + return false; // can not render sounds on AudioEngine that has no channels + + return true; + } + + + /** + * Clears the fields associated with sample data for this sound. + * Assumes soundAtom is non-null, and that non-null atom + * would have non-null sound field. + */ + void clearSoundData(SoundSchedulerAtom soundAtom) { + if (checkAudioDevice3D() && + soundAtom.sampleId != SoundRetained.NULL_SOUND) { + stopSound(soundAtom, false); // force stop of playing sound + // Unload sound data from AudioDevice + audioDevice3D.clearSound(soundAtom.sampleId); + } + + soundAtom.sampleId = SoundRetained.NULL_SOUND; + // set load state into atom + soundAtom.loadStatus = SoundRetained.LOAD_NULL; + // NOTE: setting node load status not 1-to-1 w/actual load; + // this is incorrect + SoundRetained sound = soundAtom.sound; + soundAtom.loadStatus = SoundRetained.LOAD_NULL; + soundAtom.soundData = null; + sound.changeAtomList(soundAtom, SoundRetained.LOAD_NULL); + } + + + /** + * Attempts to load sound data for a particular sound source onto + * the chosen/initialized audio device + * If this called, it is assumed that SoundRetained.audioDevice is + * NOT null. + * If an error in loading occurs (an exception is caught,...) + * an error is printed out to stderr - an exception is not thrown. + * @param soundData descrition of sound source data + */ + // QUESTION: should this method be synchronized? + void attachSoundData(SoundSchedulerAtom soundAtom, + MediaContainer soundData, boolean forceReload) { + + if (!forceReload && (soundAtom.soundData == soundData)) { + return; + } + SoundRetained sound = soundAtom.sound.sgSound; + if (!checkAudioDevice3D()) { + if (debugFlag) + debugPrint(".attachSoundData audioDevice3D null"); + soundAtom.loadStatus = SoundRetained.LOAD_PENDING; + sound.changeAtomList(soundAtom, SoundRetained.LOAD_PENDING); + return; + } + if (soundAtom.soundData != null) { + // clear sound data field for view specific atom NOT sound node + clearSoundData(soundAtom); + if (soundData == null) { + if (debugFlag) + debugPrint(".attachSoundData with null soundData"); + return; + } + } + + URL url = ((MediaContainerRetained)sound.soundData.retained).url; + String path = ((MediaContainerRetained)sound.soundData.retained).urlString; + InputStream stream = ((MediaContainerRetained)sound.soundData.retained).inputStream; + if (url == null && path == null && stream == null) { + if (debugFlag) + debugPrint(".attachSoundData with null soundData"); + // clear non-null sample associated with this soundData + if (soundAtom.sampleId != SoundRetained.NULL_SOUND) { + clearSoundData(soundAtom); + } + return; + } + + int id; + if (sound instanceof ConeSoundRetained) + sound.soundType = AudioDevice3D.CONE_SOUND; + else if (sound instanceof PointSoundRetained) + sound.soundType = AudioDevice3D.POINT_SOUND; + else + sound.soundType = AudioDevice3D.BACKGROUND_SOUND; + if (debugFlag) { + debugPrint(".attachSoundData soundType = " + sound.soundType); + debugPrint(".attachSoundData this is = " + sound); + } + + // Clone the MediaContainer associated with this node and + // set the capability bits for this clone to allow access to + // all fields; this copy is passed to the audioDevice. + // As the fields of the MediaContainer expands, this code must + // be appended. + MediaContainer cloneMediaContainer = new MediaContainer(); + cloneMediaContainer.duplicateAttributes(soundData, true); + cloneMediaContainer.setCapability(MediaContainer.ALLOW_CACHE_READ); + cloneMediaContainer.setCapability(MediaContainer.ALLOW_URL_READ); + + id = audioDevice3D.prepareSound(sound.soundType, cloneMediaContainer); + if (debugFlag) + debugPrint(".attachSoundData prepareSound returned " + id); + + if (id == SoundRetained.NULL_SOUND) { + soundAtom.loadStatus = SoundRetained.LOAD_FAILED; + // NOTE: setting node load status not 1-to-1 with actual load; + // this is incorrect + sound.changeAtomList(soundAtom, SoundRetained.LOAD_FAILED); + //System.err.println(path + ": "+ J3dI18N.getString("SoundRetained1")); + } + else { + if (debugFlag) + debugPrint(".attachSoundData - sampleId set"); + soundAtom.sampleId = id; + + // For now loopLength=sampleLength, loop points not supported + long duration = audioDevice3D.getSampleDuration(id); + soundAtom.sampleLength = duration; + soundAtom.loopLength = soundAtom.sampleLength; + + // TODO: for most this will be 0 but not all + soundAtom.loopStartOffset = 0; + soundAtom.attackLength = 0; // portion of sample before loop section + soundAtom.releaseLength = 0; // portion of sample after loop section + soundAtom.loadStatus = SoundRetained.LOAD_COMPLETE; + soundAtom.soundData = soundData; + sound.changeAtomList(soundAtom, SoundRetained.LOAD_COMPLETE); + if (debugFlag) + debugPrint(" attachSoundData; index = "+soundAtom.sampleId); + } + } + + + SoundSchedulerAtom findSoundAtom(SoundRetained node, int nthInstance) { + // find nth sound atom in the list of prioritized sounds that + // references this sound node + // nthInstance=1 would look for first instance + if (node == null) + return null; + SoundSchedulerAtom returnAtom = null; + synchronized (prioritizedSounds) { + if (!prioritizedSounds.isEmpty()) { + SoundSchedulerAtom soundAtom = null; + int atomFound = 0; + // find sound in list and remove it + int arrSize = prioritizedSounds.size(); + for (int index=0; index<arrSize; index++) { + soundAtom = (SoundSchedulerAtom)prioritizedSounds.get(index); + if (soundAtom.sound == null) + continue; + // soundAtom.sound is address of mirror sound not org node + if (soundAtom.sound.sgSound == node) { + atomFound++; + // orginal app node pass into method + // QUESTION: is mirror node still correct? + // TODO: ensure only mirror nodes passed into method + if (atomFound == nthInstance) { + returnAtom = soundAtom; + break; + } + } + else if (soundAtom.sound.sgSound == node.sgSound) { + atomFound++; + // store potentially new mirror sound into soundAtom + soundAtom.sound = node; + if (atomFound == nthInstance) { + returnAtom = soundAtom; + break; + } + } + } + } + } + return returnAtom; + } + + + /** + * 'Dirty' flag == listenerUpdated flag + * The ambiguous name 'dirtyFlag' is a legacy from when there was only a + * single dirty flag set by Core yet tested and cleared in this scheduler. + * These methods specifically set/test/clear the local listenerUpdated flag. + */ + // Called by CanvasViewCache when listener parameter(s) changes + void setListenerFlag(int flag) { + listenerUpdated |= flag; + } + + void clearListenerFlag() { + listenerUpdated = 0x0; + } + + boolean testListenerFlag() { + // Test if any bits are on + if (listenerUpdated > 0) + return true; + else + return false; + } + + /** + * set dirty flags associated with SoundSchedulerAtom + */ + void setAttribsDirtyFlag(SoundRetained node, int dirtyFlag) { + if (debugFlag) + debugPrint(".setAttribsDirtyFlag " + node ); + // find sound atom that references this sound node + SoundSchedulerAtom soundAtom = null; + for (int i=1; ;i++) { + soundAtom = findSoundAtom(node, i); + if (soundAtom == null) + break; + soundAtom.setAttribsDirtyFlag(dirtyFlag); + } + } + + void setStateDirtyFlag(SoundRetained node, int dirtyFlag) { + if (debugFlag) + debugPrint(".setStateDirtyFlag " + node ); + // find sound atom that references this sound node + SoundSchedulerAtom soundAtom = null; + for (int i=1; ;i++) { + soundAtom = findSoundAtom(node, i); + if (soundAtom == null) + break; + soundAtom.setStateDirtyFlag(dirtyFlag); + } + } + + + void printAtomState(SoundSchedulerAtom atom) { + SoundRetained sound = atom.sound.sgSound; + debugPrint(" this atom = " + atom + " "); + debugPrint(" references sound = " + sound + " "); + debugPrint(" enabled " + atom.enabled); + debugPrint(" status " + atom.status); + debugPrint(" activated " + atom.activated); + debugPrint(" released " + sound.release); + debugPrint(" continuous " + sound.continuous); + debugPrint(" scheduling " + atom.schedulingAction); + } + + // Debug print mechanism for Sound nodes + + static final boolean debugFlag = false; + static final boolean internalErrors = false; + + void debugPrint(String message) { + if (debugFlag) + System.out.println("SS."+message); + } + + void processViewSpecificGroupChanged(J3dMessage m) { + int component = ((Integer)m.args[0]).intValue(); + Object[] objAry = (Object[])m.args[1]; + if (((component & ViewSpecificGroupRetained.ADD_VIEW) != 0) || + ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { + int i; + Object obj; + View v = (View)objAry[0]; + ArrayList leafList = (ArrayList)objAry[2]; + // View being added is this view + if (v == view) { + int size = leafList.size(); + for (i = 0; i < size; i++) { + obj = leafList.get(i); + if (obj instanceof SoundRetained) { + nRetainedSounds++; + addSound((SoundRetained) obj); + } + else if (obj instanceof SoundscapeRetained) { + auralAttribsChanged = true; + } + } + + } + + } + if (((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0)|| + ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { + int i; + Object obj; + ArrayList leafList; + View v; + + if ((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0) { + v = (View)objAry[0]; + leafList = (ArrayList)objAry[2]; + } + else { + v = (View)objAry[4]; + leafList = (ArrayList)objAry[6]; + } + if (v == view) { + int size = leafList.size(); + for (i = 0; i < size; i++) { + obj = leafList.get(i); + if (obj instanceof SoundRetained) { + SoundSchedulerAtom soundAtom = null; + for (int arrIndx=1; ;arrIndx++) { + soundAtom = findSoundAtom((SoundRetained)obj, + arrIndx); + if (soundAtom == null) + break; + stopSound(soundAtom, false); + } + } + else if (obj instanceof SoundscapeRetained) { + auralAttribsChanged = true; + } + } + } + } + + } + + void processBoundingLeafChanged(J3dMessage m) { + // Notify all users of this bounding leaf, it may + // result in the re-evaluation of the lights/fogs/backgrounds + Object[] users = (Object[])(m.args[3]); + int i; + + for (i = 0; i < users.length; i++) { + LeafRetained leaf = (LeafRetained)users[i]; + if (leaf instanceof SoundRetained && universe.soundStructure.isSoundScopedToView(leaf, view)) { + auralAttribsChanged = true; + } + else if (leaf instanceof SoundscapeRetained && universe.soundStructure.isSoundscapeScopedToView(leaf, view)){ + auralAttribsChanged = true; + } + } + } + + void cleanup() { + // clean up any messages that are queued up, since they are + // irrelevant + // clearMessages(); + } +} diff --git a/src/classes/share/javax/media/j3d/SoundSchedulerAtom.java b/src/classes/share/javax/media/j3d/SoundSchedulerAtom.java new file mode 100644 index 0000000..224d807 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SoundSchedulerAtom.java @@ -0,0 +1,694 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * A SoundSchedulerAtom is the smallest object representing a Sound within + * SoundScheduler. This class contains View-Depedent fields. Some of these + * fields may appear to over lap fields in the Sound Node classes, but + * remember that the Sound Node fields are universal, user-defined fields + * and do not take into account specific Audio Device view-dependent + * conditions. + */ + +class SoundSchedulerAtom extends Object { + + /** + * The mirror sound node component of this sound scheduler atom + */ + SoundRetained sound = null; + + /** + * MediaContainer currently loaded for this atom + */ + MediaContainer soundData = null; + + // Maintain continuously playing silent sound sources. + long startTime = 0; + long endTime = 0; + + long sampleLength = 0; + long loopStartOffset = 0; // for most this will be 0 + long loopLength = 0; // for most this is end sample - sampleLength + long attackLength = 0; // portion of sample before loop section + long releaseLength = 0; // portion of sample after loop section + + int loadStatus = SoundRetained.LOAD_NULL; + boolean playing = false; + int numberChannels = 0; + + /** + * Is this sound in an active scheduling region + */ + boolean activated = false; + + /** + * Switch for turning sound on or off while the sound is "active" + */ + static final int OFF = 0; + static final int ON = 1; + static final int PENDING_ON = 2; + static final int PENDING_OFF = 3; + int enabled = OFF; + + /** + * Switch for muting and unmuting sound while it is playing + */ + static final int UNMUTED = 0; + static final int MUTED = 1; + static final int PENDING_UNMUTE = 2; + static final int PENDING_MUTE = 3; + int muted = UNMUTED; + + /** + * Switch for pausing and unpausing sound while it is playing + */ + static final int UNPAUSED = 0; // or resumed + static final int PAUSED = 1; + static final int PENDING_UNPAUSE = 2; // or pending resume + static final int PENDING_PAUSE = 3; + int paused = UNPAUSED; + + + /** + * Pending action for this sound determined by the SoundScheduler + */ + static final int DO_NOTHING = 0; + static final int LEAVE_OFF = 1; + static final int LEAVE_SILENT = 2; + static final int LEAVE_AUDIBLE = 3; + static final int LEAVE_PAUSED = 4; + + static final int RESTART_AUDIBLE = 5; + static final int START_AUDIBLE = 6; + static final int RESTART_SILENT = 7; + static final int START_SILENT = 8; + + static final int MAKE_AUDIBLE = 11; + static final int MAKE_SILENT = 12; + static final int PAUSE_AUDIBLE = 13; + static final int PAUSE_SILENT = 14; + static final int RESUME_AUDIBLE = 15; + static final int RESUME_SILENT = 16; + static final int TURN_OFF = 17; + static final int UPDATE = 18; + static final int COMPLETE = 19; + int schedulingAction = DO_NOTHING; + + /** + * This status flag is used for sound scheduling + */ + static final int SOUND_OFF = 0; // The sound is not playing + static final int SOUND_AUDIBLE = 1; // The sound is potentially audible + static final int SOUND_SILENT = 2; // The sound is playing silently + static final int SOUND_PAUSED = 3; // The sound is playing silently + static final int SOUND_COMPLETE = 4; // The sound is finished playing + int status = SOUND_OFF; + + // Sound atoms have two dirty flags: attribsDirty for sound node fields + // and stateDirty for changes to sound state not reflected by sound fields. + // When the field/parameter associated with the dirty bit has been: + // passed to all SoundSchedulers to update sound rendering or 'run' state + // the bit for that field is cleared by the SoundStructure thread. + + /** + * attribsDirty bit field + * This bitmask is set when sound node attribute is changed by the user. + */ + int attribsDirty = 0x0000; + + /** + * stateDirty bit field + * This bitmask is set when scene graph state is changed. + */ + int stateDirty = 0x0000; + + // Load Sound Data Status maintained in SoundRetained class + + /** + * Identifiers of sample associated with sound source + */ + int sampleId = SoundRetained.NULL_SOUND; + + /** + * reference to Sound Scheduler this atom is associated with + */ + SoundScheduler soundScheduler = null; + + + /** + * Calculate absolute time at which sample completes + * Checks playing flag denoting if sound is started already or not: + * false - calcalutes endTime in relation to startTime + * true - re-calculates endTime based on current position in + * loop portion of sample plus release length + */ + synchronized void calculateEndTime() { + SoundRetained sgSound = sound.sgSound; + int loops = sgSound.loopCount; + if (debugFlag) + debugPrint("calculateEndTime: loop count = " + loops); + // test lengths for <= 0; this includes DURATION_UNKNOWN + if ( (sampleLength <= 0 || loopLength <= 0 || loops < 0 ) +// QUESTION: removed? but what was this trying to avoid +// changing endTime when that is already set? +// but what happens when user changes LoopCount AFTER +// sound is started - should be able to do this +// && (enabled == OFF || enabled == PENDING_OFF) + ) { + endTime = -1; + if (debugFlag) + debugPrint("calculateEndTime: set to -1"); + } + else { +// QUESTION: if "&& playing" is in above test; won't we have to test for +// length unknown and loop = -1?? + if (playing && (startTime > 0)) { + endTime = startTime + attackLength + + (loopLength * (loops+1)) + releaseLength; if (debugFlag) + debugPrint("calculateEndTime: isPlaying so = " + endTime); } + else { + // Called when release flag is true + // Determine where within the loop portion sample the + // sound is currently playing, then set endTime to + // play remaining portion of loop portion plus the + // release portion. + long currentTime = System.currentTimeMillis(); + endTime = currentTime + ( (loopLength - + ((currentTime - startTime - attackLength) % loopLength)) + + releaseLength ); + if (debugFlag) + debugPrint("calculateEndTime: NOT Playing so = " + endTime); + } + } + } + + + void enable(boolean enabled) { + if (enabled) { + setEnableState(PENDING_ON); + if (debugFlag) + debugPrint(" enableSound calls soundAtom " + + this + " setEnableState PENDING_ON"); + } + else { + setEnableState(PENDING_OFF); + if (debugFlag) + debugPrint(" enableSound calls soundAtom " + + this + " setEnableState PENDING_OFF"); + } + } + + + void mute(boolean muted) { + if (muted) { + setMuteState(PENDING_MUTE); + if (debugFlag) + debugPrint(" muteSound() calls soundAtom " + + this + " setMuteState PENDING_ON"); + } + else { + setMuteState(PENDING_UNMUTE); + if (debugFlag) + debugPrint(" muteSound() calls soundAtom " + + this + " setMuteState PENDING_UNMUTE"); + } + } + + void pause(boolean paused) { + if (paused) { + setPauseState(PENDING_PAUSE); + if (debugFlag) + debugPrint(this + ".pause calls setPauseState(PENDING_PAUSE)"); + } + else { + setPauseState(PENDING_UNPAUSE); + if (debugFlag) + debugPrint(this +".pause calls setPauseState(PENDING_UNPAUSE)"); + } + } + + +// TODO: remove this +// just set the state after debug no longer needed + void setEnableState(int state) { + enabled = state; + switch (state) { + case PENDING_ON: + if (debugFlag) + debugPrint("set enabled to PENDING_ON"); + break; + case ON: + if (debugFlag) + debugPrint("set enabled to ON"); + break; + case PENDING_OFF: + if (debugFlag) + debugPrint("set enabled to PENDING_OFF"); + break; + case OFF: + if (debugFlag) + debugPrint("set enabled to OFF"); + break; + default: + if (debugFlag) + debugPrint("state = " + state); + break; + } + } + +// TODO: remove this +// just set the state after debug no longer needed + void setMuteState(int state) { + muted = state; + switch (state) { + case PENDING_MUTE: + if (debugFlag) + debugPrint("set mute to PENDING_MUTE"); + break; + case MUTED: + if (debugFlag) + debugPrint("set mute to MUTE"); + break; + case PENDING_UNMUTE: + if (debugFlag) + debugPrint("set mute to PENDING_UNMUTE"); + break; + case UNMUTED: + if (debugFlag) + debugPrint("set mute to UNMUTE"); + break; + default: + if (debugFlag) + debugPrint("state = " + state); + break; + } + } + +// TODO: remove this +// just set the state after debug no longer needed + void setPauseState(int state) { + paused = state; + switch (state) { + case PENDING_PAUSE: + if (debugFlag) + debugPrint("set pause to PENDING_PAUSE"); + break; + case PAUSED: + if (debugFlag) + debugPrint("set pause to PAUSE"); + break; + case PENDING_UNPAUSE: + if (debugFlag) + debugPrint("set pause to PENDING_UNPAUSE"); + break; + case UNPAUSED: + if (debugFlag) + debugPrint("set pause to UNPAUSE"); + break; + default: + if (debugFlag) + debugPrint("state = " + state); + break; + } + } + + + /** + * calcActiveSchedAction() + * Calculate Sound Scheduler Action for Active sound (it's region + * intersects the viewPlatform). + * + * A big switch testing various SoundRetained fields to determine + * what SoundScheduler action to perform when sound is Active + * set sound active flag true + * switch on enable value, to set pending scheduling action + * depending on continuous and release flags and sound status + */ + synchronized int calcActiveSchedAction() { + SoundRetained sgSound = sound.sgSound; + int action = DO_NOTHING; + activated = true; + switch (enabled) { + case PENDING_ON: + setEnableState(ON); + if (debugFlag) + debugPrint(" calcActiveSchedAction: PENDING_ON"); + if (status == SOUND_OFF || + status == SOUND_PAUSED) + action = START_AUDIBLE; + else + action = RESTART_AUDIBLE; + break; + case ON: + if (debugFlag) + debugPrint(" calcActiveSchedAction: ON"); + if (status == SOUND_OFF) + // should NOT see this, but if we do... + action = START_AUDIBLE; + else if (status == SOUND_SILENT) + action = MAKE_AUDIBLE; + else // status == SOUND_AUDIBLE + action = LEAVE_AUDIBLE; + break; + case PENDING_OFF: + setEnableState(OFF); + if (debugFlag) + debugPrint("enable = " + enabled + + "enabled set to OFF"); + // fail thru + case OFF: + // QUESTION: Why would enable status ever be OFF yet + // status SOUND_AUDIBLE or _SILENT? + if (status == SOUND_AUDIBLE) { + if (sgSound.release) { + if (debugFlag) + debugPrint("enable = " + enabled + + ", AUDIBLE, released, " + + "action <- LEAVE_AUDIBLE"); + if (enabled == PENDING_OFF) { + // re-calculate EndTime + calculateEndTime(); + } + action = LEAVE_AUDIBLE; + } + else { + if (debugFlag) + debugPrint("enable = " + enabled + + ", AUDIBLE, not released, "+ + "action <- TURN_OFF"); + action = TURN_OFF; + } + } + else if (status == SOUND_SILENT) { + if (sgSound.release) { + if (debugFlag) + debugPrint("enable = " + enabled + + ", SILENT, released, " + + "action <- MAKE_AUDIBLE"); + // re-calculate EndTime + calculateEndTime(); + action = MAKE_AUDIBLE; + } + else { + if (debugFlag) + debugPrint("enable = " + enabled + + ", SILENT, not released, " + + "action <- TURN_OFF"); + action = TURN_OFF; + } + } + else { // status == SOUND_OFF + action = LEAVE_OFF; + } + break; + } // switch on enabled flag + + // if sounds pause state is PENDING_PAUSE modify action to perform. + if (paused == PENDING_PAUSE) { + // if this pause state is set to PAUSE then assume the sound is + // already paused, so any incoming action that leave the state + // as it already is, leaves the sound paused. + if (debugFlag) + debugPrint(" PENDING_PAUSE"); + switch (action) { + case MAKE_AUDIBLE: + case LEAVE_AUDIBLE: + case RESUME_AUDIBLE: + action = PAUSE_AUDIBLE; + break; + case MAKE_SILENT: + case LEAVE_SILENT: + case RESUME_SILENT: + action = PAUSE_SILENT; + break; + default: + // don't change action for any other cases + break; + } + } + // if sounds pause state is PENDING_UNPAUSE modify action + else if (paused == PENDING_UNPAUSE) { + debugPrint(" PENDING_UNPAUSE"); + switch (action) { + // When restart (audible or silent) pause flag is checked and + // explicitly set in SoundScheduler + case MAKE_AUDIBLE: + case LEAVE_AUDIBLE: + case PAUSE_AUDIBLE: + action = RESUME_AUDIBLE; + break; + case MAKE_SILENT: + case LEAVE_SILENT: + case PAUSE_SILENT: + action = RESUME_SILENT; + break; + default: + // don't change action for any other cases + break; + } + } + return(action); + } // end of calcActiveSchedAction + + + /** + * calcInactiveSchedAction() + * Calculate Sound Scheduler action for Inactive sound + * + * A big switch testing various SoundRetained fields to determine + * what SoundScheduler action to perform when sound is inactive. + * set sound active flag false + * switch on enable value, to set pending scheduling action + * depending on continuous and release flags and sound status + */ + synchronized int calcInactiveSchedAction() { + int action = DO_NOTHING; + SoundRetained sgSound = sound.sgSound; + + // Sound is Inactive + // Generally, sound is OFF unless continuous flag true + // then sound is silently playing if on. + activated = false; + + switch (enabled) { + case PENDING_ON: + if (debugFlag) + debugPrint(" calcInactiveSchedAction: PENDING_ON "); + setEnableState(ON); + if (sgSound.continuous) { + if (status == SOUND_OFF) + action = START_SILENT; + else // status == SOUND_AUDIBLE or SOUND_SILENT + action = RESTART_SILENT; + } + else { // sound is not continuous + if (status == SOUND_OFF) + action = LEAVE_OFF; + else // status == SOUND_SILENT || SOUND_AUDIBLE + action = TURN_OFF; + } + break; + case ON: + if (debugFlag) + debugPrint(" calcInactiveSchedActio: ON "); + if (sgSound.continuous) { + if (status == SOUND_AUDIBLE) + action = MAKE_SILENT; + else if (status == SOUND_OFF) + action = START_SILENT; + else // status == SOUND_SILENT + action = LEAVE_SILENT; + } + else { // sound is not continuous + // nothing to do if already off + if (status == SOUND_OFF) + action = LEAVE_OFF; + else // status == SOUND_SILENT or SOUND_AUDIBLE + action = TURN_OFF; + } + break; + case PENDING_OFF: + setEnableState(OFF); + if (debugFlag) + debugPrint("Enable = " + enabled + + "enabled set to OFF"); + // fall thru + + case OFF: + if (sgSound.release && sgSound.continuous) { + if (enabled == PENDING_OFF) { + // re-calculate EndTime + calculateEndTime(); + } + if (status == SOUND_AUDIBLE) { + if (debugFlag) + debugPrint("Enable = " + enabled + + ", AUDIBLE, released & continuous - " + + "action <- MAKE_SILENT"); + action = MAKE_SILENT; + } + else if (status == SOUND_SILENT) { + if (debugFlag) + debugPrint("Enable = " + enabled + + ", SILENT, released & continuous - " + + "action <- TURN_OFF"); + action = LEAVE_SILENT; + } + else { + if (debugFlag) + debugPrint("Enable = " + enabled + + ", already OFF, action <- LEAVE_OFF"); + action = LEAVE_OFF; + } + } + else { // continuous and release flag not both true + if (status == SOUND_OFF) { + if (debugFlag) + debugPrint("Enable = " + enabled + + ", already OFF, action <- LEAVE_OFF"); + action = LEAVE_OFF; + } + else { + if (debugFlag) + debugPrint("Enable = " + enabled + + ", not already OFF, action <- TURN_OFF"); + action = TURN_OFF; + } + } + break; + default: + break; + } // switch + + // if sounds pause state is PENDING_PAUSE modify action to perform. + if (paused == PENDING_PAUSE) { + // if this pause state is set to PAUSE then assume the sound is + // already paused, so any incoming action that leave the state + // as it already is, leaves the sound paused. + switch (action) { + case MAKE_SILENT: + case LEAVE_SILENT: + case RESUME_SILENT: + action = PAUSE_SILENT; + break; + default: + // don't change action for any other cases + break; + } + } + // if sounds pause state is PENDING_UNPAUSE modify action + else if (paused == PENDING_UNPAUSE) { + switch (action) { + case LEAVE_SILENT: + action = RESUME_SILENT; + break; + default: + // don't change action for any other cases + break; + } + } + return (action); + } // end of calcInactiveSchedAction + +// TODO isPLaying +// TODO setLoadingState + + // Debug print mechanism for Sound nodes + static final boolean debugFlag = false; + static final boolean internalErrors = false; + + void debugPrint(String message) { + if (debugFlag) { + System.out.println(message); + } + } + + + /** + * Set bit(s) in soundDirty field + * @param binary flag denotes bits to set ON + */ + void setAttribsDirtyFlag(int bitFlag) { + attribsDirty |= bitFlag; + if (debugFlag) + debugPrint("setAttribsDirtyFlag = " + bitFlag); + return ; + } + void setStateDirtyFlag(int bitFlag) { + stateDirty |= bitFlag; + if (debugFlag) + debugPrint("setStateDirtyFlag = " + bitFlag); + return ; + } + + /** + * Clear sound's dirty flag bit value. + * @param binary flag denotes bits to set OFF + */ + void clearAttribsDirtyFlag(int bitFlag) { + if (debugFlag) + debugPrint("clearAttribsDirtyFlag = " + bitFlag); + attribsDirty &= ~bitFlag; + return ; + } + void clearAttribsDirtyFlag() { + // clear all bits + if (debugFlag) + debugPrint("clearAttribsDirtyFlag = ALL"); + attribsDirty = 0x0; + return ; + } + void clearStateDirtyFlag(int bitFlag) { + if (debugFlag) + debugPrint("clearStateDirtyFlag = " + bitFlag); + stateDirty &= ~bitFlag; + return ; + } + void clearStateDirtyFlag() { + if (debugFlag) + debugPrint("clearStateDirtyFlag = ALL"); + stateDirty = 0x0; + return ; + } + + + /** + * Test sound's dirty flag bit(s) + * @param field denotes which bitmask to set into + * @param binary flag denotes bits to set Test + * @return true if bit(s) in bitFlag are set dirty (on) + */ + boolean testDirtyFlag(int field, int bitFlag) { + if ((field & bitFlag) > 0) + return true; + else + return false; + } + + /** + * Test sound's dirty flags for ANY bits on + * @return true if any bit in bitFlag is flipped on + */ + boolean testDirtyFlags() { + if ((attribsDirty & 0xFFFF) > 0) + return true; + else if ((stateDirty & 0xFFFF) > 0) + return true; + else + return false; + } + +} diff --git a/src/classes/share/javax/media/j3d/SoundStructure.java b/src/classes/share/javax/media/j3d/SoundStructure.java new file mode 100644 index 0000000..ac36949 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SoundStructure.java @@ -0,0 +1,721 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +/** + * A sound structure is a object that organizes Sounds and + * soundscapes. + * This structure parallels the RenderingEnv structure and + * is used for sounds + */ + +class SoundStructure extends J3dStructure { + /** + * The list of Sound nodes + */ + UnorderList nonViewScopedSounds = new UnorderList(SoundRetained.class); + HashMap viewScopedSounds = new HashMap(); + + /** + * The list of Soundscapes + */ + UnorderList nonViewScopedSoundscapes = new UnorderList(SoundscapeRetained.class); + HashMap viewScopedSoundscapes = new HashMap(); + + /** + * The list of view platforms + */ + UnorderList viewPlatforms = new UnorderList(ViewPlatformRetained.class); + + /** + * A bounds used for getting a view platform scheduling BoundingSphere + */ + BoundingSphere tempSphere = new BoundingSphere(); + BoundingSphere vpsphere = new BoundingSphere(); + + // ArrayList of leafRetained object whose mirrorObjects + // should be updated + ArrayList objList = new ArrayList(); + + // ArrayList of leafRetained object whose boundingleaf xform + // should be updated + ArrayList xformChangeList = new ArrayList(); + + // ArrayList of switches that have changed + ArrayList switchChangeLeafNodes = new ArrayList(); + ArrayList switchChangeLeafMasks = new ArrayList(); + + // variables for processing transform messages + boolean transformMsg = false; + UpdateTargets targets = null; + + /** + * This constructor does nothing + */ + SoundStructure(VirtualUniverse u) { + super(u, J3dThread.UPDATE_SOUND); + if (debugFlag) + debugPrint("SoundStructure constructed"); + } + + void processMessages(long referenceTime) { + J3dMessage messages[] = getMessages(referenceTime); + int nMsg = getNumMessage(); + J3dMessage m; + + if (nMsg <= 0) { + return; + } + + + for (int i=0; i < nMsg; i++) { + m = messages[i]; + + switch (m.type) { + case J3dMessage.INSERT_NODES : + // Prioritize retained and non-retained sounds for this view + insertNodes(m); + break; + case J3dMessage.REMOVE_NODES: + removeNodes(m); + break; + case J3dMessage.SOUND_ATTRIB_CHANGED: + changeNodeAttrib(m); + break; + case J3dMessage.SOUND_STATE_CHANGED: + changeNodeState(m); + break; + case J3dMessage.SOUNDSCAPE_CHANGED: + case J3dMessage.AURALATTRIBUTES_CHANGED: + // TODO: this needs to be changed + changeNodeAttrib(m); + break; + case J3dMessage.TRANSFORM_CHANGED: + transformMsg = true; + break; + case J3dMessage.SWITCH_CHANGED: + processSwitchChanged(m); + // may need to process dirty switched-on transform + if (universe.transformStructure.getLazyUpdate()) { + transformMsg = true; + } + break; + case J3dMessage.VIEWSPECIFICGROUP_CHANGED: + updateViewSpecificGroupChanged(m); + break; + // TODO: case J3dMessage.BOUNDINGLEAF_CHANGED + } + + /* + // NOTE: this should already be handled by including/ORing + // SOUND_SCHEDULER in targetThread for these message types!! + // Dispatch a message about a sound change + ViewPlatformRetained vpLists[] = (ViewPlatformRetained []) + viewPlatforms.toArray(false); + + // QUESTION: can I just use this message to pass to all the Sound Bins + for (int k=viewPlatforms.arraySize()- 1; k>=0; k--) { + View[] views = vpLists[k].getViewList(); + for (int j=(views.length-1); j>=0; j--) { + View v = (View)(views[j]); + m.view = v; + VirtualUniverse.mc.processMessage(m); + } + } + */ + m.decRefcount(); + } + if (transformMsg) { + targets = universe.transformStructure.getTargetList(); + updateTransformChange(targets, referenceTime); + transformMsg = false; + targets = null; + } + + Arrays.fill(messages, 0, nMsg, null); + } + + void insertNodes(J3dMessage m) { + Object[] nodes = (Object[])m.args[0]; + ArrayList viewScopedNodes = (ArrayList)m.args[3]; + ArrayList scopedNodesViewList = (ArrayList)m.args[4]; + Object node; + + for (int i=0; i<nodes.length; i++) { + node = (Object) nodes[i]; + if (node instanceof SoundRetained) { + addNonScopedSound((SoundRetained) node); + } + if (node instanceof SoundscapeRetained) { + addNonSoundscape((SoundscapeRetained) node); + } + } + // Handle ViewScoped Nodes + if (viewScopedNodes != null) { + int size = viewScopedNodes.size(); + int vlsize; + for (int i = 0; i < size; i++) { + node = (NodeRetained)viewScopedNodes.get(i); + ArrayList vl = (ArrayList) scopedNodesViewList.get(i); + int vsize = vl.size(); + if (node instanceof SoundRetained) { + ((SoundRetained)node).isViewScoped = true; + for (int k = 0; k < vsize; k++) { + View view = (View) vl.get(k); + addScopedSound((SoundRetained) node, view); + } + } + else if (node instanceof SoundscapeRetained) { + ((SoundscapeRetained)node).isViewScoped = true; + for (int k = 0; k < vsize; k++) { + View view = (View) vl.get(k); + addScopedSoundscape((SoundscapeRetained) node, view); + } + } + } + } + /* + // TODO: + if (node instanceof AuralAttributesRetained) { + } + else if (node instanceof ViewPlatformRetained) { + addViewPlatform((ViewPlatformRetained) node); + } + */ + } + + + /** + * Add sound to sounds list. + */ + void addScopedSound(SoundRetained mirSound, View view) { + if (debugFlag) + debugPrint("SoundStructure.addSound()"); + ArrayList l = (ArrayList) viewScopedSounds.get(view); + if (l == null) { + l = new ArrayList(); + viewScopedSounds.put(view, l); + } + l.add(mirSound); + } // end addSound() + + void addNonScopedSound(SoundRetained mirSound) { + if (debugFlag) + debugPrint("SoundStructure.addSound()"); + nonViewScopedSounds.add(mirSound); + } // end addSound() + + + void addScopedSoundscape(SoundscapeRetained soundscape, View view) { + if (debugFlag) + debugPrint("SoundStructure.addSoundscape()"); + ArrayList l = (ArrayList) viewScopedSoundscapes.get(view); + if (l == null) { + l = new ArrayList(); + viewScopedSoundscapes.put(view, l); + } + l.add(soundscape); + } + + void addNonSoundscape(SoundscapeRetained soundscape) { + if (debugFlag) + debugPrint("SoundStructure.addSoundscape()"); + nonViewScopedSoundscapes.add(soundscape); + } + + + void removeNodes(J3dMessage m) { + Object[] nodes = (Object[])m.args[0]; + ArrayList viewScopedNodes = (ArrayList)m.args[3]; + ArrayList scopedNodesViewList = (ArrayList)m.args[4]; + Object node; + + for (int i=0; i<nodes.length; i++) { + node = (Object) nodes[i]; + if (node instanceof SoundRetained) { + deleteNonScopedSound((SoundRetained) node); + } + if (node instanceof SoundscapeRetained) { + deleteNonScopedSoundscape((SoundscapeRetained) node); + } + } + // Handle ViewScoped Nodes + if (viewScopedNodes != null) { + int size = viewScopedNodes.size(); + int vlsize; + for (int i = 0; i < size; i++) { + node = (NodeRetained)viewScopedNodes.get(i); + ArrayList vl = (ArrayList) scopedNodesViewList.get(i); + // If the node object is scoped to this view, then .. + int vsize = vl.size(); + + if (node instanceof SoundRetained) { + ((SoundRetained)node).isViewScoped = false; + for (int k = 0; k < vsize; k++) { + View view = (View) vl.get(k); + deleteScopedSound((SoundRetained) node, view); + } + } + else if (node instanceof SoundscapeRetained) { + ((SoundscapeRetained)node).isViewScoped = false; + for (int k = 0; k < vsize; k++) { + View view = (View) vl.get(k); + deleteScopedSoundscape((SoundscapeRetained) node, view); + } + } + } + } + } + + void deleteNonScopedSound(SoundRetained sound) { + if (!nonViewScopedSounds.isEmpty()) { + // find sound in list and remove it + int index = nonViewScopedSounds.indexOf(sound); + nonViewScopedSounds.remove(index); + } + } + + void deleteNonScopedSoundscape(SoundscapeRetained soundscape) { + boolean error = nonViewScopedSoundscapes.remove(soundscape); + } + + void deleteScopedSound(SoundRetained sound, View view) { + ArrayList l = (ArrayList) viewScopedSounds.get(view); + if (!l.isEmpty()) { + // find sound in list and remove it + int index = l.indexOf(sound); + l.remove(index); + } + if (l.isEmpty()) + viewScopedSounds.remove(view); + } + + void deleteScopedSoundscape(SoundscapeRetained soundscape, View view) { + ArrayList l = (ArrayList) viewScopedSoundscapes.get(view); + if (!l.isEmpty()) { + // find sound in list and remove it + int index = l.indexOf(soundscape); + l.remove(index); + } + if (l.isEmpty()) + viewScopedSoundscapes.remove(view); + + } + + void changeNodeAttrib(J3dMessage m) { + int attribDirty; + Object node = m.args[0]; + Object value = m.args[1]; + if (debugFlag) + debugPrint("SoundStructure.changeNodeAttrib:"); + + if (node instanceof SoundRetained) { + attribDirty = ((Integer)value).intValue(); + if (debugFlag) + debugPrint(" Sound node dirty bit = " + attribDirty); + if ((attribDirty & SoundRetained.PRIORITY_DIRTY_BIT) > 0) { + // TODO: shuffle in SoundScheduler + /* + shuffleSound((SoundRetained) node); + */ + } + if ((attribDirty & SoundRetained.SOUND_DATA_DIRTY_BIT) > 0) { + loadSound((SoundRetained) node, true); + } + ((SoundRetained)node).updateMirrorObject(m.args); + } + if (node instanceof SoundscapeRetained) { +/* + attribDirty = ((Integer)value).intValue(); + if (((attribDirty & SoundscapeRetained.BOUNDING_LEAF_CHANGED) != 0) || + ((attribDirty & SoundscapeRetained.APPLICATION_BOUNDS_CHANGED) != 0) ) { +*/ + ((SoundscapeRetained)node).updateTransformChange(); +/* + } +*/ +// TODO: have no dirty flag for soundscape, just auralAttributes... +// what if reference to AA changes in soundscape??? + } + + } + + + void changeNodeState(J3dMessage m) { + int stateDirty; + Object node = m.args[0]; + Object value = m.args[1]; + if (debugFlag) + debugPrint("SoundStructure.changeNodeState:"); + if (node instanceof SoundRetained) { + stateDirty = ((Integer)value).intValue(); + if (debugFlag) + debugPrint(" Sound node dirty bit = " + stateDirty); + if ((stateDirty & SoundRetained.LIVE_DIRTY_BIT) > 0) { + loadSound((SoundRetained) node, false); + } + if ((stateDirty & SoundRetained.ENABLE_DIRTY_BIT) > 0) { + enableSound((SoundRetained) node); + } + ((SoundRetained)node).updateMirrorObject(m.args); + } + } + + // return true if one of ViewPlatforms intersect region + boolean intersect(Bounds region) { + if (region == null) + return false; + + ViewPlatformRetained vpLists[] = (ViewPlatformRetained []) + viewPlatforms.toArray(false); + + for (int i=viewPlatforms.arraySize()- 1; i>=0; i--) { + vpLists[i].schedSphere.getWithLock(tempSphere); + if (tempSphere.intersect(region)) { + return true; + } + } + return false; + } + + void loadSound(SoundRetained sound, boolean forceLoad) { +// QUESTION: should not be calling into soundScheduler directly??? + MediaContainer mediaContainer = sound.getSoundData(); + ViewPlatformRetained vpLists[] = (ViewPlatformRetained []) + viewPlatforms.toArray(false); + + for (int i=viewPlatforms.arraySize()- 1; i>=0; i--) { + View[] views = vpLists[i].getViewList(); + for (int j=(views.length-1); j>=0; j--) { + View v = (View)(views[j]); +// TODO: Shouldn't this be done with messages?? + v.soundScheduler.loadSound(sound, forceLoad); + } + } + } + + void enableSound(SoundRetained sound) { + ViewPlatformRetained vpLists[] = (ViewPlatformRetained []) + viewPlatforms.toArray(false); + for (int i=viewPlatforms.arraySize()- 1; i>=0; i--) { + View[] views = vpLists[i].getViewList(); + for (int j=(views.length-1); j>=0; j--) { + View v = (View)(views[j]); + v.soundScheduler.enableSound(sound); + } + } + } + + void muteSound(SoundRetained sound) { + ViewPlatformRetained vpLists[] = (ViewPlatformRetained []) + viewPlatforms.toArray(false); + for (int i=viewPlatforms.arraySize()- 1; i>=0; i--) { + View[] views = vpLists[i].getViewList(); + for (int j=(views.length-1); j>=0; j--) { + View v = (View)(views[j]); + v.soundScheduler.muteSound(sound); + } + } + } + + void pauseSound(SoundRetained sound) { + ViewPlatformRetained vpLists[] = (ViewPlatformRetained []) + viewPlatforms.toArray(false); + for (int i=viewPlatforms.arraySize()- 1; i>=0; i--) { + View[] views = vpLists[i].getViewList(); + for (int j=(views.length-1); j>=0; j--) { + View v = (View)(views[j]); + v.soundScheduler.pauseSound(sound); + } + } + } + + void processSwitchChanged(J3dMessage m) { + SoundRetained sound; + LeafRetained leaf; + UnorderList arrList; + int size; + Object[] nodes; + + UpdateTargets targets = (UpdateTargets)m.args[0]; + arrList = targets.targetList[Targets.SND_TARGETS]; + + if (arrList != null) { + size = arrList.size(); + nodes = arrList.toArray(false); + + for (int i=size-1; i>=0; i--) { + leaf = (LeafRetained)nodes[i]; + sound = (SoundRetained) leaf; + if (sound.switchState.currentSwitchOn) { + // System.out.println("SoundStructure.switch on"); + // add To Schedule List + } else { + // System.out.println("SoundStructure.switch off"); + // remove From Schedule List + } + } + } + } + +// How can active flag (based on View orientataion) be set here for all Views?!? + + UnorderList getSoundList(View view) { + ArrayList l = (ArrayList)viewScopedSounds.get(view); + // No sounds scoped to this view + if (l == null) + return nonViewScopedSounds; + UnorderList newS = (UnorderList) nonViewScopedSounds.clone(); + int size = l.size(); + for (int i = 0; i < size; i++) { + newS.add(l.get(i)); + } + return newS; + + } + UnorderList getSoundscapeList(View view) { + ArrayList l = (ArrayList)viewScopedSoundscapes.get(view); + // No sounds scoped to this view + if (l == null) + return nonViewScopedSoundscapes; + UnorderList newS = (UnorderList) nonViewScopedSoundscapes.clone(); + int size = l.size(); + for (int i = 0; i < size; i++) { + newS.add(l.get(i)); + } + return newS; + + } + + +/* +// TODO: how is immediate mode handled? below code taken from SoundSchedule +// Don't know how we'll process immediate mode sounds; +// Append immediate mode sounds to live sounds list + if (graphicsCtx != null) { + synchronized (graphicsCtx.sounds) { + nImmedSounds = graphicsCtx.numSounds(); + numSoundsToProcess = nSounds + nImmedSounds; + if (sounds.length < numSoundsToProcess) { + // increase the array length of sounds array list + // by added 32 elements more than universe list size + sounds = new SoundRetained[numSoundsToProcess + 32]; + } + for (int i=0; i<nImmedSounds; i++) { + sound = (SoundRetained)((graphicsCtx.getSound(i)).retained); if (debugFlag) { + debugPrint("#=#=#= sound at " + sound); + printSoundState(sound); + } + + if (sound != null && sound.getInImmCtx()) { + // There is no 'mirror' copy of Immediate mode sounds made. + // Put a reference to sound node itself in .sgSound field. + // For most purposes (except transforms & transformed fields) + // Scheduler code will treat live scenegraph sounds and + // immediate mode sound the same way. + sound.sgSound = sound; + sounds[nSounds] = sound; + if (debugFlag) { + debugPrint("#=#=#= sounds["+nSounds+"] at " + + sounds[nSounds]); + printSoundState(sounds[nSounds]); + } + nSounds++; + } + } + } // sync of GraphicsContext3D.sounds list + } + else { // graphics context not set yet, try setting it now + Canvas3D canvas = view.getFirstCanvas(); + if (canvas != null) + graphicsCtx = canvas.getGraphicsContext3D(); + } + + if (debugFlag) { + debugPrint("SoundStructure: number of sounds in scene graph = "+nRetainedSounds); + debugPrint("SoundStructure: number of immediate mode sounds = "+nImmedSounds); + } +*/ + + + void updateTransformChange(UpdateTargets targets, long referenceTime) { + // QUESTION: how often and when should xformChangeList be processed + // node.updateTransformChange() called immediately rather than + // waiting for updateObject to be called and process xformChangeList + // which apprears to only happen when sound started... + + UnorderList arrList = targets.targetList[Targets.SND_TARGETS]; + if (arrList != null) { + int j,i; + Object nodes[], nodesArr[]; + int size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (j = 0; j<size; j++) { + nodes = (Object[])nodesArr[j]; + + for (i = 0; i < nodes.length; i++) { + + if (nodes[i] instanceof ConeSoundRetained) { + xformChangeList.add(nodes[i]); + ConeSoundRetained cnSndNode = + (ConeSoundRetained)nodes[i]; + cnSndNode.updateTransformChange(); + + } else if (nodes[i] instanceof PointSoundRetained) { + xformChangeList.add(nodes[i]); + PointSoundRetained ptSndNode = + (PointSoundRetained)nodes[i]; + ptSndNode.updateTransformChange(); + + } else if (nodes[i] instanceof SoundRetained) { + xformChangeList.add(nodes[i]); + SoundRetained sndNode = (SoundRetained)nodes[i]; + sndNode.updateTransformChange(); + + } else if (nodes[i] instanceof SoundscapeRetained) { + xformChangeList.add(nodes[i]); + SoundscapeRetained sndScapeNode = + (SoundscapeRetained)nodes[i]; + sndScapeNode.updateTransformChange(); + + } else if (nodes[i] instanceof AuralAttributesRetained) { + xformChangeList.add(nodes[i]); + } + } + } + } + } + + // Debug print mechanism for Sound nodes + static final boolean debugFlag = false; + static final boolean internalErrors = false; + + void debugPrint(String message) { + if (debugFlag) { + System.out.println(message); + } + } + + + boolean isSoundScopedToView(Object obj, View view) { + SoundRetained s = (SoundRetained)obj; + if (s.isViewScoped) { + ArrayList l = (ArrayList)viewScopedSounds.get(view); + if (!l.contains(s)) + return false; + } + return true; + } + + boolean isSoundscapeScopedToView(Object obj, View view) { + SoundscapeRetained s = (SoundscapeRetained)obj; + if (s.isViewScoped) { + ArrayList l = (ArrayList)viewScopedSoundscapes.get(view); + if (!l.contains(s)) + return false; + } + return true; + } + + void updateViewSpecificGroupChanged(J3dMessage m) { + int component = ((Integer)m.args[0]).intValue(); + Object[] objAry = (Object[])m.args[1]; + + ArrayList soundList = null; + ArrayList soundsScapeList = null; + + if (((component & ViewSpecificGroupRetained.ADD_VIEW) != 0) || + ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { + int i; + Object obj; + View view = (View)objAry[0]; + ArrayList leafList = (ArrayList)objAry[2]; + int size = leafList.size(); + // Leaves is non-null only for the top VSG + if (size > 0) { + // Now process the list of affected leaved + for( i = 0; i < size; i++) { + obj = leafList.get(i); + if (obj instanceof SoundRetained) { + if (soundList == null) { + if ((soundList = (ArrayList)viewScopedSounds.get(view)) == null) { + soundList = new ArrayList(); + viewScopedSounds.put(view, soundList); + } + } + soundList.add(obj); + } + else if (obj instanceof SoundscapeRetained) { + if (soundsScapeList == null) { + if ((soundsScapeList = (ArrayList)viewScopedSoundscapes.get(view)) == null) { + soundsScapeList = new ArrayList(); + viewScopedSoundscapes.put(view, soundsScapeList); + } + } + soundsScapeList.add(obj); + } + } + } + } + if (((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0)|| + ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) { + int i; + Object obj; + ArrayList leafList; + View view; + + + if ((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0) { + view = (View)objAry[0]; + leafList = (ArrayList)objAry[2]; + } + else { + view = (View)objAry[4]; + leafList = (ArrayList)objAry[6]; + } + int size = leafList.size(); + // Leaves is non-null only for the top VSG + if (size > 0) { + // Now process the list of affected leaved + for( i = 0; i < size; i++) { + obj = leafList.get(i); + if (obj instanceof SoundRetained) { + if (soundList == null) { + soundList = (ArrayList)viewScopedSounds.get(view); + } + soundList.remove(obj); + } + if (obj instanceof SoundscapeRetained) { + if (soundsScapeList == null) { + soundsScapeList = (ArrayList)viewScopedSoundscapes.get(view); + } + soundsScapeList.remove(obj); + } + } + // If there are no more lights scoped to the view, + // remove the mapping + if (soundList != null && soundList.size() == 0) + viewScopedSounds.remove(view); + if (soundsScapeList != null && soundsScapeList.size() == 0) + viewScopedSoundscapes.remove(view); + } + + } + + } + + void cleanup() {} +} diff --git a/src/classes/share/javax/media/j3d/Soundscape.java b/src/classes/share/javax/media/j3d/Soundscape.java new file mode 100644 index 0000000..ce81e74 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Soundscape.java @@ -0,0 +1,332 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The Soundscape Leaf Node defines the attributes that characterize the + * listener's environment as it pertains to sound. This node defines an + * application region and an associated aural attribute component object + * that controls reverberation and atmospheric properties that affect sound + * source rendering. Multiple Soundscape nodes can be included in a single + * scene graph. + * <P> + * The Soundscape application region, different from a Sound node's scheduling + * region, is used to select which Soundscape (and thus which aural attribute + * object) is to be applied to the sounds being rendered. This selection is + * based on the position of the ViewPlatform (i.e., the listener), not the + * position of the sound. + * <P> + * It will be common that multiple Soundscape regions are contained within a + * scene graph. For example, two Soundscape regions within a single space the + * listener can move about: a region with a large open area on the right, and + * a smaller more constricted, less reverberant area on the left. The rever- + * beration attributes for these two regions could be set to approximate their + * physical differences so that active sounds are rendered differently depending + * on which region the listener is in. + */ +public class Soundscape extends Leaf { + + // Constants + // + // These flags, when enabled using the setCapability method, allow an + // application to invoke methods that respectively read and write the + // application region and the aural attributes. These capability flags + // are enforced only when the node is part of a live or compiled scene + // graph. + + /** + * For Soundscape component objects, specifies that this object + * allows read access to its application bounds + */ + public static final int + ALLOW_APPLICATION_BOUNDS_READ = CapabilityBits.SOUNDSCAPE_ALLOW_APPLICATION_BOUNDS_READ; + + /** + * For Soundscape component objects, specifies that this object + * allows write access to its application bounds + */ + public static final int + ALLOW_APPLICATION_BOUNDS_WRITE = CapabilityBits.SOUNDSCAPE_ALLOW_APPLICATION_BOUNDS_WRITE; + + /** + * For Soundscape component objects, specifies that this object + * allows the reading of it's aural attributes information + */ + public static final int + ALLOW_ATTRIBUTES_READ = CapabilityBits.SOUNDSCAPE_ALLOW_ATTRIBUTES_READ; + + /** + * For Soundscape component objects, specifies that this object + * allows the writing of it's aural attribute information + */ + public static final int + ALLOW_ATTRIBUTES_WRITE = CapabilityBits.SOUNDSCAPE_ALLOW_ATTRIBUTES_WRITE; + + /** + * Constructs and initializes a new Sound node using following + * defaults: + *<UL> application region: null (no active region)</UL> + *<UL> aural attributes: null (uses default aural attributes)</UL> + */ + public Soundscape() { + // Just use default values + } + + /** + * Constructs and initializes a new Sound node using specified + * parameters + * @param region application region + * @param attributes array of aural attribute component objects + */ + public Soundscape(Bounds region, + AuralAttributes attributes) { + ((SoundscapeRetained)this.retained).setApplicationBounds(region); + ((SoundscapeRetained)this.retained).setAuralAttributes(attributes); + } + + /** + * Creates the retained mode SoundscapeRetained object that this + * component object will point to. + */ + void createRetained() { + this.retained = new SoundscapeRetained(); + this.retained.setSource(this); + } + + /** + * Set the Soundscape's application region to the specified bounds + * specified in local coordinates of this leaf node. The aural + * attributes associated with this Soundscape are used to render + * the active sounds when this application region intersects the + * ViewPlatform's activation volume. The getApplicationBounds method + * returns a new Bounds object. + * This region is used when the application bounding leaf is null. + * @param region the bounds that contains the Soundscape's new application + * region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setApplicationBounds(Bounds region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Soundscape0")); + + ((SoundscapeRetained)this.retained).setApplicationBounds(region); + } + + /** + * Retrieves the Soundscape node's application bounds. + * @return this Soundscape's application bounds information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Bounds getApplicationBounds() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Soundscape1")); + + return ((SoundscapeRetained)this.retained).getApplicationBounds(); + } + + /** + * Set the Soundscape's application region to the specified bounding leaf. + * When set to a value other than null, this overrides the application + * bounds object. + * @param region the bounding leaf node used to specify the Soundscape + * node's new application region. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setApplicationBoundingLeaf(BoundingLeaf region) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Soundscape0")); + + ((SoundscapeRetained)this.retained).setApplicationBoundingLeaf(region); + } + + /** + * Retrieves the Soundscape node's application bounding leaf. + * @return this Soundscape's application bounding leaf information + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BoundingLeaf getApplicationBoundingLeaf() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_APPLICATION_BOUNDS_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Soundscape1")); + + return ((SoundscapeRetained)this.retained).getApplicationBoundingLeaf(); + } + + /** + * Set a set of aural attributes for this Soundscape + * @param attributes aural attributes + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setAuralAttributes(AuralAttributes attributes) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ATTRIBUTES_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Soundscape4")); + + ((SoundscapeRetained)this.retained).setAuralAttributes(attributes); + } + + /** + * Retrieve reference of Aural Attributes + * @return reference to aural attributes + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public AuralAttributes getAuralAttributes() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ATTRIBUTES_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Soundscape5")); + + return ((SoundscapeRetained)this.retained).getAuralAttributes(); + } + + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Soundscape s = new Soundscape(); + s.duplicateNode(this, forceDuplicate); + return s; + } + + /** + * Copies all node information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method. + * <P> + * For any <code>NodeComponent</code> objects + * contained by the object being duplicated, each <code>NodeComponent</code> + * object's <code>duplicateOnCloneTree</code> value is used to determine + * whether the <code>NodeComponent</code> should be duplicated in the new node + * or if just a reference to the current node should be placed in the + * new node. This flag can be overridden by setting the + * <code>forceDuplicate</code> parameter in the <code>cloneTree</code> + * method to <code>true</code>. + * <br> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @exception ClassCastException if originalNode is not an instance of + * <code>Soundscape</code> + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public void duplicateNode(Node originalNode, boolean forceDuplicate) { + checkDuplicateNode(originalNode, forceDuplicate); + } + + /** + * Copies all Soundscape information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + SoundscapeRetained attr = (SoundscapeRetained) originalNode.retained; + SoundscapeRetained rt = (SoundscapeRetained) retained; + + rt.setApplicationBounds(attr.getApplicationBounds()); + + rt.setAuralAttributes((AuralAttributes) getNodeComponent( + attr.getAuralAttributes(), + forceDuplicate, + originalNode.nodeHashtable)); + + // the following reference will set correctly in updateNodeReferences + rt.setApplicationBoundingLeaf(attr.getApplicationBoundingLeaf()); + } + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + + SoundscapeRetained rt = (SoundscapeRetained) retained; + + BoundingLeaf bl = rt.getApplicationBoundingLeaf(); + + if (bl != null) { + Object o = referenceTable.getNewObjectReference(bl); + rt.setApplicationBoundingLeaf((BoundingLeaf) o); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/SoundscapeRetained.java b/src/classes/share/javax/media/j3d/SoundscapeRetained.java new file mode 100644 index 0000000..026bbbe --- /dev/null +++ b/src/classes/share/javax/media/j3d/SoundscapeRetained.java @@ -0,0 +1,440 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.ArrayList; + +/** + * The SoundscapeRetained object defines all soundscape rendering state + * as a subclass of a Leaf node. + */ +class SoundscapeRetained extends LeafRetained +{ + static final int ATTRIBUTES_CHANGED = 0x00001; + static final int BOUNDING_LEAF_CHANGED = 0x00002; + static final int APPLICATION_BOUNDS_CHANGED = 0x00004; + + /** + * Soundscape nodes application region + */ + Bounds applicationRegion = null; + + /** + * The bounding leaf reference + */ + BoundingLeafRetained boundingLeaf = null; + + /** + * The transformed Application Region + */ + Bounds transformedRegion = null; + + /** + * Aural attributes associated with this Soundscape + */ + AuralAttributesRetained attributes = null; + + // A bitmask that indicates that the something has changed. + int isDirty = 0xffff; + + // Target threads to be notified when sound changes + int targetThreads = J3dThread.UPDATE_SOUND | + J3dThread.SOUND_SCHEDULER; + + + // Is true, if the mirror light is viewScoped + boolean isViewScoped = false; + + void dispatchMessage(int dirtyBit, Object argument) { + // Send message including a integer argument + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.type = J3dMessage.SOUNDSCAPE_CHANGED; + createMessage.universe = universe; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(dirtyBit); + createMessage.args[2] = new Integer(0); + createMessage.args[3] = null; + createMessage.args[4] = argument; + VirtualUniverse.mc.processMessage(createMessage); + } + + + SoundscapeRetained() { + super(); + this.nodeType = NodeRetained.SOUNDSCAPE; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + } + + + /** + * Set the Soundscape's application region. + * @param region a region that contains the Soundscape's new application region + */ + void setApplicationBounds(Bounds region) + { + if (region != null) { + applicationRegion = (Bounds) region.clone(); + if (staticTransform != null) { + applicationRegion.transform(staticTransform.transform); + } + } + else { + applicationRegion = null; + } + updateTransformChange(); + this.isDirty |= APPLICATION_BOUNDS_CHANGED; + dispatchMessage(APPLICATION_BOUNDS_CHANGED, region); + + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Get the Soundscape's application region. + * @return this Soundscape's application region information + */ + Bounds getApplicationBounds() + { + Bounds b = null; + + if (this.applicationRegion == null) + return (Bounds)null; + else { + b = (Bounds) applicationRegion.clone(); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + b.transform(invTransform); + } + return b; + } + } + + /** + * Set the Soundscape's application region to the specified Leaf node. + */ + void setApplicationBoundingLeaf(BoundingLeaf region) { + if (boundingLeaf != null) { + // Remove the soundscape as users of the original bounding leaf + boundingLeaf.mirrorBoundingLeaf.removeUser(this); + } + if (region != null) { + boundingLeaf = (BoundingLeafRetained)region.retained; + boundingLeaf.mirrorBoundingLeaf.addUser(this); + } else { + boundingLeaf = null; + } + updateTransformChange(); + this.isDirty |= BOUNDING_LEAF_CHANGED; + dispatchMessage(BOUNDING_LEAF_CHANGED, region); +// QUESTION needed?? + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Get the Soundscape's application region + */ + BoundingLeaf getApplicationBoundingLeaf() { + if (boundingLeaf != null) { + return (BoundingLeaf)boundingLeaf.source; + } else { + return (BoundingLeaf)null; + } + } + + /** + * Set a set of aural attributes for this Soundscape + * @param attributes aural attributes to be set + */ + void setAuralAttributes(AuralAttributes attributes) + { + if (this.source.isLive()) { + if (this.attributes != null) { + this.attributes.clearLive(refCount); + } + + if (attributes != null) { + ((AuralAttributesRetained)attributes.retained).setLive(inBackgroundGroup, refCount); + } + } + + if (this.attributes != null) { + this.attributes.removeUser(this); + } + + if (attributes != null) { + this.attributes = (AuralAttributesRetained)attributes.retained; + this.attributes.addUser(this); + } else { + this.attributes = null; + } + + // copy all fields out of attributes and put into our copy of attributes + this.isDirty |= ATTRIBUTES_CHANGED; + dispatchMessage(ATTRIBUTES_CHANGED, attributes); + if (source != null && source.isLive()) { + notifySceneGraphChanged(false); + } + } + + /** + * Retrieve a reference to Aural Attributes + * @return attributes aural attributes to be returned + */ + AuralAttributes getAuralAttributes() + { + if (attributes != null) { + return ((AuralAttributes) attributes.source); + } + else + return ((AuralAttributes) null); + } + +/* +// NOTE: OLD CODE + // The update Object function. + public synchronized void updateObject() { + if ((attributes != null) && (attributes.aaDirty)) { + if (attributes.mirrorAa == null) { + attributes.mirrorAa = new AuralAttributesRetained(); + } + attributes.mirrorAa.update(attributes); + } + } +*/ + + // The update Object function. + synchronized void updateMirrorObject(Object[] objs) { + // NOTE: There doesn't seem to be a use for mirror objects since + // Soundscapes can't be shared. + // This method updates the transformed region from either bounding + // leaf or application bounds. Bounding leaf takes precidence. + Transform3D trans = null; + int component = ((Integer)objs[1]).intValue(); + if ((component & BOUNDING_LEAF_CHANGED) != 0) { + if (this.boundingLeaf != null) { + transformedRegion = boundingLeaf.transformedRegion; + } + else { // evaluate Application Region if not null + if (applicationRegion != null) { + transformedRegion = (Bounds)applicationRegion.clone(); + transformedRegion.transform(applicationRegion, + getLastLocalToVworld()); + } + else { + transformedRegion = null; + } + } + } + else if ((component & APPLICATION_BOUNDS_CHANGED) != 0) { + // application bounds only used when bounding leaf null + if (boundingLeaf == null) { + transformedRegion = (Bounds)applicationRegion.clone(); + transformedRegion.transform(applicationRegion, + getLastLocalToVworld()); + } + else { + transformedRegion = null; + } + } + } + + // The update tranform fields + synchronized void updateTransformChange() { + if (boundingLeaf != null) { + transformedRegion = boundingLeaf.transformedRegion; + } + else { // evaluate Application Region if not null + if (applicationRegion != null) { + transformedRegion = applicationRegion.copy(transformedRegion); + transformedRegion.transform(applicationRegion, + getLastLocalToVworld()); + } + else { + transformedRegion = null; + } + } + } + + void updateBoundingLeaf(long refTime) { + // This is necessary, if for example, the region + // changes from sphere to box. + if (boundingLeaf != null && boundingLeaf.switchState.currentSwitchOn) { + transformedRegion = boundingLeaf.transformedRegion; + } else { // evaluate Application Region if not null + if (applicationRegion != null) { + transformedRegion = applicationRegion.copy(transformedRegion); + transformedRegion.transform(applicationRegion, + getLastLocalToVworld()); + } else { + transformedRegion = null; + } + } + } + +// QUESTION: not needed? +/* + synchronized void initMirrorObject(SoundscapeRetained ms) { + GroupRetained group; + Transform3D trans; + Bounds region = null; + + if (ms == null) + return; +} + ms.isDirty = isDirty; + ms.setApplicationBounds(getApplicationBounds()); + ms.setApplicationBoundingLeaf(getApplicationBoundingLeaf()); + ms.setAuralAttributes(getAuralAttributes()); + +// QUESTION: no lineage of mirror node kept?? + ms.sgSound = sgSound; + ms.key = null; + ms.mirrorSounds = new SoundscapeRetained[1]; + ms.numMirrorSounds = 0; + ms.parent = parent; + ms.transformedRegion = null; + if (boundingLeaf != null) { + if (ms.boundingLeaf != null) + ms.boundingLeaf.removeUser(ms); + ms.boundingLeaf = boundingLeaf.mirrorBoundingLeaf; + // Add this mirror object as user + ms.boundingLeaf.addUser(ms); + ms.transformedRegion = ms.boundingLeaf.transformedRegion; + } + else { + ms.boundingLeaf = null; + } + + if (applicationRegion != null) { + ms.applicationRegion = (Bounds) applicationRegion.clone(); + // Assign region only if bounding leaf is null + if (ms.transformedRegion == null) { + ms.transformedRegion = (Bounds) ms.applicationRegion.clone(); + ms.transformedRegion.transform(ms.applicationRegion, + ms.getLastLocalToVworld()); + } + + } + else { + ms.applicationRegion = null; + } + } +*/ + + /** + * This setLive routine first calls the superclass's method, then + * it adds itself to the list of soundscapes + */ + void setLive(SetLiveState s) { + super.doSetLive(s); + + if (attributes != null) { + attributes.setLive(inBackgroundGroup, s.refCount); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.SND_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + // If its view Scoped, then add this list + // to be sent to Sound Structure + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(this); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(this); + } + + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("SoundscapeRetained1")); + } + + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("SoundscapeRetained0")); + } + + // process switch leaf + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.SND_TARGETS); + } + switchState = (SwitchState)s.switchStates.get(0); + s.notifyThreads |= (J3dThread.UPDATE_SOUND | + J3dThread.SOUND_SCHEDULER); + + super.markAsLive(); + } + + /** + * This clearLive routine first calls the superclass's method, then + * it removes itself to the list of lights + */ + void clearLive(SetLiveState s) { + super.clearLive(s); + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.SND_TARGETS); + } + + if (attributes != null) { + attributes.clearLive(s.refCount); + } + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.SND_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + // If its view Scoped, then add this list + // to be sent to Sound Structure + if ((s.viewScopedNodeList != null) && (s.viewLists != null)) { + s.viewScopedNodeList.add(this); + s.scopedNodesViewList.add(s.viewLists.get(0)); + } else { + s.nodeList.add(this); + } + s.notifyThreads |= (J3dThread.UPDATE_SOUND | + J3dThread.SOUND_SCHEDULER); + } + + // Simply pass along to the NodeComponents + /* + void compile(CompileState compState) { + setCompiled(); + + if (attributes != null) + attributes.compile(compState); + } + */ + + // This makes this sound look just like the one passed in + void update(SoundscapeRetained ss) { + applicationRegion = (Bounds)ss.applicationRegion.clone(); + attributes = ss.attributes; + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + if (applicationRegion != null) { + applicationRegion.transform(xform.transform); + } + } + + void getMirrorObjects(ArrayList leafList, HashKey key) { + leafList.add(this); + } +} diff --git a/src/classes/share/javax/media/j3d/SpotLight.java b/src/classes/share/javax/media/j3d/SpotLight.java new file mode 100644 index 0000000..dbb687c --- /dev/null +++ b/src/classes/share/javax/media/j3d/SpotLight.java @@ -0,0 +1,341 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color3f; +import javax.vecmath.Point3f; +import javax.vecmath.Vector3f; + +/** + * The SpotLight object specifies an attenuated light source at a + * fixed point in space that radiates light in a specified direction + * from the light source. A SpotLight has the same attributes as a + * PointLight node, with the addition of the following:<P> + * <UL> + * <LI>Direction - The axis of the cone of light. The default + * direction is (0.0, 0.0, -1.0). The spot light direction is + * significant only when the spread angle is not PI radians + * (which it is by default).</LI> + * <P> + * <LI>Spread angle - The angle in radians between the direction axis + * and a ray along the edge of the cone. Note that the angle of the + * cone at the apex is then twice this value. The range of values + * is [0.0,PI/2] radians, with a special value of PI radians. Values + * lower than 0 are clamped to 0 and values over PI/2 are clamped + * to PI. The default spread angle is PI radians. </LI> + * <P> + * <LI>Concentration - Specifies how quickly the light intensity + * attenuates as a function of the angle of radiation as measured from + * the direction of radiation. The light's intensity is highest at the + * center of the cone and is attenuated toward the edges of the cone + * by the cosine of the angle between the direction of the light + * and the direction from the light to the object being lit, raised + * to the power of the spot concentration exponent. + * The higher the concentration value, the more focused the light + * source. The range of values is [0.0,128.0]. The default + * concentration is 0.0, which provides uniform light + * distribution.</LI><P> + * </UL> + * A spot light contributes to diffuse and specular reflections, which + * depend on the orientation and position of an object's surface. + * A spot light does not contribute to ambient reflections. + */ + +public class SpotLight extends PointLight { + /** + * Specifies that the Node allows writing to its spot lights spread angle + * information. + */ + public static final int + ALLOW_SPREAD_ANGLE_WRITE = CapabilityBits.SPOT_LIGHT_ALLOW_SPREAD_ANGLE_WRITE; + + /** + * Specifies that the Node allows reading its spot lights spread angle + * information. + */ + public static final int + ALLOW_SPREAD_ANGLE_READ = CapabilityBits.SPOT_LIGHT_ALLOW_SPREAD_ANGLE_READ; + + /** + * Specifies that the Node allows writing to its spot lights concentration + * information. + */ + public static final int + ALLOW_CONCENTRATION_WRITE = CapabilityBits.SPOT_LIGHT_ALLOW_CONCENTRATION_WRITE; + + /** + * Specifies that the Node allows reading its spot lights concentration + * information. + */ + public static final int + ALLOW_CONCENTRATION_READ = CapabilityBits.SPOT_LIGHT_ALLOW_CONCENTRATION_READ; + + /** + * Specifies that the Node allows writing to its spot lights direction + * information. + */ + public static final int + ALLOW_DIRECTION_WRITE = CapabilityBits.SPOT_LIGHT_ALLOW_DIRECTION_WRITE; + + /** + * Specifies that the Node allows reading its spot lights direction + * information. + */ + public static final int + ALLOW_DIRECTION_READ = CapabilityBits.SPOT_LIGHT_ALLOW_DIRECTION_READ; + + /** + * Constructs a SpotLight node with default parameters. + * The default values are as follows: + * <ul> + * direction : (0,0,-1)<br> + * spread angle : <i>PI</i> radians<br> + * concentration : 0.0<br> + * </ul> + */ + public SpotLight() { + } + + /** + * Constructs and initializes a SpotLight node using the + * specified parameters. + * @param color the color of the light source + * @param position the position of the light in three-space + * @param attenuation the attenuation (constant, linear, quadratic) + * of the light + * @param direction the direction of the light + * @param spreadAngle the spread angle of the light + * @param concentration the concentration of the light + */ + public SpotLight(Color3f color, + Point3f position, + Point3f attenuation, + Vector3f direction, + float spreadAngle, + float concentration) { + super(color, position, attenuation); + ((SpotLightRetained)this.retained).initDirection(direction); + ((SpotLightRetained)this.retained).initSpreadAngle(spreadAngle); + ((SpotLightRetained)this.retained).initConcentration(concentration); + } + + /** + * Constructs and initializes a SpotLight node using the + * specified parameters. + * @param lightOn flag indicating whether this light is on or off + * @param color the color of the light source + * @param position the position of the light in three-space + * @param attenuation the attenuation (constant, linear, quadratic) of the light + * @param direction the direction of the light + * @param spreadAngle the spread angle of the light + * @param concentration the concentration of the light + */ + public SpotLight(boolean lightOn, + Color3f color, + Point3f position, + Point3f attenuation, + Vector3f direction, + float spreadAngle, + float concentration) { + super(lightOn, color, position, attenuation); + ((SpotLightRetained)this.retained).initDirection(direction); + ((SpotLightRetained)this.retained).initSpreadAngle(spreadAngle); + ((SpotLightRetained)this.retained).initConcentration(concentration); + } + + /** + * Creates the retained mode SpotLightRetained object that this + * SpotLight component object will point to. + */ + void createRetained() { + this.retained = new SpotLightRetained(); + this.retained.setSource(this); + } + + + /** + * Sets spot light spread angle. + * @param spreadAngle the new spread angle for spot light + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph. + */ + public void setSpreadAngle(float spreadAngle) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SPREAD_ANGLE_WRITE)) + throw new + CapabilityNotSetException(J3dI18N.getString("SpotLight0")); + + if (isLive()) + ((SpotLightRetained)this.retained).setSpreadAngle(spreadAngle); + else + ((SpotLightRetained)this.retained).initSpreadAngle(spreadAngle); + } + + /** + * Gets spot light spread angle. + * @return the new spread angle for spot light. The value returned + * is the clamped value. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getSpreadAngle() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SPREAD_ANGLE_READ)) + throw new + CapabilityNotSetException(J3dI18N.getString("SpotLight1")); + + return ((SpotLightRetained)this.retained).getSpreadAngle(); + } + + /** + * Sets spot light concentration. + * @param concentration the new concentration for spot light + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setConcentration(float concentration) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CONCENTRATION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("SpotLight2")); + + if (isLive()) + ((SpotLightRetained)this.retained).setConcentration(concentration); + else + ((SpotLightRetained)this.retained).initConcentration(concentration); + } + + /** + * Gets spot light concentration. + * @return the new concentration for spot light + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getConcentration() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CONCENTRATION_READ)) + throw new + CapabilityNotSetException(J3dI18N.getString("SpotLight3")); + return ((SpotLightRetained)this.retained).getConcentration(); + } + + /** + * Sets light direction. + * @param x the new X direction + * @param y the new Y direction + * @param z the new Z direction + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDirection(float x, float y, float z) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_WRITE)) + throw new + CapabilityNotSetException(J3dI18N.getString("SpotLight4")); + + if (isLive()) + ((SpotLightRetained)this.retained).setDirection(x,y,z); + else + ((SpotLightRetained)this.retained).initDirection(x,y,z); + } + + /** + * Sets this Light's current direction and places it in the parameter specified. + * @param direction the vector that will receive this node's direction + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setDirection(Vector3f direction) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_WRITE)) + throw new + CapabilityNotSetException(J3dI18N.getString("SpotLight4")); + + if (isLive()) + ((SpotLightRetained)this.retained).setDirection(direction); + else + ((SpotLightRetained)this.retained).initDirection(direction); + } + + /** + * Gets this Light's current direction and places it in the + * parameter specified. + * @param direction the vector that will receive this node's direction + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getDirection(Vector3f direction) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_DIRECTION_READ)) + throw new + CapabilityNotSetException(J3dI18N.getString("SpotLight6")); + ((SpotLightRetained)this.retained).getDirection(direction); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + SpotLight s = new SpotLight(); + s.duplicateNode(this, forceDuplicate); + return s; + } + + + /** + * Copies all SpotLight information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean + forceDuplicate) { + + super.duplicateAttributes(originalNode, forceDuplicate); + + SpotLightRetained attr = (SpotLightRetained) originalNode.retained; + SpotLightRetained rt = (SpotLightRetained) retained; + + rt.initSpreadAngle(attr.getSpreadAngle()); + rt.initConcentration(attr.getConcentration()); + Vector3f v = new Vector3f(); + attr.getDirection(v); + rt.initDirection(v); + + } +} diff --git a/src/classes/share/javax/media/j3d/SpotLightRetained.java b/src/classes/share/javax/media/j3d/SpotLightRetained.java new file mode 100644 index 0000000..8f88e63 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SpotLightRetained.java @@ -0,0 +1,341 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * A local spot light source object. + */ + +class SpotLightRetained extends PointLightRetained { + static final int DIRECTION_CHANGED = LAST_POINTLIGHT_DEFINED_BIT << 1; + static final int ANGLE_CHANGED = LAST_POINTLIGHT_DEFINED_BIT << 2; + static final int CONCENTRATION_CHANGED = LAST_POINTLIGHT_DEFINED_BIT << 3; + + /** + * The spot light's direction. + */ + Vector3f direction = new Vector3f(0.0f, 0.0f, -1.0f); + + // The transformed direction of this light + Vector3f xformDirection = new Vector3f(0.0f, 0.0f, -1.0f); + + /** + * The spot light's spread angle. + */ + float spreadAngle = (float)Math.PI; + + /** + * The spot light's concentration. + */ + float concentration = 0.0f; + + + SpotLightRetained() { + this.nodeType = NodeRetained.SPOTLIGHT; + lightType = 4; + } + + /** + * Initializes the spot light's spread angle. + * @param spreadAngle the light's spread angle + */ + void initSpreadAngle(float spreadAngle) { + if (spreadAngle < 0.0) { + this.spreadAngle = 0.0f; + } + else if (spreadAngle > (float) Math.PI * 0.5f) { + this.spreadAngle = (float)Math.PI; + } + else { + this.spreadAngle = spreadAngle; + } + } + + + void setLive(SetLiveState s) { + super.doSetLive(s); + J3dMessage createMessage = super.initMessage(12); + Object[] objs = (Object[])createMessage.args[4]; + objs[9] = new Float(spreadAngle); + objs[10] = new Float(concentration) ; + objs[11] = new Vector3f(direction); + VirtualUniverse.mc.processMessage(createMessage); + } + + /** + * Sets the spot light's spread angle. + * @param spreadAngle the light's spread angle + */ + void setSpreadAngle(float spreadAngle) { + initSpreadAngle(spreadAngle); + sendMessage(ANGLE_CHANGED, new Float(this.spreadAngle)); + } + + /** + * Returns the spot light's spread angle. + * @return the spread angle of the light + */ + float getSpreadAngle() { + return this.spreadAngle; + } + + /** + * Initializes the spot light's concentration. + * @param concentration the concentration of the light + */ + void initConcentration(float concentration) { + this.concentration = concentration; + } + + /** + * Sets the spot light's concentration. + * @param concentration the concentration of the light + */ + void setConcentration(float concentration) { + initConcentration(concentration); + sendMessage(CONCENTRATION_CHANGED, new Float(concentration)); + } + + /** + * Retrieves the spot light's concentration. + * @return the concentration of the light + */ + float getConcentration() { + return this.concentration; + } + + /** + * Initializes the spot light's direction from the vector provided. + * @param direction the new direction of the light + */ + void initDirection(Vector3f direction) { + this.direction.set(direction); + + if (staticTransform != null) { + staticTransform.transform.transform(this.direction, this.direction); + } + } + + /** + * Sets the spot light's direction from the vector provided. + * @param direction the new direction of the light + */ + void setDirection(Vector3f direction) { + initDirection(direction); + sendMessage(DIRECTION_CHANGED, new Vector3f(direction)); + } + + + /** + * Initializes this light's direction from the three values provided. + * @param x the new x direction + * @param y the new y direction + * @param z the new z direction + */ + void initDirection(float x, float y, float z) { + this.direction.x = x; + this.direction.y = y; + this.direction.z = z; + if (staticTransform != null) { + staticTransform.transform.transform(this.direction, this.direction); + } + } + + /** + * Sets this light's direction from the three values provided. + * @param x the new x direction + * @param y the new y direction + * @param z the new z direction + */ + void setDirection(float x, float y, float z) { + setDirection(new Vector3f(x, y, z)); + } + + + /** + * Retrieves this light's direction and places it in the + * vector provided. + * @param direction the variable to receive the direction vector + */ + void getDirection(Vector3f direction) { + direction.set(this.direction); + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + invTransform.transform(direction, direction); + } + } + + /** + * This update function, and its native counterpart, + * updates a spot light. This includes its color, attenuation, + * transformed position, spread angle, concentration, + * and its transformed position. + */ + native void updateLight(long ctx, + int lightSlot, float red, float green, + float blue, float ax, float ay, float az, + float px, float py, float pz, float spreadAngle, + float concentration, float dx, float dy, + float dz); + + void update(long ctx, int lightSlot, double scale) { + validateAttenuationInEc(scale); + updateLight(ctx, lightSlot, color.x, color.y, color.z, + attenuation.x, linearAttenuationInEc, + quadraticAttenuationInEc, + xformPosition.x, xformPosition.y, + xformPosition.z, spreadAngle, concentration, + xformDirection.x, xformDirection.y, + xformDirection.z); + } + + + /** + * This update function, and its native counterpart, + * updates a directional light. This includes its + * color and its transformed direction. + */ + // Note : if you add any more fields here , you need to update + // updateLight() in RenderingEnvironmentStructure + void updateMirrorObject(Object[] objs) { + + int component = ((Integer)objs[1]).intValue(); + Transform3D trans; + int i; + int numLgts = ((Integer)objs[2]).intValue(); + LightRetained[] mLgts = (LightRetained[]) objs[3]; + if ((component & DIRECTION_CHANGED) != 0) { + for (i = 0; i < numLgts; i++) { + if (mLgts[i].nodeType == NodeRetained.SPOTLIGHT) { + SpotLightRetained ml = (SpotLightRetained)mLgts[i]; + ml.direction = (Vector3f)objs[4]; + ml.getLastLocalToVworld().transform(ml.direction, + ml.xformDirection); + ml.xformDirection.normalize(); + } + } + } + else if ((component & ANGLE_CHANGED) != 0) { + for (i = 0; i < numLgts; i++) { + if (mLgts[i].nodeType == NodeRetained.SPOTLIGHT) { + SpotLightRetained ml = (SpotLightRetained)mLgts[i]; + ml.spreadAngle = ((Float)objs[4]).floatValue(); + } + } + + } + else if ((component & CONCENTRATION_CHANGED) != 0) { + + for (i = 0; i < numLgts; i++) { + if (mLgts[i].nodeType == NodeRetained.SPOTLIGHT) { + SpotLightRetained ml = (SpotLightRetained)mLgts[i]; + ml.concentration = ((Float)objs[4]).floatValue(); + } + } + } + else if ((component & INIT_MIRROR) != 0) { + for (i = 0; i < numLgts; i++) { + if (mLgts[i].nodeType == NodeRetained.SPOTLIGHT) { + SpotLightRetained ml = (SpotLightRetained)mLgts[i]; + ml.spreadAngle = ((Float)((Object[])objs[4])[9]).floatValue(); + ml.concentration = ((Float)((Object[])objs[4])[10]).floatValue(); + ml.direction = (Vector3f)((Object[])objs[4])[11]; + ml.getLastLocalToVworld().transform(ml.direction, + ml.xformDirection); + ml.xformDirection.normalize(); + } + } + } + + // call the parent's mirror object update routine + super.updateMirrorObject(objs); + } + + + /* + // This update function, and its native counterpart, + // updates a spot light. This includes its color, attenuation, + // transformed position, spread angle, concentration, + // and its transformed position. + native void updateLight(int lightSlot, float red, float green, + float blue, float ax, float ay, float az, + float px, float py, float pz, float spreadAngle, + float concentration, float dx, float dy, + float dz); + void update(int lightSlot, double scale) { + updateLight(lightSlot, color.x, color.y, color.z, + attenuation.x, linearAttenuationInEc, + quadraticAttenuationInEc, + xformPosition.x, xformPosition.y, + xformPosition.z, spreadAngle, concentration, + xformDirection.x, xformDirection.y, + xformDirection.z); + } + + synchronized void update(LightRetained l, boolean clear) { + SpotLightRetained sl = (SpotLightRetained)l; + super.update(sl, clear); + + l.sgLight.getLocalToVworld(trans, l.key); + trans.transform(direction, sl.xformDirection); + sl.xformDirection.normalize(); + trans.transform(position, sl.xformPosition); + sl.spreadAngle = spreadAngle; + sl.concentration = concentration; + } + */ + + + // Clones only the retained side, internal use only + protected Object clone() { + SpotLightRetained sr = (SpotLightRetained)super.clone(); + sr.direction = new Vector3f(direction); + sr.xformDirection = new Vector3f(); + return sr; + } + + + + // Called on the mirror object + void updateTransformChange() { + super.updateTransformChange(); + + getLastLocalToVworld().transform(direction, xformDirection); + xformDirection.normalize(); + + } + + final void sendMessage(int attrMask, Object attr) { + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = targetThreads; + createMessage.universe = universe; + createMessage.type = J3dMessage.LIGHT_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + if (inSharedGroup) + createMessage.args[2] = new Integer(numMirrorLights); + else + createMessage.args[2] = new Integer(1); + createMessage.args[3] = mirrorLights.clone(); + createMessage.args[4] = attr; + VirtualUniverse.mc.processMessage(createMessage); + } + + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + xform.transform.transform(direction, direction); + } +} diff --git a/src/classes/share/javax/media/j3d/StructureUpdateThread.java b/src/classes/share/javax/media/j3d/StructureUpdateThread.java new file mode 100644 index 0000000..30c79b4 --- /dev/null +++ b/src/classes/share/javax/media/j3d/StructureUpdateThread.java @@ -0,0 +1,85 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The StructureUpdateThread is thread that passes messages to its structure + */ + +class StructureUpdateThread extends J3dThread { + /** + * The structure that this thread works for + */ + J3dStructure structure; + + /** + * Some variables used to name threads correctly + */ + private static int numInstances[] = new int[7]; + private int instanceNum[] = new int[7]; + + private synchronized int newInstanceNum(int idx) { + return (++numInstances[idx]); + } + + int getInstanceNum(int idx) { + if (instanceNum[idx] == 0) + instanceNum[idx] = newInstanceNum(idx); + return instanceNum[idx]; + } + + /** + * Just saves the structure + */ + StructureUpdateThread(ThreadGroup t, J3dStructure s, int threadType) { + super(t); + structure = s; + type = threadType; + classification = J3dThread.UPDATE_THREAD; + + switch (type) { + case J3dThread.UPDATE_GEOMETRY: + setName("J3D-GeometryStructureUpdateThread-" + getInstanceNum(0)); + break; + case J3dThread.UPDATE_RENDER: + setName("J3D-RenderStructureUpdateThread-" + getInstanceNum(1)); + break; + case J3dThread.UPDATE_BEHAVIOR: + setName("J3D-BehaviorStructureUpdateThread-" + getInstanceNum(2)); + break; + case J3dThread.UPDATE_SOUND: + setName("J3D-SoundStructureUpdateThread-" + getInstanceNum(3)); + break; + case J3dThread.UPDATE_RENDERING_ATTRIBUTES: + // Only one exists in Java3D system + setName("J3D-RenderingAttributesStructureUpdateThread"); + break; + case J3dThread.UPDATE_RENDERING_ENVIRONMENT: + setName("J3D-RenderingEnvironmentStructureUpdateThread-"+ + getInstanceNum(4)); + break; + case J3dThread.UPDATE_TRANSFORM: + setName("J3D-TransformStructureUpdateThread-"+ getInstanceNum(5)); + break; + case J3dThread.SOUND_SCHEDULER: + setName("J3D-SoundSchedulerUpdateThread-"+ getInstanceNum(6)); + break; + + } + + } + + void doWork(long referenceTime) { + structure.processMessages(referenceTime); + } +} diff --git a/src/classes/share/javax/media/j3d/Switch.java b/src/classes/share/javax/media/j3d/Switch.java new file mode 100644 index 0000000..da80df3 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Switch.java @@ -0,0 +1,247 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.BitSet; + +/** + * The Switch node controls which of its children will be rendered. + * It defines a child selection value (a switch value) that can either + * select a single child, or it can select 0 or more children using a + * mask to indicate which children are selected for rendering. + * The Switch node contains an ordered list of children, but the + * index order of the children in the list is only used for selecting + * the appropriate child or children and does not specify rendering + * order. + */ + +public class Switch extends Group { + + /** + * Specifies that this node allows reading its child selection + * and mask values and its current child. + */ + public static final int + ALLOW_SWITCH_READ = CapabilityBits.SWITCH_ALLOW_SWITCH_READ; + + /** + * Specifies that this node allows writing its child selection + * and mask values. + */ + public static final int + ALLOW_SWITCH_WRITE = CapabilityBits.SWITCH_ALLOW_SWITCH_WRITE; + + /** + * Specifies that no children are rendered. + * This value may be used in place of a non-negative child + * selection index. + */ + public static final int CHILD_NONE = -1; + + /** + * Specifies that all children are rendered. This setting causes + * the switch node to function as an ordinary group node. + * This value may be used in place of a non-negative child + * selection index. + */ + public static final int CHILD_ALL = -2; + + /** + * Specifies that the childMask BitSet is + * used to select which children are rendered. + * This value may be used in place of a non-negative child + * selection index. + */ + public static final int CHILD_MASK = -3; + + /** + * Constructs a Switch node with default parameters. + * The default values are as follows: + * <ul> + * child selection index : CHILD_NONE<br> + * child selection mask : false (for all children)<br> + * </ul> + */ + public Switch() { + } + + /** + * Constructs and initializes a Switch node using the specified + * child selection index. + * @param whichChild the initial child selection index + */ + public Switch(int whichChild) { + ((SwitchRetained)this.retained).setWhichChild(whichChild, true); + } + + /** + * Constructs and initializes a Switch node using the specified + * child selection index and mask. + * @param whichChild the initial child selection index + * @param childMask the initial child selection mask + */ + public Switch(int whichChild, BitSet childMask){ + ((SwitchRetained)this.retained).setWhichChild(whichChild, true); + ((SwitchRetained)this.retained).setChildMask(childMask); + } + + /** + * Creates the retained mode SwitchRetained object that this + * Switch object will point to. + */ + void createRetained() { + this.retained = new SwitchRetained(); + this.retained.setSource(this); + } + + /** + * Sets the child selection index that specifies which child is rendered. + * If the value is out of range, then no children are drawn. + * @param child a non-negative integer index value, indicating a + * specific child, or one of the following constants: CHILD_NONE, + * CHILD_ALL, or CHILD_MASK. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #CHILD_NONE + * @see #CHILD_ALL + * @see #CHILD_MASK + */ + public void setWhichChild(int child) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SWITCH_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Switch0")); + + ((SwitchRetained)this.retained).setWhichChild(child, false); + } + + /** + * Retrieves the current child selection index that specifies which + * child is rendered. + * @return a non-negative integer index value, indicating a + * specific child, or one of the following constants: CHILD_NONE, + * CHILD_ALL, or CHILD_MASK + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #CHILD_NONE + * @see #CHILD_ALL + * @see #CHILD_MASK + */ + public int getWhichChild() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SWITCH_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Switch1")); + + return ((SwitchRetained)this.retained).getWhichChild(); + } + + /** + * Sets the child selection mask. This mask is used when + * the child selection index is set to CHILD_MASK. + * @param childMask a BitSet that specifies which children are rendered + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setChildMask(BitSet childMask) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SWITCH_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Switch2")); + + ((SwitchRetained)this.retained).setChildMask(childMask); + } + + /** + * Retrieves the current child selection mask. This mask is used when + * the child selection index is set to CHILD_MASK. + * @return the child selection mask + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public BitSet getChildMask() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SWITCH_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Switch3")); + + return ((SwitchRetained)this.retained).getChildMask(); + } + + /** + * Retrieves the currently selected child. If the child selection index + * is out of range or is set to CHILD_NONE, CHILD_ALL, or CHILD_MASK, + * then this method returns null. + * @return a reference to the current child chosen for rendering + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Node currentChild() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHILDREN_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Switch4")); + + return ((SwitchRetained)this.retained).currentChild(); + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + Switch s = new Switch(); + s.duplicateNode(this, forceDuplicate); + return s; + } + + /** + * Copies all Switch information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Group#cloneNode + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + SwitchRetained attr = (SwitchRetained) originalNode.retained; + SwitchRetained rt = (SwitchRetained) retained; + + rt.setChildMask(attr.getChildMask()); + rt.setWhichChild(attr.getWhichChild(), true); + } +} diff --git a/src/classes/share/javax/media/j3d/SwitchRetained.java b/src/classes/share/javax/media/j3d/SwitchRetained.java new file mode 100644 index 0000000..93e7f10 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SwitchRetained.java @@ -0,0 +1,885 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.BitSet; +import java.util.ArrayList; + +/** + * The switch node controls which one of its children will be rendered. + */ + +class SwitchRetained extends GroupRetained implements TargetsInterface +{ + static final int GEO_NODES = 0x0001; + static final int ENV_NODES = 0x0002; + static final int BEHAVIOR_NODES = 0x0004; + static final int SOUND_NODES = 0x0008; + static final int BOUNDINGLEAF_NODES = 0x0010; + + /** + * The value specifing which child to render. + */ + int whichChild = Switch.CHILD_NONE; + + /** + * The BitSet specifying which children are to be selected for + * rendering. This is used ONLY if whichChild is set to CHILD_MASK. + */ + BitSet childMask = new BitSet(); + + /** + * The childmask bitset used for rendering + */ + BitSet renderChildMask = new BitSet(); + + // A boolean indication that something changed + boolean isDirty = true; + + // switchLevel per key, used in traversing switch children + ArrayList switchLevels = new ArrayList(1); + + // key which identifies a unique path from a locale to this switch link + HashKey switchKey = new HashKey(); + + // switch index counter to identify specific children + int switchIndexCount = 0; + + // for message processing + UpdateTargets updateTargets = null; + + ArrayList childrenSwitchStates = null; + + SwitchRetained() { + this.nodeType = NodeRetained.SWITCH; + } + + /** + * Sets which child should be drawn. + * @param whichChild the child to choose during a render operation + */ + // synchronized with clearLive + synchronized void setWhichChild(int whichChild, boolean updateAlways) { + + int i, nchildren; + + this.whichChild = whichChild; + isDirty = true; + + if (source != null && source.isLive()) { + updateTargets = new UpdateTargets(); + ArrayList updateList = new ArrayList(1); + nchildren = children.size(); + switch (whichChild) { + case Switch.CHILD_ALL: + for (i=0; i<nchildren; i++) { + if (renderChildMask.get(i) == false || updateAlways) { + renderChildMask.set(i); + updateSwitchChild(i, true, updateList); + } + } + break; + case Switch.CHILD_NONE: + for (i=0; i<nchildren; i++) { + if (renderChildMask.get(i) == true || updateAlways) { + renderChildMask.clear(i); + updateSwitchChild(i, false, updateList); + } + } + break; + case Switch.CHILD_MASK: + for (i=0; i<nchildren; i++) { + if (childMask.get(i) == true) { + if (renderChildMask.get(i) == false || updateAlways) { + renderChildMask.set(i); + updateSwitchChild(i, true, updateList); + } + } else { + if (renderChildMask.get(i) == true || updateAlways) { + renderChildMask.clear(i); + updateSwitchChild(i, false, updateList); + } + } + } + break; + default: + for (i=0; i<nchildren; i++) { + if (i == whichChild) { + if (renderChildMask.get(i) == false || updateAlways) { + renderChildMask.set(i); + updateSwitchChild(i, true, updateList); + } + } else { + if (renderChildMask.get(i) == true || updateAlways) { + renderChildMask.clear(i); + updateSwitchChild(i, false, updateList); + } + } + } + break; + } + sendMessage(updateList); + } + } + + /** + * Returns the index of the current child. + * @return the default child's index + */ + int getWhichChild() { + return this.whichChild; + } + + /** + * Sets current childMask. + * @param childMask a BitSet to select the children for rendering + */ + // synchronized with clearLive + synchronized final void setChildMask(BitSet childMask) { + int i, nbits, nchildren; + + if (childMask.size() > this.childMask.size()) { + nbits = childMask.size(); + } else { + nbits = this.childMask.size(); + } + + for (i=0; i<nbits; i++) { + if (childMask.get(i)) { + this.childMask.set(i); + } else { + this.childMask.clear(i); + } + } + this.isDirty = true; + if (source != null && source.isLive() && + whichChild == Switch.CHILD_MASK) { + updateTargets = new UpdateTargets(); + ArrayList updateList = new ArrayList(1); + nchildren = children.size(); + for (i=0; i<nchildren; i++) { + if (childMask.get(i) == true) { + if (renderChildMask.get(i) == false) { + renderChildMask.set(i); + updateSwitchChild(i, true, updateList); + } + } else { + if (renderChildMask.get(i) == true) { + renderChildMask.clear(i); + updateSwitchChild(i, false, updateList); + } + } + } + sendMessage(updateList); + } + } + + void sendMessage(ArrayList updateList) { + + J3dMessage m ; + int i,j,size,threads; + Object[] nodesArr, nodes; + + threads = updateTargets.computeSwitchThreads(); + + if (threads > 0) { + + m = VirtualUniverse.mc.getMessage(); + m.type = J3dMessage.SWITCH_CHANGED; + m.universe = universe; + m.threads = threads; + m.args[0] = updateTargets; + m.args[2] = updateList; + UnorderList blnList = + updateTargets.targetList[Targets.BLN_TARGETS]; + + if (blnList != null) { + BoundingLeafRetained mbleaf; + size = blnList.size(); + + Object[] boundingLeafUsersArr = new Object[size]; + nodesArr = blnList.toArray(false); + for (j=0; j<size; j++) { + nodes = (Object[])nodesArr[j]; + Object[] boundingLeafUsers = new Object[nodes.length]; + boundingLeafUsersArr[j] = boundingLeafUsers; + for (i=0; i<nodes.length; i++) { + mbleaf = (BoundingLeafRetained) nodes[i]; + boundingLeafUsers[i] = mbleaf.users.toArray(); + } + } + m.args[1] = boundingLeafUsersArr; + } + VirtualUniverse.mc.processMessage(m); + } + + UnorderList vpList = updateTargets.targetList[Targets.VPF_TARGETS]; + if (vpList != null) { + ViewPlatformRetained vp; + size = vpList.size(); + + nodesArr = vpList.toArray(false); + for (j=0; j<size; j++) { + nodes = (Object[])nodesArr[j]; + for (i=0; i<nodes.length; i++) { + vp = (ViewPlatformRetained)nodes[i]; + vp.processSwitchChanged(); + } + } + } + } + + /** + * Returns the current childMask. + * @return the current childMask + */ + final BitSet getChildMask() { + return (BitSet)this.childMask.clone(); + } + + /** + * Returns the current child. + * @return the current child + */ + Node currentChild() { + if ((whichChild < 0) || (whichChild >= children.size())) + return null; + else + return getChild(whichChild); + } + + void updateSwitchChild(int child, boolean switchOn, ArrayList updateList) { + int i; + int switchLevel; + + if (inSharedGroup) { + for (i=0; i<localToVworldKeys.length; i++) { + switchLevel = ((Integer)switchLevels.get(i)).intValue(); + traverseSwitchChild(child, localToVworldKeys[i], i, this, + false, false, switchOn, switchLevel, updateList); + } + } else { + switchLevel = ((Integer)switchLevels.get(0)).intValue(); + traverseSwitchChild(child, null, 0, this, false, false, + switchOn, switchLevel, updateList); + } + } + + // Switch specific data at SetLive. + void setAuxData(SetLiveState s, int index, int hkIndex) { + int size; + ArrayList switchStates; + + // Group's setAuxData() + super.setAuxData(s, index, hkIndex); + switchLevels.add(new Integer(s.switchLevels[index])); + int nchildren = children.size(); + for (int i=0; i<nchildren; i++) { + switchStates = (ArrayList)childrenSwitchStates.get(i); + switchStates.add(hkIndex, new SwitchState(true)); + } + } + + void setNodeData(SetLiveState s) { + super.setNodeData(s); + // add this node to parent's childSwitchLink + if (s.childSwitchLinks != null) { + if(!inSharedGroup || + // only add if not already added in sharedGroup + !s.childSwitchLinks.contains(this)) { + s.childSwitchLinks.add(this); + } + } + // Note that s.childSwitchLinks is updated in super.setLive + s.parentSwitchLink = this; + + if (!inSharedGroup) { + setAuxData(s, 0, 0); + } else { + // For inSharedGroup case. + int j, hkIndex; + + s.hashkeyIndex = new int[s.keys.length]; + for(j=0; j<s.keys.length; j++) { + hkIndex = s.keys[j].equals(localToVworldKeys, 0, + localToVworldKeys.length); + if(hkIndex >= 0) { + setAuxData(s, j, hkIndex); + } else { + System.out.println("Can't Find matching hashKey in setNodeData."); + System.out.println("We're in TROUBLE!!!"); + } + s.hashkeyIndex[j] = hkIndex; + } + } + } + + void setLive(SetLiveState s) { + int i,j,k; + boolean switchOn; + SwitchRetained switchRoot; + int size; + + // save setLiveState + Targets[] savedSwitchTargets = s.switchTargets; + ArrayList savedSwitchStates = s.switchStates; + SwitchRetained[] savedClosestSwitchParents = s.closestSwitchParents; + int[] savedClosestSwitchIndices = s.closestSwitchIndices; + ArrayList savedChildSwitchLinks = s.childSwitchLinks; + GroupRetained savedParentSwitchLink = s.parentSwitchLink; + int[] savedHashkeyIndex = s.hashkeyIndex; + + // update setLiveState for this node + s.closestSwitchParents = (SwitchRetained[]) + savedClosestSwitchParents.clone(); + s.closestSwitchIndices = (int[])savedClosestSwitchIndices.clone(); + + // Note that s.containsNodesList is updated in super.setLive + // Note that s.closestSwitchIndices is updated in super.setLive + for (i=0; i< s.switchLevels.length; i++) { + s.switchLevels[i]++; + s.closestSwitchParents[i] = this; + } + + super.doSetLive(s); + + initRenderChildMask(); + + // update switch leaves' compositeSwitchMask + // and update switch leaves' switchOn flag if this is top level switch + if (inSharedGroup) { + for (i=0; i<s.keys.length; i++) { + j = s.hashkeyIndex[i]; + // j is index in ContainNodes + if (j < localToVworldKeys.length) { + switchRoot = (s.switchLevels[i] == 0)? this : null; + size = children.size(); + for (k=0; k<size; k++) { + switchOn = renderChildMask.get(k); + traverseSwitchChild(k, s.keys[i], j, switchRoot, true, false, + switchOn, s.switchLevels[i], null); + } + } + } + } else { + switchRoot = (s.switchLevels[0] == 0)? this : null; + size = children.size(); + for (i=0; i<size; i++) { + switchOn = renderChildMask.get(i); + traverseSwitchChild(i, null, 0, switchRoot, true, false, + switchOn, s.switchLevels[0], null); + } + } + + // restore setLiveState + s.switchTargets = savedSwitchTargets; + s.switchStates = savedSwitchStates; + s.closestSwitchParents = savedClosestSwitchParents; + s.closestSwitchIndices = savedClosestSwitchIndices; + for (i=0; i< s.switchLevels.length; i++) { + s.switchLevels[i]--; + } + s.childSwitchLinks = savedChildSwitchLinks; + s.parentSwitchLink = savedParentSwitchLink; + s.hashkeyIndex = savedHashkeyIndex; + super.markAsLive(); + } + + + void removeNodeData(SetLiveState s) { + + int numChildren = children.size(); + int i, j; + ArrayList switchStates; + + if (refCount <= 0) { + // remove this node from parentSwitchLink's childSwitchLinks + // clear childSwitchLinks + ArrayList switchLinks; + if (parentSwitchLink != null) { + for(i=0; i<parentSwitchLink.childrenSwitchLinks.size();i++) { + switchLinks = (ArrayList) + parentSwitchLink.childrenSwitchLinks.get(i); + if (switchLinks.contains(this)) { + switchLinks.remove(this); + break; + } + } + } + for (j=0; j<numChildren; j++) { + switchStates = (ArrayList)childrenSwitchStates.get(j); + switchStates.clear(); + } + switchLevels.remove(0); + } else { + // remove children dependent data + int hkIndex; + + // Must be in reverse, to preserve right indexing. + for (i = s.keys.length-1; i >= 0; i--) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + if(hkIndex >= 0) { + for (j=0; j<numChildren; j++) { + switchStates = (ArrayList)childrenSwitchStates.get(j); + switchStates.remove(hkIndex); + } + switchLevels.remove(hkIndex); + } + } + } + + super.removeNodeData(s); + } + + + + // synchronized with setWhichChild and setChildMask + synchronized void clearLive(SetLiveState s) { + Targets[] savedSwitchTargets = s.switchTargets; + s.switchTargets = null; + super.clearLive(s); + s.switchTargets = savedSwitchTargets; + } + + void initRenderChildMask() { + int i, nchildren; + nchildren = children.size(); + switch(whichChild) { + case Switch.CHILD_ALL: + for (i=0; i<nchildren; i++) { + renderChildMask.set(i); + } + break; + case Switch.CHILD_NONE: + for (i=0; i<nchildren; i++) { + renderChildMask.clear(i); + } + break; + case Switch.CHILD_MASK: + for (i=0; i<nchildren; i++) { + if (childMask.get(i) == true) { + renderChildMask.set(i); + } else { + renderChildMask.clear(i); + } + } + break; + default: + for (i=0; i<nchildren; i++) { + + if (i == whichChild) { + renderChildMask.set(i); + } else { + renderChildMask.clear(i); + } + } + } + } + + void traverseSwitchChild(int child, HashKey key, int index, + SwitchRetained switchRoot, boolean init, + boolean swChanged, boolean switchOn, + int switchLevel, ArrayList updateList) { + int i,j,k; + SwitchRetained sw; + LinkRetained ln; + Object obj; + ArrayList childSwitchLinks; + + boolean newSwChanged = false; + ArrayList childSwitchStates = + (ArrayList)childrenSwitchStates.get(child); + SwitchState switchState = (SwitchState)childSwitchStates.get(index); + switchState.updateCompositeSwitchMask(switchLevel, switchOn); + + if (switchRoot != null) { + if (init) { + if (!switchState.initialized) { + switchState.initSwitchOn(); + } + } else { + boolean compositeSwitchOn = switchState.evalCompositeSwitchOn(); + if (switchState.cachedSwitchOn != compositeSwitchOn) { + switchState.updateCachedSwitchOn(); + + switchRoot.updateTargets.addCachedTargets( + switchState.cachedTargets); + newSwChanged = true; + updateList.add(switchState); + } + } + } + + + childSwitchLinks = (ArrayList)childrenSwitchLinks.get(child); + int cslSize =childSwitchLinks.size(); + for (i=0; i<cslSize; i++) { + + obj = childSwitchLinks.get(i); + if (obj instanceof SwitchRetained) { + sw = (SwitchRetained)obj; + int swSize = sw.children.size(); + for(j=0; j<swSize; j++) { + sw.traverseSwitchChild(j, key, index, switchRoot, init, + newSwChanged, switchOn, switchLevel, + updateList); + } + } else { // LinkRetained + ln = (LinkRetained)obj; + if (key == null) { + switchKey.reset(); + switchKey.append(locale.nodeId); + } else { + switchKey.set(key); + } + switchKey.append(LinkRetained.plus).append(ln.nodeId); + + if ((ln.sharedGroup != null) && + (ln.sharedGroup.localToVworldKeys != null)) { + + j = switchKey.equals(ln.sharedGroup.localToVworldKeys,0, + ln.sharedGroup.localToVworldKeys.length); + if(j < 0) { + System.out.println("SwitchRetained : Can't find hashKey"); + } + + if (j<ln.sharedGroup.localToVworldKeys.length) { + int lscSize = ln.sharedGroup.children.size(); + for(k=0; k<lscSize; k++) { + ln.sharedGroup.traverseSwitchChild(k, ln.sharedGroup. + localToVworldKeys[j], + j, switchRoot, + init, newSwChanged, + switchOn, switchLevel, updateList); + } + } + } + } + } + } + + void traverseSwitchParent() { + boolean switchOn; + int switchLevel; + SwitchRetained switchRoot; + int i,j; + int size; + + // first traverse this node's child + if (inSharedGroup) { + for (j=0; j<localToVworldKeys.length; j++) { + switchLevel = ((Integer)switchLevels.get(j)).intValue(); + switchRoot = (switchLevel == 0)? this : null; + size = children.size(); + for (i=0; i<size; i++) { + switchOn = renderChildMask.get(i); + traverseSwitchChild(i, localToVworldKeys[j], j, switchRoot, + true, false, switchOn, switchLevel, null); + } + } + } else { + switchLevel = ((Integer)switchLevels.get(0)).intValue(); + switchRoot = (switchLevel == 0)? this : null; + size = children.size(); + for (i=0; i<size; i++) { + switchOn = renderChildMask.get(i); + traverseSwitchChild(i, null, 0, switchRoot, + true, false, switchOn, switchLevel, null); + } + } + + // now traverse this node's parent + if (parentSwitchLink != null) { + if (parentSwitchLink instanceof SwitchRetained) { + ((SwitchRetained)parentSwitchLink).traverseSwitchParent(); + } else if (parentSwitchLink instanceof SharedGroupRetained) { + ((SharedGroupRetained)parentSwitchLink).traverseSwitchParent(); + } + } + } + + + void computeCombineBounds(Bounds bounds) { + int i; + NodeRetained child; + + if(boundsAutoCompute) { + + if(whichChild == Switch.CHILD_ALL) { + for(i=0; i<children.size(); i++) { + child = (NodeRetained)children.get(i); + if(child != null) + child.computeCombineBounds(bounds); + } + } + else if(whichChild == Switch.CHILD_MASK) { + for(i=0; i<children.size(); i++) { + if(childMask.get(i)) { + child = (NodeRetained)children.get(i); + if(child != null) + child.computeCombineBounds(bounds); + } + } + } + else if(whichChild != Switch.CHILD_NONE) { + if (whichChild < children.size()) { + child = (NodeRetained)children.get(whichChild); + if(child != null) + child.computeCombineBounds(bounds); + } + } + + } + else + // Should this be lock too ? ( MT safe ? ) + synchronized(localBounds) { + bounds.combine(localBounds); + } + } + + + /** + * Gets the bounding object of a node. + * @return the node's bounding object + */ + Bounds getBounds() { + + int i; + NodeRetained child; + + if(boundsAutoCompute) { + BoundingSphere boundingSphere = new BoundingSphere(); + boundingSphere.setRadius(-1.0); + + if(whichChild == Switch.CHILD_ALL) { + for(i=0; i<children.size(); i++) { + child = (NodeRetained)children.get(i); + if(child != null) + child.computeCombineBounds((Bounds) boundingSphere); + } + } + else if(whichChild == Switch.CHILD_MASK) { + for(i=0; i<children.size(); i++) { + if(childMask.get(i)) { + child = (NodeRetained)children.get(i); + if(child != null) + child.computeCombineBounds((Bounds) boundingSphere); + } + } + } + else if(whichChild != Switch.CHILD_NONE && + whichChild >= 0 && + whichChild < children.size()) { + + child = (NodeRetained)children.get(whichChild); + if(child != null) + child.computeCombineBounds((Bounds) boundingSphere); + } + + return (Bounds) boundingSphere; + } + else + return super.getBounds(); + } + + + /* + void compile(CompileState compState) { + setCompiled(); + compState.startGroup(null); // don't merge at this level + compileChildren(compState); + compState.endGroup(); + } + */ + + /** + * Compiles the children of the switch, preventing shape merging at + * this level or above + */ + void compile(CompileState compState) { + + + super.compile(compState); + + // don't remove this group node + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numSwitches++; + } + } + + void insertChildrenData(int index) { + if (childrenSwitchStates == null) { + childrenSwitchStates = new ArrayList(1); + childrenSwitchLinks = new ArrayList(1); + } + + childrenSwitchLinks.add(index, new ArrayList(1)); + + ArrayList switchStates = new ArrayList(1); + childrenSwitchStates.add(index, switchStates); + if (source != null && source.isLive()) { + for (int i=0; i<localToVworld.length; i++) { + switchStates.add(new SwitchState(true)); + } + } + } + + void appendChildrenData() { + if (childrenSwitchStates == null) { + childrenSwitchStates = new ArrayList(1); + childrenSwitchLinks = new ArrayList(1); + } + childrenSwitchLinks.add(new ArrayList(1)); + + ArrayList switchStates = new ArrayList(1); + childrenSwitchStates.add(switchStates); + if (source != null && source.isLive()) { + for (int i=0; i<localToVworld.length; i++) { + switchStates.add(new SwitchState(true)); + } + } + } + + void removeChildrenData(int index) { + ArrayList oldSwitchStates = (ArrayList)childrenSwitchStates.get(index) +; + oldSwitchStates.clear(); + childrenSwitchStates.remove(index); + + ArrayList oldSwitchLinks = (ArrayList)childrenSwitchLinks.get(index); + oldSwitchLinks.clear(); + childrenSwitchLinks.remove(index); + } + + void childDoSetLive(NodeRetained child, int childIndex, SetLiveState s) { + + int numPaths = (inSharedGroup)? s.keys.length : 1; + s.childSwitchLinks = (ArrayList)childrenSwitchLinks.get(childIndex); + for (int j=0; j< numPaths; j++) { + s.closestSwitchIndices[j] = switchIndexCount; + s.closestSwitchParents[j] = this; + } + // use switchIndexCount instead of child index to avoid + // reordering due to add/remove child later + switchIndexCount++; + + Targets[] newTargets = new Targets[numPaths]; + for(int i=0; i<numPaths; i++) { + newTargets[i] = new Targets(); + } + s.switchTargets = newTargets; + s.switchStates = (ArrayList)childrenSwitchStates.get(childIndex); + + if(child!=null) + child.setLive(s); + + CachedTargets cachedTargets; + SwitchState switchState; + if (! inSharedGroup) { + cachedTargets = s.switchTargets[0].snapShotInit(); + switchState = (SwitchState) s.switchStates.get(0); + switchState.cachedTargets = cachedTargets; + } else { + for(int i=0; i<numPaths; i++) { + cachedTargets = s.switchTargets[i].snapShotInit(); + switchState = (SwitchState)s.switchStates.get( + s.hashkeyIndex[i]); + switchState.cachedTargets = cachedTargets; + } + } + } + + // *************************** + // TargetsInterface methods + // *************************** + + TargetsInterface getClosestTargetsInterface(int type) { + return (type == TargetsInterface.SWITCH_TARGETS)? + (TargetsInterface)this: + (TargetsInterface)parentTransformLink; + } + + public CachedTargets getCachedTargets(int type, int index, int child) { + if (type == TargetsInterface.SWITCH_TARGETS) { + ArrayList switchStates = + (ArrayList)childrenSwitchStates.get(child); + if (index < switchStates.size()) { + SwitchState switchState = + (SwitchState)switchStates.get(index); + return switchState.cachedTargets; + } else { + return null; + } + } else { + System.out.println("getCachedTargets: wrong arguments"); + return null; + } + } + + public void resetCachedTargets(int type, + CachedTargets[] newCtArr, int child) { + if (type == TargetsInterface.SWITCH_TARGETS) { + ArrayList switchStates = (ArrayList)childrenSwitchStates.get( + child); + if (newCtArr.length != switchStates.size()) { + System.out.println("resetCachedTargets: unmatched length!" + + newCtArr.length + " " + switchStates.size()); + System.out.println(" resetCachedTargets: " + this); + } + SwitchState switchState; + for (int i=0; i<newCtArr.length; i++) { + switchState = (SwitchState)switchStates.get(i); + switchState.cachedTargets = newCtArr[i]; + } + } else { + System.out.println("resetCachedTargets: wrong arguments"); + } + } + + public ArrayList getTargetsData(int type, int child) { + if (type == TargetsInterface.SWITCH_TARGETS) { + return (ArrayList)childrenSwitchStates.get(child); + } else { + System.out.println("getTargetsData: wrong arguments"); + return null; + } + } + + public int getTargetThreads(int type) { + System.out.println("getTargetsThreads: wrong arguments"); + return -1; + } + + public void updateCachedTargets(int type, CachedTargets[] newCt) { + System.out.println("updateCachedTarget: wrong arguments"); + } + + public void computeTargetThreads(int type, CachedTargets[] newCt) { + System.out.println("computeTargetThreads: wrong arguments"); + } + + public void updateTargetThreads(int type, CachedTargets[] newCt) { + System.out.println("updateTargetThreads: wrong arguments"); + } + + public void propagateTargetThreads(int type, int newTargetThreads) { + System.out.println("propagateTargetThreads: wrong arguments"); + } + + public void copyCachedTargets(int type, CachedTargets[] newCt) { + System.out.println("copyCachedTarget: wrong arguments"); + } +} diff --git a/src/classes/share/javax/media/j3d/SwitchState.java b/src/classes/share/javax/media/j3d/SwitchState.java new file mode 100644 index 0000000..82bc348 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SwitchState.java @@ -0,0 +1,112 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class SwitchState { + // a bitmask to track the composite switchOn state in a nested switch + // tree. A bit is set if a branch is switched off at the switch + // level specified by the position of the bit in the bitmask + // It is an array of long in order to support infinite deep nested + // switch tree + long compositeSwitchMask[] = new long[]{0}; + + // switchOn state cached in user thread + boolean cachedSwitchOn = true; + + // switchOn state in current time, is true if compositeSwitchMask == 0 + boolean currentSwitchOn = true; + + // switchOn state in last time, is true if compositeSwitchMask == 0 + boolean lastSwitchOn = true; + + boolean initialized = false; + + CachedTargets cachedTargets = null; + + boolean inSwitch = false; + + public SwitchState(boolean inSwitch) { + this.inSwitch = inSwitch; + initialized = !inSwitch; + } + + void dump() { + System.out.println( + " MASK " + compositeSwitchMask[0] + + " CACH " + cachedSwitchOn + + " CURR " + currentSwitchOn + + " LAST " + lastSwitchOn); + } + + void updateCompositeSwitchMask(int switchLevel, boolean switchOn) { + if (switchLevel < 64) { + if (switchOn) { + compositeSwitchMask[0] &= ~(1 << switchLevel); + } else { + compositeSwitchMask[0] |= (1 << switchLevel); + } + } else { + int i; + int index = switchLevel/64; + int offset = switchLevel%64; + + if (index > compositeSwitchMask.length) { + long newCompositeSwitchMask[] = new long[index+1]; + System.arraycopy(compositeSwitchMask, 0, + newCompositeSwitchMask, 0, index); + compositeSwitchMask = newCompositeSwitchMask; + } + if (switchOn) { + compositeSwitchMask[index] &= ~(1 << offset); + } else { + compositeSwitchMask[index] |= (1 << offset); + } + } + } + + void initSwitchOn() { + boolean switchOn = evalCompositeSwitchOn(); + currentSwitchOn = lastSwitchOn = cachedSwitchOn = switchOn; + //currentSwitchOn = cachedSwitchOn = switchOn; + initialized = true; + } + + void updateCurrentSwitchOn() { + currentSwitchOn = !currentSwitchOn; + } + + void updateLastSwitchOn() { + lastSwitchOn = currentSwitchOn; + } + + void updateCachedSwitchOn() { + cachedSwitchOn = !cachedSwitchOn; + } + + boolean evalCompositeSwitchOn() { + boolean switchOn; + if (compositeSwitchMask.length == 1) { + switchOn = (compositeSwitchMask[0] == 0); + } else { + switchOn = true; + for (int i=0; i<compositeSwitchMask.length; i++) { + if (compositeSwitchMask[i] != 0) { + switchOn = false; + break; + } + } + } + return switchOn; + } +} + diff --git a/src/classes/share/javax/media/j3d/SwitchValueInterpolator.java b/src/classes/share/javax/media/j3d/SwitchValueInterpolator.java new file mode 100644 index 0000000..c974db1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/SwitchValueInterpolator.java @@ -0,0 +1,276 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; + +/** + * SwitchValueInterpolator behavior. This class defines a + * behavior that modifies the selected child of the target + * switch node by linearly interpolating between a pair of + * specified child index values (using the value generated + * by the specified Alpha object). + */ + +public class SwitchValueInterpolator extends Interpolator { + + Switch target; + int firstSwitchIndex; + int lastSwitchIndex; + int childCount; + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, default constructor used by cloneNode + SwitchValueInterpolator() { + } + + /** + * Constructs a SwitchValueInterpolator behavior that varies its target + * Switch node's child index between 0 and <i>n</i>-1, where <i>n</i> + * is the number of children in the target Switch node. + * @param alpha the alpha object for this interpolator + * @param target the Switch node affected by this interpolator + */ + public SwitchValueInterpolator(Alpha alpha, + Switch target) { + + super(alpha); + + this.target = target; + firstSwitchIndex = 0; + childCount = target.numChildren(); + lastSwitchIndex = childCount - 1; + + } + + /** + * Constructs a SwitchValueInterpolator behavior that varies its target + * Switch node's child index between the two values provided. + * @param alpha the alpha object for this interpolator + * @param target the Switch node affected by this interpolator + * @param firstChildIndex the index of first child in the Switch node to + * select + * @param lastChildIndex the index of last child in the Switch node to + * select + */ + public SwitchValueInterpolator(Alpha alpha, + Switch target, + int firstChildIndex, + int lastChildIndex) { + + super(alpha); + + this.target = target; + firstSwitchIndex = firstChildIndex; + lastSwitchIndex = lastChildIndex; + computeChildCount(); + } + + /** + * This method sets the firstChildIndex for this interpolator. + * @param firstIndex the new index for the first child + */ + public void setFirstChildIndex(int firstIndex) { + firstSwitchIndex = firstIndex; + computeChildCount(); + } + + /** + * This method retrieves this interpolator's firstChildIndex. + * @return the interpolator's firstChildIndex + */ + public int getFirstChildIndex() { + return this.firstSwitchIndex; + } + + /** + * This method sets the lastChildIndex for this interpolator. + * @param lastIndex the new index for the last child + */ + public void setLastChildIndex(int lastIndex) { + lastSwitchIndex = lastIndex; + computeChildCount(); + } + + /** + * This method retrieves this interpolator's lastSwitchIndex. + * @return the interpolator's maximum scale value + */ + public int getLastChildIndex() { + return this.lastSwitchIndex; + } + + /** + * This method sets the target for this interpolator. + * @param target the target Switch node + */ + public void setTarget(Switch target) { + this.target = target; + } + + /** + * This method retrieves this interpolator's target Switch node + * reference. + * @return the interpolator's target Switch node + */ + public Switch getTarget() { + return target; + } + + // The SwitchValueInterpolator's initialize routine uses the default + // initialization routine. + + /** + * This method is invoked by the behavior scheduler every frame. + * It maps the alpha value that corresponds to the current time + * into a child index value and updates the specified Switch node + * with this new child index value. + * @param criteria an enumeration of the criteria that triggered + * this stimulus + */ + public void processStimulus(Enumeration criteria) { + // Handle stimulus + WakeupCriterion criterion = passiveWakeupCriterion; + + if (alpha != null) { + float value = alpha.value(); + + if (value != prevAlphaValue) { + int child; + + if (lastSwitchIndex > firstSwitchIndex) { + child = (int)(firstSwitchIndex + + (int)(value * (childCount-1) + 0.49999999999f)); + } else { + child = (int)(firstSwitchIndex - + (int)(value * (childCount-1) + 0.49999999999f)); + } + target.setWhichChild(child); + prevAlphaValue = value; + } + if (!alpha.finished() && !alpha.isPaused()) { + criterion = defaultWakeupCriterion; + } + } + wakeupOn(criterion); + } + + + /** + * calculate the number of the child to manage for this switch node + */ + final private void computeChildCount() { + if (lastSwitchIndex >= firstSwitchIndex) { + childCount = lastSwitchIndex - firstSwitchIndex + 1; + } else { + childCount = firstSwitchIndex - lastSwitchIndex + 1; + } + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + SwitchValueInterpolator svi = new SwitchValueInterpolator(); + svi.duplicateNode(this, forceDuplicate); + return svi; + } + + + /** + * Copies all SwitchValueInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + SwitchValueInterpolator si = + (SwitchValueInterpolator) originalNode; + + setFirstChildIndex(si.getFirstChildIndex()); + setLastChildIndex(si.getLastChildIndex()); + // this reference will be updated in updateNodeReferences() + setTarget(si.getTarget()); + } + + /** + * Callback used to allow a node to check if any nodes referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any node references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding Node in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * node is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + super.updateNodeReferences(referenceTable); + + // check Switch + Node n = getTarget(); + + if (n != null) { + setTarget((Switch) referenceTable.getNewObjectReference(n)); + } + } +} diff --git a/src/classes/share/javax/media/j3d/Table.java b/src/classes/share/javax/media/j3d/Table.java new file mode 100644 index 0000000..432b83b --- /dev/null +++ b/src/classes/share/javax/media/j3d/Table.java @@ -0,0 +1,84 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Used by ImageComponent for data conversions + */ + +class Table extends Object { + + // 2 to 8 bit data conversion + static final int[] table2To8Bit = {0,85,170,255}; + + // 3 to 8 bit data conversion + static final int[] table3To8Bit = {0,36,73,109,146,182,219,255}; + + // 4 to 8 bit data conversion + static final int[] table4To8Bit = { + 0,17,34,51,68,85,102,119,136,153,170,187,204,221,238,255}; + + // 5 to 8 bit data conversion + static final int[] table5To8Bit = { + 0,8,16,25,33,41,49,58,66,74,82,90,99,107,115,123,132,140,148,156, + 165,173,181,189,197,206,214,222,230,239,247,255}; + + // 8 to 4 bit data conversion + static final int[] table8To4Bit = { + 0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11, + 11,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12, + 12,12,12,12,12,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14, + 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15, + 15,15}; + + // 8 to 5 bit data conversion + static int[] table8To5Bit = { + 0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,3,3,3,3,3, + 3,3,3,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7, + 7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,11,11,11, + 11,11,11,11,11,12,12,12,12,12,12,12,12,13,13,13,13,13,13,13,13,13,14, + 14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16, + 17,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19, + 19,19,20,20,20,20,20,20,20,20,21,21,21,21,21,21,21,21,22,22,22,22,22, + 22,22,22,22,23,23,23,23,23,23,23,23,24,24,24,24,24,24,24,24,25,25,25, + 25,25,25,25,25,26,26,26,26,26,26,26,26,27,27,27,27,27,27,27,27,27,28, + 28,28,28,28,28,28,28,29,29,29,29,29,29,29,29,30,30,30,30,30,30,30,30, + 31,31,31,31,31}; + + // 8 to 3 bit data conversion + static int[] table8To3Bit = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3, + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7}; + + // 8 to 2 bit data conversion + static int[] table8To2Bit = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 3,3,3,3,3,3,3,3,3,3,3}; +} diff --git a/src/classes/share/javax/media/j3d/Targets.java b/src/classes/share/javax/media/j3d/Targets.java new file mode 100644 index 0000000..1e6c13a --- /dev/null +++ b/src/classes/share/javax/media/j3d/Targets.java @@ -0,0 +1,198 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +class Targets { + + static final int MAX_NODELIST = 7; + + static final int GEO_TARGETS = 0; // For geometryAtoms. + static final int ENV_TARGETS = 1; // For enviroment nodes. + static final int BEH_TARGETS = 2; // For behavior nodes. + static final int SND_TARGETS = 3; // For sound nodes. + static final int VPF_TARGETS = 4; // For viewPlatform nodes. + static final int BLN_TARGETS = 5; // For boundingLeaf nodes. + static final int GRP_TARGETS = 6; // For group nodes. + + ArrayList[] targetList = new ArrayList[MAX_NODELIST]; + + void addNode(NnuId node, int targetType) { + if(targetList[targetType] == null) + targetList[targetType] = new ArrayList(1); + + targetList[targetType].add(node); + } + + void addNodeArray(NnuId[] nodeArr, int targetType) { + if(targetList[targetType] == null) + targetList[targetType] = new ArrayList(1); + + targetList[targetType].add(nodeArr); + } + + + void removeNode(int index, int targetType) { + if(targetList[targetType] != null) { + targetList[targetType].remove(index); + } + } + + + void addNodes(ArrayList nodeList, int targetType) { + if(targetList[targetType] == null) + targetList[targetType] = new ArrayList(1); + + targetList[targetType].addAll(nodeList); + } + + + void clearNodes() { + for(int i=0; i<MAX_NODELIST; i++) { + if (targetList[i] != null) { + targetList[i].clear(); + } + } + } + + CachedTargets snapShotInit() { + + CachedTargets cachedTargets = new CachedTargets(); + + + for(int i=0; i<MAX_NODELIST; i++) { + if(targetList[i] != null) { + int size = targetList[i].size(); + NnuId[] nArr = new NnuId[size]; + targetList[i].toArray(nArr); + cachedTargets.targetArr[i] = nArr; + // System.out.println("Before sort : "); + // NnuIdManager.printIds(cachedTargets.targetArr[i]); + NnuIdManager.sort((NnuId[])cachedTargets.targetArr[i]); + // System.out.println("After sort : "); + // NnuIdManager.printIds(cachedTargets.targetArr[i]); + } else { + cachedTargets.targetArr[i] = null; + } + } + + clearNodes(); + + return cachedTargets; + } + + + CachedTargets snapShotAdd(CachedTargets cachedTargets) { + + int i, size; + + CachedTargets newCachedTargets = new CachedTargets(); + + for(i=0; i<MAX_NODELIST; i++) { + if((targetList[i] != null) && (cachedTargets.targetArr[i] == null)) { + size = targetList[i].size(); + NnuId[] nArr = new NnuId[size]; + targetList[i].toArray(nArr); + newCachedTargets.targetArr[i] = nArr; + NnuIdManager.sort(newCachedTargets.targetArr[i]); + + } + else if((targetList[i] != null) && (cachedTargets.targetArr[i] != null)) { + + size = targetList[i].size(); + NnuId[] targetArr = new NnuId[size]; + targetList[i].toArray(targetArr); + NnuIdManager.sort(targetArr); + newCachedTargets.targetArr[i] = + NnuIdManager.merge(cachedTargets.targetArr[i], targetArr); + + } + else if((targetList[i] == null) && (cachedTargets.targetArr[i] != null)) { + newCachedTargets.targetArr[i] = cachedTargets.targetArr[i]; + } + } + + clearNodes(); + + return newCachedTargets; + + } + + + CachedTargets snapShotRemove(CachedTargets cachedTargets) { + + int i, size; + NnuId[] targetArr; + + + CachedTargets newCachedTargets = new CachedTargets(); + + for(i=0; i<MAX_NODELIST; i++) { + + if((targetList[i] != null) && (cachedTargets.targetArr[i] != null)) { + size = targetList[i].size(); + targetArr = new NnuId[size]; + targetList[i].toArray(targetArr); + NnuIdManager.sort(targetArr); + newCachedTargets.targetArr[i] = + NnuIdManager.delete(cachedTargets.targetArr[i], targetArr); + + } + else if((targetList[i] == null) && (cachedTargets.targetArr[i] != null)) { + newCachedTargets.targetArr[i] = cachedTargets.targetArr[i]; + + } + else if((targetList[i] != null) && (cachedTargets.targetArr[i] == null)) { + System.out.println("You can't remove something that isn't there"); + } + + } + + clearNodes(); + + return newCachedTargets; + + } + + boolean isEmpty() { + boolean empty = true; + + for (int i=0; i < MAX_NODELIST; i++) { + if (targetList[i] != null) { + empty = false; + break; + } + } + return empty; + } + + void addCachedTargets(CachedTargets cachedTargets) { + for(int i=0; i<MAX_NODELIST; i++) { + if (cachedTargets.targetArr[i] != null ) { + addNodeArray(cachedTargets.targetArr[i], i); + } + } + } + + void dump() { + for(int i=0; i<Targets.MAX_NODELIST; i++) { + if (targetList[i] != null) { + System.out.println(" " + CachedTargets.typeString[i]); + for(int j=0; j<targetList[i].size(); j++) { + System.out.println(" " + targetList[i].get(j)); + } + } + } + } +} diff --git a/src/classes/share/javax/media/j3d/TargetsInterface.java b/src/classes/share/javax/media/j3d/TargetsInterface.java new file mode 100644 index 0000000..34d2f8e --- /dev/null +++ b/src/classes/share/javax/media/j3d/TargetsInterface.java @@ -0,0 +1,35 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.ArrayList; + + +interface TargetsInterface { + + static final int TRANSFORM_TARGETS = 0; + static final int SWITCH_TARGETS = 1; + + // used by Switch, TransformGroup and SharedGroup + abstract CachedTargets getCachedTargets(int type, int index, int child); + abstract void resetCachedTargets(int type, CachedTargets[] newCt, int child); + // used by TransformGroup and SharedGroup + abstract int getTargetThreads(int type); + abstract void updateCachedTargets(int type, CachedTargets[] newCt); + abstract void computeTargetThreads(int type, CachedTargets[] newCt); + abstract void updateTargetThreads(int type, CachedTargets[] newCt); + abstract void propagateTargetThreads(int type, int childTargetThreads); + abstract void copyCachedTargets(int type, CachedTargets[] newCt); + + // used by Switch and SharedGroup + abstract ArrayList getTargetsData(int type, int index); +} diff --git a/src/classes/share/javax/media/j3d/TexCoordGeneration.java b/src/classes/share/javax/media/j3d/TexCoordGeneration.java new file mode 100644 index 0000000..59ac29d --- /dev/null +++ b/src/classes/share/javax/media/j3d/TexCoordGeneration.java @@ -0,0 +1,644 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Vector4f; + +/** + * The TexCoordGeneration object contains all parameters needed for + * automatic texture coordinate generation. It is included as part + * of an Appearance component object. + * <p> + * Texture coordinates determine which texel in the texture map is + * assigned to a given vertex. Texture coordinates are interpolated + * between vertices, similarly to how colors are interpolated between + * two vertices of lines and polygons. + * <p> + * Texture coordinates consist of two, three or four coordinates. + * These coordinates + * are referred to as the <i>S</i>, <i>T</i>, <i>R</i>, and <i>Q</i> + * coordinates. + * 2D textures use the <i>S</i> and <i>T</i> coordinates. 3D textures + * use the <i>S</i>, <i>T</i> and <i>R</i> coordinates. The <i>Q</i> + * coordinate, similar to the <i>w</i> coordinate of the <i>(x, y, z, w)</i> + * object coordinates, is used to create homogeneous coordinates. + * <p> + * Rather than the programmer having to explicitly assign texture + * coordinates, Java 3D can automatically generate the texture + * coordinates to achieve texture mapping onto contours. + * The TexCoordGeneration attributes specify the functions for automatically + * generating texture coordinates. The texture attributes that can be + * defined are: + * <p><ul> + * <li>Texture format - defines whether the generated texture + * coordinates are 2D, 3D, or 4D:<p> + * <ul> + * <li>TEXTURE_COORDINATE_2 - generates 2D texture coordinates + * (S and T).<p> + * <li>TEXTURE_COORDINATE_3 - generates 3D texture coordinates + * (S, T, and R).<p> + * <li>TEXTURE_COORDINATE_4 - generates 4D texture coordinates + * (S, T, R, and Q).<p> + * </ul> + * <li>Texture generation mode - defines how the texture coordinates + * are generated:<p> + * <ul> + * <li>OBJECT_LINEAR - texture coordinates are generated as a linear + * function in object coordinates. The function used is:<p> + * <ul> + * <code>g = p<sub>1</sub>x<sub>o</sub> + p<sub>2</sub>y<sub>o</sub> + p<sub>3</sub>z<sub>o</sub> + p<sub>4</sub>w<sub>o</sub></code> + * <p> + * where<br> + * <ul><code>g</code> is the value computed for the coordinate.<br> + * <code>p<sub>1</sub></code>, <code>p<sub>2</sub></code>, + * <code>p<sub>3</sub></code>, and <code>p<sub>4</sub></code> + * are the plane equation coefficients (described below).<br> + * x<sub>o</sub>, y<sub>o</sub>, z<sub>o</sub>, and w<sub>o</sub> are + * the object coordinates of the vertex.<p> + * </ul></ul> + * <li>EYE_LINEAR - texture coordinates are generated as a linear + * function in eye coordinates. The function used is:<p> + * <ul> + * <code>g = p<sub>1</sub>'x<sub>e</sub> + p<sub>2</sub>'y<sub>e</sub> + p<sub>3</sub>'z<sub>e</sub> + p<sub>4</sub>'w<sub>e</sub></code> + * <p> + * where<br> + * <ul><code>x<sub>e</sub></code>, <code>y<sub>e</sub></code>, + * <code>z<sub>e</sub></code>, and w<sub>e</sub></code> are the eye + * coordinates of the vertex.<br> + * <code>p<sub>1</sub>'</code>, <code>p<sub>2</sub>'</code>, + * <code>p<sub>3</sub>'</code>, and <code>p<sub>4</sub>'</code> + * are the plane equation coefficients transformed into eye + * coordinates.<p> + * </ul></ul> + * + * <li>SPHERE_MAP - texture coordinates are generated using + * spherical reflection mapping in eye coordinates. Used to simulate + * the reflected image of a spherical environment onto a polygon.<p> + * + * <li>NORMAL_MAP - texture coordinates are generated to match + * vertices' normals in eye coordinates. This is only available if + * TextureCubeMap is available. + * </li><p> + * + * <li>REFLECTION_MAP - texture coordinates are generated to match + * vertices' reflection vectors in eye coordinates. This is only available + * if TextureCubeMap is available. + * </li><p> + * </ul> + * <li>Plane equation coefficients - defines the coefficients for the + * plane equations used to generate the coordinates in the + * OBJECT_LINEAR and EYE_LINEAR texture generation modes. + * The coefficients define a reference plane in either object coordinates + * or in eye coordinates, depending on the texture generation mode. + * <p> + * The equation coefficients are set by the <code>setPlaneS</code>, + * <code>setPlaneT</code>, <code>setPlaneR</code>, and <code>setPlaneQ</code> + * methods for each of the S, T, R, and Q coordinate functions, respectively. + * By default the equation coefficients are set as follows:<p> + * <ul> + * plane S = (1.0, 0.0, 0.0, 0.0)<br> + * plane T = (0.0, 1.0, 0.0, 0.0)<br> + * plane R = (0.0, 0.0, 0.0, 0.0)<br> + * plane Q = (0.0, 0.0, 0.0, 0.0)<p> + * </ul></ul> + * Texture coordinate generation is enabled or disabled by the + * <code>setEnable</code> method. When enabled, the specified + * texture coordinate is computed according to the generating function + * associated with the coordinate. When disabled, subsequent vertices + * take the specified texture coordinate from the current set of + * texture coordinates.<p> + * + * @see Canvas3D#queryProperties + */ +public class TexCoordGeneration extends NodeComponent { + + /** + * Specifies that this TexCoordGeneration object allows reading its + * enable flag. + */ + public static final int + ALLOW_ENABLE_READ = CapabilityBits.TEX_COORD_GENERATION_ALLOW_ENABLE_READ; + + /** + * Specifies that this TexCoordGeneration object allows writing its + * enable flag. + */ + public static final int + ALLOW_ENABLE_WRITE = CapabilityBits.TEX_COORD_GENERATION_ALLOW_ENABLE_WRITE; + + /** + * Specifies that this TexCoordGeneration object allows reading its + * format information. + */ + public static final int + ALLOW_FORMAT_READ = CapabilityBits.TEX_COORD_GENERATION_ALLOW_FORMAT_READ; + + /** + * Specifies that this TexCoordGeneration object allows reading its + * mode information. + */ + public static final int + ALLOW_MODE_READ = CapabilityBits.TEX_COORD_GENERATION_ALLOW_MODE_READ; + + /** + * Specifies that this TexCoordGeneration object allows reading its + * planeS, planeR, and planeT component information. + */ + public static final int + ALLOW_PLANE_READ = CapabilityBits.TEX_COORD_GENERATION_ALLOW_PLANE_READ; + + /** + * Specifies that this TexCoordGeneration object allows writing its + * planeS, planeR, and planeT component information. + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_PLANE_WRITE = + CapabilityBits.TEX_COORD_GENERATION_ALLOW_PLANE_WRITE; + + /** + * Generates texture coordinates as a linear function in + * object coordinates. + * + * @see #setGenMode + */ + public static final int OBJECT_LINEAR = 0; + /** + * Generates texture coordinates as a linear function in + * eye coordinates. + * + * @see #setGenMode + */ + public static final int EYE_LINEAR = 1; + /** + * Generates texture coordinates using a spherical reflection + * mapping in eye coordinates. + * + * @see #setGenMode + */ + public static final int SPHERE_MAP = 2; + /** + * Generates texture coordinates that match vertices' normals in + * eye coordinates. + * + * @see #setGenMode + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public static final int NORMAL_MAP = 3; + /** + * Generates texture coordinates that match vertices' reflection + * vectors in eye coordinates. + * + * @see #setGenMode + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public static final int REFLECTION_MAP = 4; + + // Definitions for format + /** + * Generates 2D texture coordinates (S and T). + * + * @see #setFormat + */ + public static final int TEXTURE_COORDINATE_2 = 0; + /** + * Generates 3D texture coordinates (S, T, and R). + * + * @see #setFormat + */ + public static final int TEXTURE_COORDINATE_3 = 1; + /** + * Generates 4D texture coordinates (S, T, R, and Q). + * + * @see #setFormat + * + * @since Java 3D 1.3 + */ + public static final int TEXTURE_COORDINATE_4 = 2; + + /** + * Constructs a TexCoordGeneration object with default parameters. + * The default values are as follows: + * <ul> + * enable flag : true<br> + * texture generation mode : OBJECT_LINEAR<br> + * format : TEXTURE_COORDINATE_2<br> + * plane S : (1,0,0,0)<br> + * plane T : (0,1,0,0)<br> + * plane R : (0,0,0,0)<br> + * plane Q : (0,0,0,0)<br> + * </ul> + */ + public TexCoordGeneration() { + // Just use the defaults + } + + /** + * Constructs a TexCoordGeneration object with the specified genMode and + * format. + * Defaults will be used for the rest of the state variables. + * @param genMode texture generation mode, one of: OBJECT_LINEAR, + * EYE_LINEAR, SPHERE_MAP, NORMAL_MAP, or REFLECTION_MAP + * @param format texture format, one of: TEXTURE_COORDINATE_2, + * TEXTURE_COORDINATE_3, or TEXTURE_COORDINATE_4 + * + * @see Canvas3D#queryProperties + */ + public TexCoordGeneration(int genMode, int format) { + ((TexCoordGenerationRetained)this.retained).initGenMode(genMode); + ((TexCoordGenerationRetained)this.retained).initFormat(format); + } + + /** + * Constructs a TexCoordGeneration object with the specified genMode, + * format, and the S coordinate plane equation. + * Defaults will be used for the rest of the state variables. + * @param genMode texture generation mode, one of: OBJECT_LINEAR, + * EYE_LINEAR, SPHERE_MAP, NORMAL_MAP, or REFLECTION_MAP + * @param format texture format, one of: TEXTURE_COORDINATE_2, + * TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4 + * @param planeS plane equation for the S coordinate + * + * @see Canvas3D#queryProperties + */ + public TexCoordGeneration(int genMode, int format, Vector4f planeS) { + ((TexCoordGenerationRetained)this.retained).initGenMode(genMode); + ((TexCoordGenerationRetained)this.retained).initFormat(format); + ((TexCoordGenerationRetained)this.retained).initPlaneS(planeS); + } + + /** + * Constructs a TexCoordGeneration object with the specified genMode, + * format, and the S and T coordinate plane equations. + * Defaults will be used for the rest of the state variables. + * @param genMode texture generation mode, one of: OBJECT_LINEAR, + * EYE_LINEAR, SPHERE_MAP, NORMAL_MAP, or REFLECTION_MAP + * @param format texture format, one of: TEXTURE_COORDINATE_2, + * TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4 + * @param planeS plane equation for the S coordinate + * @param planeT plane equation for the T coordinate + * + * @see Canvas3D#queryProperties + */ + public TexCoordGeneration(int genMode, int format, Vector4f planeS, + Vector4f planeT) { + ((TexCoordGenerationRetained)this.retained).initGenMode(genMode); + ((TexCoordGenerationRetained)this.retained).initFormat(format); + ((TexCoordGenerationRetained)this.retained).initPlaneS(planeS); + ((TexCoordGenerationRetained)this.retained).initPlaneT(planeT); + } + + /** + * Constructs a TexCoordGeneration object with the specified genMode, + * format, and the S, T, and R coordinate plane equations. + * @param genMode texture generation mode, one of: OBJECT_LINEAR, + * EYE_LINEAR, SPHERE_MAP, NORMAL_MAP, or REFLECTION_MAP + * @param format texture format, one of: TEXTURE_COORDINATE_2, + * TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4 + * @param planeS plane equation for the S coordinate + * @param planeT plane equation for the T coordinate + * @param planeR plane equation for the R coordinate + * + * @see Canvas3D#queryProperties + */ + public TexCoordGeneration(int genMode, int format, Vector4f planeS, + Vector4f planeT, Vector4f planeR) { + ((TexCoordGenerationRetained)this.retained).initGenMode(genMode); + ((TexCoordGenerationRetained)this.retained).initFormat(format); + ((TexCoordGenerationRetained)this.retained).initPlaneS(planeS); + ((TexCoordGenerationRetained)this.retained).initPlaneT(planeT); + ((TexCoordGenerationRetained)this.retained).initPlaneR(planeR); + } + + /** + * Constructs a TexCoordGeneration object with the specified genMode, + * format, and the S, T, R, and Q coordinate plane equations. + * @param genMode texture generation mode, one of: OBJECT_LINEAR, + * EYE_LINEAR, SPHERE_MAP, NORMAL_MAP, or REFLECTION_MAP + * @param format texture format, one of: TEXTURE_COORDINATE_2, + * TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4 + * @param planeS plane equation for the S coordinate + * @param planeT plane equation for the T coordinate + * @param planeR plane equation for the R coordinate + * @param planeQ plane equation for the Q coordinate + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public TexCoordGeneration(int genMode, int format, Vector4f planeS, + Vector4f planeT, Vector4f planeR, + Vector4f planeQ) { + ((TexCoordGenerationRetained)this.retained).initGenMode(genMode); + ((TexCoordGenerationRetained)this.retained).initFormat(format); + ((TexCoordGenerationRetained)this.retained).initPlaneS(planeS); + ((TexCoordGenerationRetained)this.retained).initPlaneT(planeT); + ((TexCoordGenerationRetained)this.retained).initPlaneR(planeR); + ((TexCoordGenerationRetained)this.retained).initPlaneQ(planeQ); + } + + /** + * Enables or disables texture coordinate generation for this + * appearance component object. + * @param state true or false to enable or disable texture coordinate + * generation + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setEnable(boolean state) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ENABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration0")); + if (isLive()) + ((TexCoordGenerationRetained)this.retained).setEnable(state); + else + ((TexCoordGenerationRetained)this.retained).initEnable(state); + } + + /** + * Retrieves the state of the texCoordGeneration enable flag. + * @return true if texture coordinate generation is enabled, + * false if texture coordinate generation is disabled + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getEnable() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ENABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration1")); + return ((TexCoordGenerationRetained)this.retained).getEnable(); + } + /** + * Sets the TexCoordGeneration format to the specified value. + * @param format texture format, one of: TEXTURE_COORDINATE_2, + * TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4 + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + public void setFormat(int format) { + checkForLiveOrCompiled(); + ((TexCoordGenerationRetained)this.retained).initFormat(format); + + } + + /** + * Retrieves the current TexCoordGeneration format. + * @return the texture format + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getFormat() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_FORMAT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration2")); + return ((TexCoordGenerationRetained)this.retained).getFormat(); + } + + /** + * Sets the TexCoordGeneration generation mode to the specified value. + * @param genMode texture generation mode, one of: OBJECT_LINEAR, + * EYE_LINEAR, SPHERE_MAP, NORMAL_MAP, or REFLECTION_MAP. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @exception IllegalArgumentException if <code>genMode</code> is + * a value other than <code>OBJECT_LINEAR</code>, <code>EYE_LINEAR</code>, + * <code>SPHERE_MAP</code>, <code>NORMAL_MAP</code>, or + * <code>REFLECTION_MAP</code>. + * + * @see Canvas3D#queryProperties + */ + public void setGenMode(int genMode) { + checkForLiveOrCompiled(); + + if ((genMode < OBJECT_LINEAR) || (genMode > REFLECTION_MAP)) { + throw new IllegalArgumentException( + J3dI18N.getString("TexCoordGeneration5")); + } + ((TexCoordGenerationRetained)this.retained).initGenMode(genMode); + } + + /** + * Retrieves the current TexCoordGeneration generation mode. + * @return the texture generation mode + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getGenMode() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration3")); + return ((TexCoordGenerationRetained)this.retained).getGenMode(); + } + + /** + * Sets the S coordinate plane equation. This plane equation + * is used to generate the S coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeS plane equation for the S coordinate + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPlaneS(Vector4f planeS) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PLANE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration6")); + + if (isLive()) + ((TexCoordGenerationRetained)this.retained).setPlaneS(planeS); + else + ((TexCoordGenerationRetained)this.retained).initPlaneS(planeS); + } + + /** + * Retrieves a copy of the plane equation used to + * generate the S coordinate. + * @param planeS the S coordinate plane equation + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getPlaneS(Vector4f planeS) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PLANE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration4")); + ((TexCoordGenerationRetained)this.retained).getPlaneS(planeS); + } + + /** + * Sets the T coordinate plane equation. This plane equation + * is used to generate the T coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeT plane equation for the T coordinate + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPlaneT(Vector4f planeT) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PLANE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration6")); + + if (isLive()) + ((TexCoordGenerationRetained)this.retained).setPlaneT(planeT); + else + ((TexCoordGenerationRetained)this.retained).initPlaneT(planeT); + } + + /** + * Retrieves a copy of the plane equation used to + * generate the T coordinate. + * @param planeT the T coordinate plane equation + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getPlaneT(Vector4f planeT) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PLANE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration4")); + ((TexCoordGenerationRetained)this.retained).getPlaneT(planeT); + } + + /** + * Sets the R coordinate plane equation. This plane equation + * is used to generate the R coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeR plane equation for the R coordinate + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPlaneR(Vector4f planeR) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PLANE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration6")); + + if (isLive()) + ((TexCoordGenerationRetained)this.retained).setPlaneR(planeR); + else + ((TexCoordGenerationRetained)this.retained).initPlaneR(planeR); + } + + /** + * Retrieves a copy of the plane equation used to + * generate the R coordinate. + * @param planeR the R coordinate plane equation + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getPlaneR(Vector4f planeR) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PLANE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration4")); + ((TexCoordGenerationRetained)this.retained).getPlaneR(planeR); + } + + /** + * Sets the Q coordinate plane equation. This plane equation + * is used to generate the Q coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeQ plane equation for the Q coordinate + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void setPlaneQ(Vector4f planeQ) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PLANE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration6")); + + if (isLive()) + ((TexCoordGenerationRetained)this.retained).setPlaneQ(planeQ); + else + ((TexCoordGenerationRetained)this.retained).initPlaneQ(planeQ); + } + + /** + * Retrieves a copy of the plane equation used to + * generate the Q coordinate. + * @param planeQ the Q coordinate plane equation + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getPlaneQ(Vector4f planeQ) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PLANE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TexCoordGeneration4")); + ((TexCoordGenerationRetained)this.retained).getPlaneQ(planeQ); + } + + /** + * Creates a retained mode TexCoordGenerationRetained object that this + * TexCoordGeneration component object will point to. + */ + void createRetained() { + this.retained = new TexCoordGenerationRetained(); + this.retained.setSource(this); + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + TexCoordGeneration tga = new TexCoordGeneration(); + tga.duplicateNodeComponent(this); + return tga; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + TexCoordGenerationRetained tex = (TexCoordGenerationRetained) + originalNodeComponent.retained; + TexCoordGenerationRetained rt = (TexCoordGenerationRetained) retained; + + Vector4f v = new Vector4f(); + + rt.initGenMode(tex.getGenMode()); + tex.getPlaneS(v); + rt.initPlaneS(v); + tex.getPlaneT(v); + rt.initPlaneT(v); + tex.getPlaneR(v); + rt.initPlaneR(v); + tex.getPlaneQ(v); + rt.initPlaneQ(v); + rt.initFormat(tex.getFormat()); + rt.initEnable(tex.getEnable()); + } +} diff --git a/src/classes/share/javax/media/j3d/TexCoordGenerationRetained.java b/src/classes/share/javax/media/j3d/TexCoordGenerationRetained.java new file mode 100644 index 0000000..7f5acb7 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TexCoordGenerationRetained.java @@ -0,0 +1,423 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Vector4f; +import java.util.ArrayList; + +/** + * The TexCoordGeneration object contains all parameters needed for texture + * coordinate generation. It is included as part of an Appearance + * component object. + */ +class TexCoordGenerationRetained extends NodeComponentRetained { + + // A list of pre-defined bits to indicate which component + // in this TexCoordGeneration object changed. + private static final int ENABLE_CHANGED = 0x01; + private static final int PLANE_S_CHANGED = 0x02; + private static final int PLANE_T_CHANGED = 0x04; + private static final int PLANE_R_CHANGED = 0x08; + private static final int PLANE_Q_CHANGED = 0x10; + + // + // State variables + // + int genMode = TexCoordGeneration.OBJECT_LINEAR; + int format = TexCoordGeneration.TEXTURE_COORDINATE_2; + + Vector4f planeS = new Vector4f(1.0f, 0.0f, 0.0f, 0.0f); + Vector4f planeT = new Vector4f(0.0f, 1.0f, 0.0f, 0.0f); + Vector4f planeR = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f); + Vector4f planeQ = new Vector4f(0.0f, 0.0f, 0.0f, 0.0f); + + /** + * Flag to enable/disable Texture coordinate generation. + */ + boolean enable = true; + + // true when mirror texCoord component set + boolean mirrorCompDirty = false; + + /** + * Enables or disables texture coordinate generation for this + * appearance component object. + * @param state true or false to enable or disable texture coordinate + * generation + */ + final void initEnable(boolean state) { + enable = state; + } + /** + * Enables or disables texture coordinate generation for this + * appearance component object and sends a message notifying + * the interested structures of the change. + * @param state true or false to enable or disable texture coordinate + * generation + */ + final void setEnable(boolean state) { + initEnable(state); + sendMessage(ENABLE_CHANGED, (state ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Retrieves the state of the texCoordGeneration enable flag. + * @return true if texture coordinate generation is enabled, + * false if texture coordinate generation is disabled + */ + final boolean getEnable() { + return enable; + } + /** + * Sets the TexCoordGeneration format to the specified value. + * @param format texture format, one of: TEXTURE_COORDINATE_2 + * or TEXTURE_COORDINATE_3 + */ + final void initFormat(int format) { + this.format = format; + } + + /** + * Retrieves the current TexCoordGeneration format. + * @return the texture format + */ + final int getFormat() { + return format; + } + + /** + * Sets the TexCoordGeneration generation mode to the specified value. + * @param genMode texture generation mode, one of: OBJECT_LINEAR, + * EYE_LINEAR, or SPHERE_MAP + */ + final void initGenMode(int genMode) { + this.genMode = genMode; + } + + /** + * Retrieves the current TexCoordGeneration generation mode. + * @return the texture generation mode + */ + final int getGenMode() { + return genMode; + } + + /** + * Sets the S coordinate plane equation. This plane equation + * is used to generate the S coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeS plane equation for the S coordinate + */ + final void setPlaneS(Vector4f planeS) { + initPlaneS(planeS); + sendMessage(PLANE_S_CHANGED, new Vector4f(planeS)); + } + + /** + * Sets the S coordinate plane equation. This plane equation + * is used to generate the S coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeS plane equation for the S coordinate + */ + final void initPlaneS(Vector4f planeS) { + this.planeS.set(planeS); + } + + /** + * Retrieves a copy of the plane equation used to + * generate the S coordinate. + * @param planeS the S coordinate plane equation + */ + final void getPlaneS(Vector4f planeS) { + planeS.set(this.planeS); + } + + /** + * Sets the T coordinate plane equation. This plane equation + * is used to generate the T coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeT plane equation for the T coordinate + */ + final void setPlaneT(Vector4f planeT) { + initPlaneT(planeT); + sendMessage(PLANE_T_CHANGED, new Vector4f(planeT)); + } + + /** + * Sets the T coordinate plane equation. This plane equation + * is used to generate the T coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeT plane equation for the T coordinate + */ + final void initPlaneT(Vector4f planeT) { + this.planeT.set(planeT); + } + + /** + * Retrieves a copy of the plane equation used to + * generate the T coordinate. + * @param planeT the T coordinate plane equation + */ + final void getPlaneT(Vector4f planeT) { + planeT.set(this.planeT); + } + + /** + * Sets the R coordinate plane equation. This plane equation + * is used to generate the R coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeR plane equation for the R coordinate + */ + final void setPlaneR(Vector4f planeR) { + initPlaneR(planeR); + sendMessage(PLANE_R_CHANGED, new Vector4f(planeR)); + } + + /** + * Sets the R coordinate plane equation. This plane equation + * is used to generate the R coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeR plane equation for the R coordinate + */ + final void initPlaneR(Vector4f planeR) { + this.planeR.set(planeR); + } + + /** + * Retrieves a copy of the plane equation used to + * generate the R coordinate. + * @param planeR the R coordinate plane equation + */ + final void getPlaneR(Vector4f planeR) { + planeR.set(this.planeR); + } + + /** + * Sets the Q coordinate plane equation. This plane equation + * is used to generate the Q coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeQ plane equation for the Q coordinate + */ + final void setPlaneQ(Vector4f planeQ) { + initPlaneQ(planeQ); + sendMessage(PLANE_Q_CHANGED, new Vector4f(planeQ)); + } + + /** + * Sets the Q coordinate plane equation. This plane equation + * is used to generate the Q coordinate in OBJECT_LINEAR and EYE_LINEAR + * texture generation modes. + * @param planeQ plane equation for the Q coordinate + */ + final void initPlaneQ(Vector4f planeQ) { + this.planeQ.set(planeQ); + } + + /** + * Retrieves a copy of the plane equation used to + * generate the Q coordinate. + * @param planeQ the Q coordinate plane equation + */ + final void getPlaneQ(Vector4f planeQ) { + planeQ.set(this.planeQ); + } + + + + /** + * Creates a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void createMirrorObject() { + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror= this; + } else { + TexCoordGenerationRetained mirrorTg = new TexCoordGenerationRetained(); + mirrorTg.set(this); + mirrorTg.source = source; + mirror = mirrorTg; + } + } else { + ((TexCoordGenerationRetained) mirror).set(this); + } + } + + /** + * These two methods update the native context, + * trans contains eyeTovworld transform in d3d + * trans contains vworldToEye transform in ogl + */ + native void updateNative(long ctx, + boolean enable, int genMode, int format, + float planeSx, float planeSy, float planeSz, float planeSw, + float planeTx, float planeTy, float planeTz, float planeTw, + float planeRx, float planeRy, float planeRz, float planeRw, + float planeQx, float planeQy, float planeQz, float planeQw, + double[] trans); + + + void updateNative(Canvas3D cv) { + int gMode = genMode; + Transform3D trans = null; + Transform3D m = cv.vworldToEc; + + if (((cv.textureExtendedFeatures & Canvas3D.TEXTURE_CUBE_MAP) == 0) && + ((genMode == TexCoordGeneration.NORMAL_MAP) || + (genMode == TexCoordGeneration.REFLECTION_MAP))) { + gMode = TexCoordGeneration.SPHERE_MAP; + } + + if (VirtualUniverse.mc.isD3D() && + (gMode == TexCoordGeneration.EYE_LINEAR)) { + trans = VirtualUniverse.mc.getTransform3D(cv.vworldToEc); + trans.invert(); + m = trans; + } + + updateNative(cv.ctx, + enable, gMode, format, planeS.x, planeS.y, planeS.z, + planeS.w, planeT.x, planeT.y, planeT.z, planeT.w, + planeR.x, planeR.y, planeR.z, planeR.w, + planeQ.x, planeQ.y, planeQ.z, planeQ.w, + m.mat); + + if (trans != null) { + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + trans); + } + } + + /** + * Initializes a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + ((TexCoordGenerationRetained)mirror).set(this); + } + + /** Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + + TexCoordGenerationRetained mirrorTc = (TexCoordGenerationRetained) mirror; + + mirrorTc.mirrorCompDirty = true; + + if ((component & ENABLE_CHANGED) != 0) { + mirrorTc.enable = ((Boolean)value).booleanValue(); + } + else if ((component & PLANE_S_CHANGED) != 0) { + mirrorTc.planeS = (Vector4f)value; + } + else if ((component & PLANE_T_CHANGED) != 0) { + mirrorTc.planeT = (Vector4f)value; + } + else if ((component & PLANE_R_CHANGED) != 0) { + mirrorTc.planeR = (Vector4f)value; + } + else if ((component & PLANE_Q_CHANGED) != 0) { + mirrorTc.planeQ = (Vector4f)value; + } + } + + + boolean equivalent(TexCoordGenerationRetained tr) { + + if (tr == null) { + return (false); + + } else if ((this.changedFrequent != 0) || (tr.changedFrequent != 0)) { + return (this == tr); + } + + return ((tr.genMode == genMode) && + (tr.format == format) && + (tr.enable == enable) && + tr.planeS.equals(planeS) && + tr.planeT.equals(planeT) && + tr.planeR.equals(planeR)); + } + + protected Object clone() { + TexCoordGenerationRetained tr = (TexCoordGenerationRetained)super.clone(); + tr.planeS = new Vector4f(planeS); + tr.planeT = new Vector4f(planeT); + tr.planeR = new Vector4f(planeR); + // other attributes is copied in super.clone() + return tr; + + } + + protected void set(TexCoordGenerationRetained tr) { + super.set(tr); + genMode = tr.genMode; + format = tr.format; + enable = tr.enable; + planeS.set(tr.planeS); + planeT.set(tr.planeT); + planeR.set(tr.planeR); + } + + final void sendMessage(int attrMask, Object attr) { + + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.TEXCOORDGENERATION_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.TEXCOORDGENERATION_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + } + + void handleFrequencyChange(int bit) { + switch (bit) { + case TexCoordGeneration.ALLOW_ENABLE_WRITE: + case TexCoordGeneration.ALLOW_PLANE_WRITE: { + setFrequencyChangeMask(bit, bit); + } + default: + break; + } + } +} diff --git a/src/classes/share/javax/media/j3d/Text3D.java b/src/classes/share/javax/media/j3d/Text3D.java new file mode 100644 index 0000000..5c3e4b6 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Text3D.java @@ -0,0 +1,619 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Point3f; + +/** + * A Text3D object is a text string that has been converted to 3D + * geometry. The Font3D object determines the appearance of the + * Text3D NodeComponent object. Each Text3D object has the following + * parameters:<P> + * <UL> + * <LI>Font3D object - describes the font style of the text string, + * such as the font family (Helvetica, Courier, etc.), style (Italic, + * bold, etc.), and point size. The size of the resulting characters will + * be equal to the point size. For example, a 12 point font will result in + * a Font3D with characters 12 meters tall. </LI><P> + * <LI>Text string - the text string to be written.</LI><P> + * <LI>Position - determines the initial placement of the Text3D string + * in three-space.</LI><P> + * <LI>Alignment - specifies how glyphs in the string are placed in + * relation to the position parameter. Valid values are: + * <UL> + * <LI> ALIGN_CENTER - the center of the string is placed on the + * <code>position</code> point.</LI> + * <LI> ALIGN_FIRST - the first character of the string is placed on + * the <code>position</code> point.</LI> + * <LI> ALIGN_LAST - the last character of the string is placed on the + * <code>position</code> point.</LI> + * </UL><P> + * <LI>Path - specifies how succeeding glyphs in the string are placed + * in relation to the previous glyph. Valid values are:</LI><P> + * <UL> + * <LI> PATH_LEFT - succeeding glyphs are placed to the left of the + * current glyph.</LI> + * <LI> PATH_RIGHT - succeeding glyphs are placed to the right of the + * current glyph.</LI> + * <LI> PATH_UP - succeeding glyphs are placed above the current glyph.</LI> + * <LI> PATH_DOWN - succeeding glyphs are placed below the current glyph.</LI> + * </UL><P> + * <LI>Character spacing - the space between characters. This spacing is + * in addition to the regular spacing between glyphs as defined in the + * Font object.</LI></UL><P> + * + * @see Font3D + */ +public class Text3D extends Geometry { + + /** + * Specifies that this Text3D object allows + * reading the Font3D component information. + * + * @see Font3D + */ + public static final int + ALLOW_FONT3D_READ = CapabilityBits.TEXT3D_ALLOW_FONT3D_READ; + + /** + * Specifies that this Text3D object allows + * writing the Font3D component information. + * + * @see Font3D + */ + public static final int + ALLOW_FONT3D_WRITE = CapabilityBits.TEXT3D_ALLOW_FONT3D_WRITE; + + /** + * Specifies that this Text3D object allows + * reading the String object. + */ + public static final int + ALLOW_STRING_READ = CapabilityBits.TEXT3D_ALLOW_STRING_READ; + + /** + * Specifies that this Text3D object allows + * writing the String object. + */ + public static final int + ALLOW_STRING_WRITE = CapabilityBits.TEXT3D_ALLOW_STRING_WRITE; + + /** + * Specifies that this Text3D object allows + * reading the text position value. + */ + public static final int + ALLOW_POSITION_READ = CapabilityBits.TEXT3D_ALLOW_POSITION_READ; + + /** + * Specifies that this Text3D object allows + * writing the text position value. + */ + public static final int + ALLOW_POSITION_WRITE = CapabilityBits.TEXT3D_ALLOW_POSITION_WRITE; + + /** + * Specifies that this Text3D object allows + * reading the text alignment value. + */ + public static final int + ALLOW_ALIGNMENT_READ = CapabilityBits.TEXT3D_ALLOW_ALIGNMENT_READ; + + /** + * Specifies that this Text3D object allows + * writing the text alignment value. + */ + public static final int + ALLOW_ALIGNMENT_WRITE = CapabilityBits.TEXT3D_ALLOW_ALIGNMENT_WRITE; + + /** + * Specifies that this Text3D object allows + * reading the text path value. + */ + public static final int + ALLOW_PATH_READ = CapabilityBits.TEXT3D_ALLOW_PATH_READ; + + /** + * Specifies that this Text3D object allows + * writing the text path value. + */ + public static final int + ALLOW_PATH_WRITE = CapabilityBits.TEXT3D_ALLOW_PATH_WRITE; + + /** + * Specifies that this Text3D object allows + * reading the text character spacing value. + */ + public static final int + ALLOW_CHARACTER_SPACING_READ = CapabilityBits.TEXT3D_ALLOW_CHARACTER_SPACING_READ; + + /** + * Specifies that this Text3D object allows + * writing the text character spacing value. + */ + public static final int + ALLOW_CHARACTER_SPACING_WRITE = CapabilityBits.TEXT3D_ALLOW_CHARACTER_SPACING_WRITE; + + /** + * Specifies that this Text3D object allows + * reading the text string bounding box value + */ + public static final int + ALLOW_BOUNDING_BOX_READ = CapabilityBits.TEXT3D_ALLOW_BOUNDING_BOX_READ; + + /** + * <code>alignment</code>: the center of the string is placed on the + * <code>position</code> point. + * + * @see #getAlignment + */ + public static final int ALIGN_CENTER = 0; + + /** + * <code>alignment</code>: the first character of the string is placed + * on the <code>position</code> point. + * + * @see #getAlignment + */ + public static final int ALIGN_FIRST = 1; + + /** + * <code>alignment</code>: the last character of the string is placed + * on the <code>position</code> point. + * + * @see #getAlignment + */ + public static final int ALIGN_LAST = 2; + + /** + * <code>path</code>: succeeding glyphs are placed to the left of + * the current glyph. + * + * @see #getPath + */ + public static final int PATH_LEFT = 0; + /** + * <code>path</code>: succeeding glyphs are placed to the left of + * the current glyph. + * + * @see #getPath + */ + public static final int PATH_RIGHT = 1; + + /** + * <code>path</code>: succeeding glyphs are placed above the + * current glyph. + * + * @see #getPath + */ + public static final int PATH_UP = 2; + + /** + * <code>path</code>: succeeding glyphs are placed below the + * current glyph. + * + * @see #getPath + */ + public static final int PATH_DOWN = 3; + + /** + * Constructs a Text3D object with default parameters. + * The default values are as follows: + * <ul> + * font 3D : null<br> + * string : null<br> + * position : (0,0,0)<br> + * alignment : ALIGN_FIRST<br> + * path : PATH_RIGHT<br> + * character spacing : 0.0<br> + * </ul> + */ + public Text3D() { + } + + /** + * Creates a Text3D object with the given Font3D object. + * + * @see Font3D + */ + public Text3D(Font3D font3D) { + ((Text3DRetained)this.retained).setFont3D(font3D); + } + + /** + * Creates a Text3D object given a Font3D object and a string. The + * string is converted into 3D glyphs. The first glyph from the + * string is placed at (0.0, 0.0, 0.0) and succeeding glyphs are + * placed to the right of the initial glyph. + * + * @see Font3D + */ + public Text3D(Font3D font3D, String string) { + ((Text3DRetained)this.retained).setFont3D(font3D); + ((Text3DRetained)this.retained).setString(string); + } + + /** + * Creates a Text3D object given a Font3D, a string and position. The + * string is converted into 3D glyphs. The first glyph from the + * string is placed at position <code>position</code> and succeeding + * glyphs are placed to the right of the initial glyph. + * + * @see Font3D + */ + public Text3D(Font3D font3D, String string, Point3f position) { + ((Text3DRetained)this.retained).setFont3D(font3D); + ((Text3DRetained)this.retained).setString(string); + ((Text3DRetained)this.retained).setPosition(position); + } + + /** + * Creates a Text3D object given a Font3D, string, position, alignment + * and path along which string is to be placed. The + * string is converted into 3D glyphs. The placement of the glyphs + * with respect to the <code>position</code> position depends on + * the alignment parameter and the path parameter. + * + * @see Font3D + */ + public Text3D(Font3D font3D, String string, Point3f position, + int alignment, int path) { + ((Text3DRetained)this.retained).setFont3D(font3D); + ((Text3DRetained)this.retained).setString(string); + ((Text3DRetained)this.retained).setPosition(position); + ((Text3DRetained)this.retained).setAlignment(alignment); + ((Text3DRetained)this.retained).setPath(path); + } + + /** + * Creates the retained mode Text3DRetained object that this + * Text3D component object will point to. + */ + void createRetained() { + this.retained = new Text3DRetained(); + this.retained.setSource(this); + } + + + /** + * Returns the Font3D objects used by this Text3D NodeComponent object. + * + * @return the Font3D object of this Text3D node - null if no Font3D + * has been associated with this node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Font3D getFont3D() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_FONT3D_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D0")); + return ((Text3DRetained)this.retained).getFont3D(); + + } + + /** + * Sets the Font3D object used by this Text3D NodeComponent object. + * + * @param font3d the Font3D object to associate with this Text3D node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setFont3D(Font3D font3d) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_FONT3D_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D1")); + ((Text3DRetained)this.retained).setFont3D(font3d); + + } + + /** + * Copies the character string used in the construction of the + * Text3D node into the supplied parameter. + * + * @return a copy of the String object in this Text3D node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public String getString() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_STRING_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D2")); + return ((Text3DRetained)this.retained).getString(); + } + + /** + * Copies the character string from the supplied parameter into the + * Text3D node. + * + * @param string the String object to recieve the Text3D node's string. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setString(String string) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_STRING_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D3")); + ((Text3DRetained)this.retained).setString(string); + } + + /** + * Copies the node's <code>position</code> field into the supplied + * parameter. The <code>position</code> is used to determine the + * initial placement of the Text3D string. The position, combined with + * the path and alignment control how the text is displayed. + * + * @param position the point to position the text. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #getAlignment + * @see #getPath + */ + public void getPosition(Point3f position) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D4")); + ((Text3DRetained)this.retained).getPosition(position); + } + + /** + * Sets the node's <code>position</code> field to the supplied + * parameter. The <code>position</code> is used to determine the + * initial placement of the Text3D string. The position, combined with + * the path and alignment control how the text is displayed. + * + * @param position the point to position the text. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #getAlignment + * @see #getPath + */ + public void setPosition(Point3f position) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POSITION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D5")); + ((Text3DRetained)this.retained).setPosition(position); + } + + /** + * Retrieves the text alignment policy for this Text3D NodeComponent + * object. The <code>alignment</code> is used to specify how + * glyphs in the string are placed in relation to the + * <code>position</code> field. Valid values for this field + * are: + * <UL> + * <LI> ALIGN_CENTER - the center of the string is placed on the + * <code>position</code> point. + * <LI> ALIGN_FIRST - the first character of the string is placed on + * the <code>position</code> point. + * <LI> ALIGN_LAST - the last character of the string is placed on the + * <code>position</code> point. + * </UL> + * The default value of this field is <code>ALIGN_FIRST</code>. + * + * @return the current alingment policy for this node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #getPosition + */ + public int getAlignment() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ALIGNMENT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D6")); + return ((Text3DRetained)this.retained).getAlignment(); + } + + /** + * Sets the text alignment policy for this Text3D NodeComponent + * object. The <code>alignment</code> is used to specify how + * glyphs in the string are placed in relation to the + * <code>position</code> field. Valid values for this field + * are: + * <UL> + * <LI> ALIGN_CENTER - the center of the string is placed on the + * <code>position</code> point. + * <LI> ALIGN_FIRST - the first character of the string is placed on + * the <code>position</code> point. + * <LI> ALIGN_LAST - the last character of the string is placed on the + * <code>position</code> point. + * </UL> + * The default value of this field is <code>ALIGN_FIRST</code>. + * + * @param alignment specifies how glyphs in the string are placed + * in relation to the position field + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #getPosition + */ + public void setAlignment(int alignment) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_ALIGNMENT_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D7")); + ((Text3DRetained)this.retained).setAlignment(alignment); + } + + /** + * Retrieves the node's <code>path</code> field. This field + * is used to specify how succeeding + * glyphs in the string are placed in relation to the previous glyph. + * Valid values for this field are: + * <UL> + * <LI> PATH_LEFT: - succeeding glyphs are placed to the left of the + * current glyph. + * <LI> PATH_RIGHT: - succeeding glyphs are placed to the right of the + * current glyph. + * <LI> PATH_UP: - succeeding glyphs are placed above the current glyph. + * <LI> PATH_DOWN: - succeeding glyphs are placed below the current glyph. + * </UL> + * The default value of this field is <code>PATH_RIGHT</code>. + * + * @return the current alingment policy for this node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getPath() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PATH_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D8")); + return ((Text3DRetained)this.retained).getPath(); + } + + /** + * Sets the node's <code>path</code> field. This field + * is used to specify how succeeding + * glyphs in the string are placed in relation to the previous glyph. + * Valid values for this field are: + * <UL> + * <LI> PATH_LEFT - succeeding glyphs are placed to the left of the + * current glyph. + * <LI> PATH_RIGHT - succeeding glyphs are placed to the right of the + * current glyph. + * <LI> PATH_UP - succeeding glyphs are placed above the current glyph. + * <LI> PATH_DOWN - succeeding glyphs are placed below the current glyph. + * </UL> + * The default value of this field is <code>PATH_RIGHT</code>. + * + * @param path the value to set the path to + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setPath(int path) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_PATH_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D9")); + ((Text3DRetained)this.retained).setPath(path); + } + + /** + * Retrieves the 3D bounding box that encloses this Text3D object. + * + * @param bounds the object to copy the bounding information to. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see BoundingBox + */ + public void getBoundingBox(BoundingBox bounds) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_BOUNDING_BOX_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D10")); + ((Text3DRetained)this.retained).getBoundingBox(bounds); + } + + /** + * Retrieves the character spacing used to construct the Text3D string. + * This spacing is in addition to the regular spacing between glyphs as + * defined in the Font object. 1.0 in this space is measured as the + * width of the largest glyph in the 2D Font. The default value is + * 0.0. + * + * @return the current character spacing value + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getCharacterSpacing() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHARACTER_SPACING_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D11")); + return ((Text3DRetained)this.retained).getCharacterSpacing(); + } + + /** + * Sets the character spacing used when constructing the Text3D string. + * This spacing is in addition to the regular spacing between glyphs as + * defined in the Font object. 1.0 in this space is measured as the + * width of the largest glyph in the 2D Font. The default value is + * 0.0. + * + * @param characterSpacing the new character spacing value + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setCharacterSpacing(float characterSpacing) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_CHARACTER_SPACING_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Text3D12")); + ((Text3DRetained)this.retained).setCharacterSpacing(characterSpacing); + } + + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + Text3D t = new Text3D(); + t.duplicateNodeComponent(this); + return t; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + Text3DRetained text = (Text3DRetained) originalNodeComponent.retained; + Text3DRetained rt = (Text3DRetained) retained; + + Font3D font3D = text.getFont3D(); + if (font3D != null) { + rt.setFont3D(font3D); + } + + String s = text.getString(); + if (s != null) { + rt.setString(s); + } + + Point3f p = new Point3f(); + text.getPosition(p); + rt.setPosition(p); + rt.setAlignment(text.getAlignment()); + rt.setPath(text.getPath()); + rt.setCharacterSpacing(text.getCharacterSpacing()); + } + +} diff --git a/src/classes/share/javax/media/j3d/Text3DRenderMethod.java b/src/classes/share/javax/media/j3d/Text3DRenderMethod.java new file mode 100644 index 0000000..8ff96a3 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Text3DRenderMethod.java @@ -0,0 +1,109 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The RenderMethod interface is used to create various ways to render + * different geometries. + */ + +class Text3DRenderMethod implements RenderMethod { + + /** + * The actual rendering code for this RenderMethod + */ + public boolean render(RenderMolecule rm, Canvas3D cv, int pass, + RenderAtomListInfo ra, int dirtyBits) { + boolean isNonUniformScale; + Transform3D trans = null; + + GeometryArrayRetained geo = (GeometryArrayRetained)ra.geometry(); + geo.setVertexFormat((rm.useAlpha && ((geo.vertexFormat & + GeometryArray.COLOR) != 0)), + rm.textureBin.attributeBin.ignoreVertexColors, cv.ctx); + + if (rm.doInfinite) { + cv.updateState(pass, dirtyBits); + while (ra != null) { + trans = ra.infLocalToVworld; + isNonUniformScale = !trans.isCongruent(); + cv.setModelViewMatrix(cv.ctx, cv.vworldToEc.mat, trans); + + ra.geometry().execute(cv, ra.renderAtom, isNonUniformScale, + (rm.useAlpha && ra.geometry().noAlpha), + rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin.ignoreVertexColors, + pass); + ra = ra.next; + } + return true; + } + + boolean isVisible = false; // True if any of the RAs is visible. + while (ra != null) { + if (cv.ra == ra.renderAtom) { + if (cv.raIsVisible) { + cv.updateState(pass, dirtyBits); + trans = ra.localToVworld; + isNonUniformScale = !trans.isCongruent(); + + cv.setModelViewMatrix(cv.ctx, cv.vworldToEc.mat, trans); + ra.geometry().execute(cv, ra.renderAtom, isNonUniformScale, + (rm.useAlpha && ra.geometry().noAlpha), + rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin. + ignoreVertexColors, + pass); + isVisible = true; + } + } + else { + if (ra.renderAtom.localeVwcBounds.intersect(cv.viewFrustum)) { + cv.updateState(pass, dirtyBits); + cv.raIsVisible = true; + trans = ra.localToVworld; + isNonUniformScale = !trans.isCongruent(); + + cv.setModelViewMatrix(cv.ctx, cv.vworldToEc.mat, trans); + ra.geometry().execute(cv, ra.renderAtom, isNonUniformScale, + (rm.useAlpha && ra.geometry().noAlpha), + rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin. + ignoreVertexColors, + pass); + isVisible = true; + } + else { + cv.raIsVisible = false; + } + cv.ra = ra.renderAtom; + } + + ra = ra.next; + + } + + geo.disableGlobalAlpha(cv.ctx, + (rm.useAlpha && ((geo.vertexFormat & + GeometryArray.COLOR) != 0)), + rm.textureBin.attributeBin.ignoreVertexColors); + + return isVisible; + } +} diff --git a/src/classes/share/javax/media/j3d/Text3DRetained.java b/src/classes/share/javax/media/j3d/Text3DRetained.java new file mode 100644 index 0000000..81631b6 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Text3DRetained.java @@ -0,0 +1,985 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.awt.font.*; +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; + +/** + * Implements Text3D class. + */ +class Text3DRetained extends GeometryRetained { + /** + * Packaged scope variables needed for implementation + */ + Font3D font3D = null; + String string = null; + Point3f position = new Point3f(0.0f, 0.0f, 0.0f); + int alignment = Text3D.ALIGN_FIRST, path = Text3D.PATH_RIGHT; + float charSpacing = 0.0f; + int numChars = 0; + static final int targetThreads = (J3dThread.UPDATE_TRANSFORM | + J3dThread.UPDATE_GEOMETRY | + J3dThread.UPDATE_RENDER); + /** + * The temporary transforms for this Text3D + */ + Transform3D[] charTransforms = new Transform3D[0]; + + /** + * A cached list of geometry arrays for the current settings + */ + GeometryArrayRetained[] geometryList = new GeometryArrayRetained[0]; + GlyphVector[] glyphVecs = new GlyphVector[0]; + + /** + * Bounding box data for this text string. + */ + Point3d lower = new Point3d(); + Point3d upper = new Point3d(); + + + /** + * An Array list used for messages + */ + ArrayList newGeometryAtomList = new ArrayList(); + ArrayList oldGeometryAtomList = new ArrayList(); + + + /** + * temporary model view matrix for immediate mode only + */ + Transform3D vpcToEc; + Transform3D drawTransform; + + + Text3DRetained(){ + this.geoType = GEO_TYPE_TEXT3D; + } + + + synchronized void computeBoundingBox() { + Point3d l = new Point3d(); + Point3d u = new Point3d(); + Vector3f location = new Vector3f(this.position); + int i, k=0, numTotal=0; + double width = 0, height = 0; + Rectangle2D bounds; + + //Reset bounds data + l.set(location); + u.set(location); + + if (numChars != 0) { + // Set loop counters based on path type + if (path == Text3D.PATH_RIGHT || path == Text3D.PATH_UP) { + k = 0; + numTotal = numChars + 1; + } else if (path == Text3D.PATH_LEFT || path == Text3D.PATH_DOWN) { + k = 1; + numTotal = numChars; + // Reset bounds to bounding box if first character + bounds = glyphVecs[0].getVisualBounds(); + u.x += bounds.getWidth(); + u.y += bounds.getHeight(); + } + + for (i=1; i<numTotal; i++, k++) { + width = glyphVecs[k].getLogicalBounds().getWidth(); + bounds = glyphVecs[k].getVisualBounds(); + // 'switch' could be outside loop with little hacking, + width += charSpacing; + height = bounds.getHeight(); + + switch (this.path) { + case Text3D.PATH_RIGHT: + u.x += (width); + if (u.y < (height + location.y)) { + u.y = location.y + height; + } + break; + case Text3D.PATH_LEFT: + l.x -= (width); + if (u.y < ( height + location.y)) { + u.y = location.y + height; + } + break; + case Text3D.PATH_UP: + u.y += height; + if (u.x < (bounds.getWidth() + location.x)) { + u.x = location.x + bounds.getWidth(); + } + break; + case Text3D.PATH_DOWN: + l.y -= height; + if (u.x < (bounds.getWidth() + location.x)) { + u.x = location.x + bounds.getWidth(); + } + break; + } + } + + // Handle string alignment. ALIGN_FIRST is handled by default + if (alignment != Text3D.ALIGN_FIRST) { + double cx = (u.x - l.x); + double cy = (u.y - l.y); + + if (alignment == Text3D.ALIGN_CENTER) { + cx *= .5; + cy *= .5; + } + switch (path) { + case Text3D.PATH_RIGHT: + l.x -= cx; + u.x -= cx; + break; + case Text3D.PATH_LEFT: + l.x += cx; + u.x += cx; + break; + case Text3D.PATH_UP: + l.y -= cy; + u.y -= cy; + break; + case Text3D.PATH_DOWN: + l.y += cy; + u.y += cy; + break; + + } + } + } + + l.z = 0.0f; + if ((font3D == null) || (font3D.fontExtrusion == null)) { + u.z = l.z; + } else { + u.z = l.z + font3D.fontExtrusion.length; + } + } + + void update() {} + + + /** + * Returns the Font3D objects used by this Text3D NodeComponent object. + * + * @return the Font3D object of this Text3D node - null if no Font3D + * has been associated with this node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + final Font3D getFont3D() { + return this.font3D; + } + + /** + * Sets the Font3D object used by this Text3D NodeComponent object. + * + * @param font3d the Font3D object to associate with this Text3D node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + final void setFont3D(Font3D font3d) { + geomLock.getLock(); + this.font3D = font3d; + updateCharacterData(); + geomLock.unLock(); + sendDataChangedMessage(); + } + + /** + * Copies the character string used in the construction of the + * Text3D node into the supplied parameter. + * + * @return a copy of the String object in this Text3D node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + final String getString() { + return this.string; + } + + /** + * Copies the character string from the supplied parameter into Tex3D + * node. + * + * @param string the String object to recieve the Text3D node's string. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + final void setString(String string) { + geomLock.getLock(); + this.string = string; + if (string == null) { + numChars = 0; + } else { + numChars = string.length(); + } + updateCharacterData(); + geomLock.unLock(); + sendDataChangedMessage(); + } + + /** + * Copies the node's <code>position</code> field into the supplied + * parameter. The <code>position</code> is used to determine the + * initial placement of the Text3D string. The position, combined with + * the path and alignment control how the text is displayed. + * + * @param position the point to position the text. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #getAlignment + * @see #getPath + */ + final void getPosition(Point3f position) { + position.set(this.position); + } + + /** + * Sets the node's <code>position</code> field to the supplied + * parameter. The <code>position</code> is used to determine the + * initial placement of the Text3D string. The position, combined with + * the path and alignment control how the text is displayed. + * + * @param position the point to position the text. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #getAlignment + * @see #getPath + */ + final void setPosition(Point3f position) { + geomLock.getLock(); + this.position.set(position); + updateTransformData(); + geomLock.unLock(); + sendTransformChangedMessage(); + } + + /** + * Retrieves the text alignment policy for this Text3D NodeComponent + * object. The <code>alignment</code> is used to specify how + * glyphs in the string are placed in relation to the + * <code>position</code> field. Valid values for this field + * are: + * <UL> + * <LI> ALIGN_CENTER - the center of the string is placed on the + * <code>position</code> point. + * <LI> ALIGN_FIRST - the first character of the string is placed on + * the <code>position</code> point. + * <LI> ALIGN_LAST - the last character of the string is placed on the + * <code>position</code> point. + * </UL> + * The default value of this field is <code>ALIGN_FIRST</code>. + * + * @return the current alingment policy for this node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #getPosition + */ + final int getAlignment() { + return alignment; + } + + /** + * Sets the text alignment policy for this Text3D NodeComponent + * object. The <code>alignment</code> is used to specify how + * glyphs in the string are placed in relation to the + * <code>position</code> field. Valid values for this field + * are: + * <UL> + * <LI> ALIGN_CENTER - the center of the string is placed on the + * <code>position</code> point. + * <LI> ALIGN_FIRST - the first character of the string is placed on + * the <code>position</code> point. + * <LI> ALIGN_LAST - the last character of the string is placed on the + * <code>position</code> point. + * </UL> + * The default value of this field is <code>ALIGN_FIRST</code>. + * + * @return the current alingment policy for this node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see #getPosition + */ + final void setAlignment(int alignment) { + geomLock.getLock(); + this.alignment = alignment; + updateTransformData(); + geomLock.unLock(); + sendTransformChangedMessage(); + } + + /** + * Retrieves the node's <code>path</code> field. This field + * is used to specify how succeeding + * glyphs in the string are placed in relation to the previous glyph. + * Valid values for this field are: + * <UL> + * <LI> PATH_LEFT: - succeeding glyphs are placed to the left of the + * current glyph. + * <LI> PATH_RIGHT: - succeeding glyphs are placed to the right of the + * current glyph. + * <LI> PATH_UP: - succeeding glyphs are placed above the current glyph. + * <LI> PATH_DOWN: - succeeding glyphs are placed below the current glyph. + * </UL> + * The default value of this field is <code>PATH_RIGHT</code>. + * + * @return the current alingment policy for this node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + final int getPath() { + return this.path; + } + + /** + * Sets the node's <code>path</code> field. This field + * is used to specify how succeeding + * glyphs in the string are placed in relation to the previous glyph. + * Valid values for this field are: + * <UL> + * <LI> PATH_LEFT - succeeding glyphs are placed to the left of the + * current glyph. + * <LI> PATH_RIGHT - succeeding glyphs are placed to the right of the + * current glyph. + * <LI> PATH_UP - succeeding glyphs are placed above the current glyph. + * <LI> PATH_DOWN - succeeding glyphs are placed below the current glyph. + * </UL> + * The default value of this field is <code>PATH_RIGHT</code>. + * + * @param path the value to set the path to. + * + * @return the current alingment policy for this node. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + final void setPath(int path) { + this.path = path; + updateTransformData(); + sendTransformChangedMessage(); + } + + /** + * Retrieves the 3D bounding box that encloses this Text3D object. + * + * @param bounds the object to copy the bounding information to. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see BoundingBox + */ + final void getBoundingBox(BoundingBox bounds) { + synchronized (this) { + bounds.setLower(lower); + bounds.setUpper(upper); + } + } + + /** + * Retrieves the character spacing used to construct the Text3D string. + * This spacing is in addition to the regular spacing between glyphs as + * defined in the Font object. 1.0 in this space is measured as the + * width of the largest glyph in the 2D Font. The default value is + * 0.0. + * + * @return the current character spacing value + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + final float getCharacterSpacing() { + return charSpacing; + } + + /** + * Sets the character spacing used hwne constructing the Text3D string. + * This spacing is in addition to the regular spacing between glyphs as + * defined in the Font object. 1.0 in this space is measured as the + * width of the largest glyph in the 2D Font. The default value is + * 0.0. + * + * @param characterSpacing the new character spacing value + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + final void setCharacterSpacing(float characterSpacing) { + geomLock.getLock(); + this.charSpacing = characterSpacing; + updateTransformData(); + geomLock.unLock(); + sendTransformChangedMessage(); + } + + + final void sendDataChangedMessage() { + J3dMessage[] m; + int i, j, k, kk, numMessages; + int gSize; + ArrayList shapeList, gaList; + Shape3DRetained s; + GeometryAtom[] newGeometryAtoms; + ArrayList tiArrList = new ArrayList(); + ArrayList newCtArrArrList = new ArrayList(); + + synchronized(liveStateLock) { + if (source.isLive()) { + synchronized (universeList) { + numMessages = universeList.size(); + m = new J3dMessage[numMessages]; + for (i=0; i<numMessages; i++) { + m[i] = VirtualUniverse.mc.getMessage(); + m[i].type = J3dMessage.TEXT3D_DATA_CHANGED; + m[i].threads = targetThreads; + shapeList = (ArrayList)userLists.get(i); + newGeometryAtomList.clear(); + oldGeometryAtomList.clear(); + + for (j=0; j<shapeList.size(); j++) { + s = (Shape3DRetained)shapeList.get(j); + if (s.boundsAutoCompute) { + // update combine bounds of mirrorShape3Ds. So we need to + // use its bounds and not localBounds. + // bounds is actually a reference to + // mirrorShape3D.source.localBounds. + // TODO : Should only need to update distinct localBounds. + s.getCombineBounds((BoundingBox)s.bounds); + } + + gSize = s.geometryList.size(); + + GeometryAtom oldGA = Shape3DRetained.getGeomAtom(s); + GeometryAtom newGA = new GeometryAtom(); + + int geometryCnt = 0; + for(k = 0; k<gSize; k++) { + GeometryRetained geomRetained = + (GeometryRetained) s.geometryList.get(k); + if(geomRetained != null) { + Text3DRetained tempT3d = (Text3DRetained)geomRetained; + geometryCnt += tempT3d.numChars; + } + else { + // Slightly wasteful, but not quite worth to optimize yet. + geometryCnt++; + } + } + + newGA.geometryArray = new GeometryRetained[geometryCnt]; + newGA.lastLocalTransformArray = new Transform3D[geometryCnt]; + // Reset geometryCnt; + geometryCnt = 0; + + newGA.locale = s.locale; + newGA.visible = s.visible; + newGA.source = s; + int gaCnt=0; + GeometryRetained geometry = null; + for(; gaCnt<gSize; gaCnt++) { + geometry = (GeometryRetained) s.geometryList.get(gaCnt); + if(geometry != null) { + newGA.geoType = geometry.geoType; + newGA.alphaEditable = s.isAlphaEditable(geometry); + break; + } + } + + for(; gaCnt<gSize; gaCnt++) { + geometry = (GeometryRetained) s.geometryList.get(gaCnt); + if(geometry == null) { + newGA.geometryArray[gaCnt] = null; + } + else { + Text3DRetained t = (Text3DRetained)geometry; + GeometryRetained geo; + for (k=0; k<t.numChars; k++, geometryCnt++) { + geo = t.geometryList[k]; + if (geo != null) { + newGA.geometryArray[geometryCnt] = geo; + newGA.lastLocalTransformArray[geometryCnt] = + t.charTransforms[k]; + + } else { + newGA.geometryArray[geometryCnt] = null; + newGA.lastLocalTransformArray[geometryCnt] = null; + } + + } + + } + } + + oldGeometryAtomList.add(oldGA); + newGeometryAtomList.add(newGA); + Shape3DRetained.setGeomAtom(s, newGA); + } + + Object[] oldGAArray = oldGeometryAtomList.toArray(); + Object[] newGAArray = newGeometryAtomList.toArray(); + ArrayList uniqueList = getUniqueSource(shapeList); + int numSrc = uniqueList.size(); + int numMS3D; + Shape3DRetained ms, src; + + for (j=0; j<numSrc; j++) { + CachedTargets[] newCtArr = null; + src = (Shape3DRetained)uniqueList.get(j); + numMS3D = src.mirrorShape3D.size(); + + TargetsInterface ti = ((GroupRetained)src. + parent).getClosestTargetsInterface( + TargetsInterface.TRANSFORM_TARGETS); + + if (ti != null) { + CachedTargets ct; + newCtArr = new CachedTargets[numMS3D]; + + for (k=0; k<numMS3D; k++) { + ms = (Shape3DRetained)src.mirrorShape3D.get(k); + + GeometryAtom ga = + Shape3DRetained.getGeomAtom(ms); + for(kk=0; kk<newGAArray.length; kk++) { + if(ga == newGAArray[kk]) { + break; + } + } + + if(kk==newGAArray.length) { + System.out.println("Text3DRetained : Problem !!! Can't find matching geomAtom"); + } + + ct = ti.getCachedTargets(TargetsInterface. + TRANSFORM_TARGETS, k, -1); + if (ct != null) { + newCtArr[k] = new CachedTargets(); + newCtArr[k].copy(ct); + newCtArr[k].replace((NnuId)oldGAArray[kk], + (NnuId)newGAArray[kk], + Targets.GEO_TARGETS); + } else { + newCtArr[k] = null; + } + + } + + ti.resetCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, newCtArr, -1); + + tiArrList.add(ti); + newCtArrArrList.add(newCtArr); + + } + + } + + m[i].args[0] = oldGAArray; + m[i].args[1] = newGAArray; + m[i].universe = (VirtualUniverse)universeList.get(i); + + if(tiArrList.size() > 0) { + m[i].args[2] = tiArrList.toArray(); + m[i].args[3] = newCtArrArrList.toArray(); + } + + tiArrList.clear(); + newCtArrArrList.clear(); + + } + VirtualUniverse.mc.processMessage(m); + } + + } + } + } + + + final void sendTransformChangedMessage() { + J3dMessage[] m; + int i, j, numMessages, sCnt; + ArrayList shapeList; + ArrayList gaList = new ArrayList(); + Shape3DRetained s; + GeometryRetained geomR; + synchronized(liveStateLock) { + if (source.isLive()) { + synchronized (universeList) { + numMessages = universeList.size(); + m = new J3dMessage[numMessages]; + for (i=0; i<numMessages; i++) { + m[i] = VirtualUniverse.mc.getMessage(); + m[i].type = J3dMessage.TEXT3D_TRANSFORM_CHANGED; + m[i].threads = targetThreads; + shapeList = (ArrayList)userLists.get(i); + // gaList = new GeometryAtom[shapeList.size() * numChars]; + for (j=0; j<shapeList.size(); j++) { + s = (Shape3DRetained)shapeList.get(j); + + // Find the right geometry. + for(sCnt=0; sCnt<s.geometryList.size(); sCnt++) { + geomR = (GeometryRetained) s.geometryList.get(sCnt); + if(geomR == this) { + break; + } + } + + if(sCnt < s.geometryList.size()) + gaList.add(Shape3DRetained.getGeomAtom(s)); + + } + m[i].args[0] = gaList.toArray(); + m[i].args[1] = charTransforms; + m[i].universe = (VirtualUniverse)universeList.get(i); + } + VirtualUniverse.mc.processMessage(m); + } + } + } + } + + /** + * Update internal reprsentation of tranform matrices and geometry. + * This method will be called whenever string or font3D change. + */ + final void updateCharacterData() { + char c[] = new char[1]; + + if (geometryList.length != numChars) { + geometryList = new GeometryArrayRetained[numChars]; + glyphVecs = new GlyphVector[numChars]; + } + + if (font3D != null) { + for (int i=0; i<numChars; i++) { + c[0] = string.charAt(i); + glyphVecs[i] = font3D.font.createGlyphVector(font3D.frc, c); + geometryList[i] = font3D.triangulateGlyphs(glyphVecs[i], c[0]); + } + } + + updateTransformData(); + } + + /** + * Update per character transform based on Text3D location, + * per character size and path. + * + * WARNING: Caller of this method must make sure SceneGraph is live, + * else exceptions may be thrown. + */ + final void updateTransformData(){ + int i, k=0, numTotal=0; + double width = 0, height = 0; + Vector3f location = new Vector3f(this.position); + Rectangle2D bounds; + + //Reset bounds data + lower.set(location); + upper.set(location); + + charTransforms = new Transform3D[numChars]; + for (i=0; i<numChars; i++) { + charTransforms[i] = VirtualUniverse.mc.getTransform3D(null); + } + + if (numChars != 0) { + charTransforms[0].set(location); + + // Set loop counters based on path type + if (path == Text3D.PATH_RIGHT || path == Text3D.PATH_UP) { + k = 0; + numTotal = numChars + 1; + } else if (path == Text3D.PATH_LEFT || path == Text3D.PATH_DOWN) { + k = 1; + numTotal = numChars; + // Reset bounds to bounding box if first character + bounds = glyphVecs[0].getVisualBounds(); + upper.x += bounds.getWidth(); + upper.y += bounds.getHeight(); + } + + for (i=1; i<numTotal; i++, k++) { + width = glyphVecs[k].getLogicalBounds().getWidth(); + bounds = glyphVecs[k].getVisualBounds(); + // 'switch' could be outside loop with little hacking, + width += charSpacing; + height = bounds.getHeight(); + + switch (this.path) { + case Text3D.PATH_RIGHT: + location.x += width; + upper.x += (width); + if (upper.y < (height + location.y)) { + upper.y = location.y + height; + } + break; + case Text3D.PATH_LEFT: + location.x -= width; + lower.x -= (width); + if (upper.y < ( height + location.y)) { + upper.y = location.y + height; + } + break; + case Text3D.PATH_UP: + location.y += height; + upper.y += height; + if (upper.x < (bounds.getWidth() + location.x)) { + upper.x = location.x + bounds.getWidth(); + } + break; + case Text3D.PATH_DOWN: + location.y -= height; + lower.y -= height; + if (upper.x < (bounds.getWidth() + location.x)) { + upper.x = location.x + bounds.getWidth(); + } + break; + } + if (i < numChars) { + charTransforms[i].set(location); + } + } + + // Handle string alignment. ALIGN_FIRST is handled by default + if (alignment != Text3D.ALIGN_FIRST) { + double cx = (upper.x - lower.x); + double cy = (upper.y - lower.y); + + if (alignment == Text3D.ALIGN_CENTER) { + cx *= .5; + cy *= .5; + } + switch (path) { + case Text3D.PATH_RIGHT: + for (i=0;i < numChars;i++) { + charTransforms[i].mat[3] -= cx; + } + lower.x -= cx; + upper.x -= cx; + break; + case Text3D.PATH_LEFT: + for (i=0;i < numChars;i++) { + charTransforms[i].mat[3] += cx; + } + lower.x += cx; + upper.x += cx; + break; + + case Text3D.PATH_UP: + for (i=0;i < numChars;i++) { + charTransforms[i].mat[7] -=cy; + } + lower.y -= cy; + upper.y -= cy; + break; + case Text3D.PATH_DOWN: + for (i=0;i < numChars;i++) { + charTransforms[i].mat[7] +=cy; + } + lower.y += cy; + upper.y += cy; + break; + + } + } + } + + lower.z = 0.0f; + if ((font3D == null) || (font3D.fontExtrusion == null)) { + upper.z = lower.z; + } else { + upper.z = lower.z + font3D.fontExtrusion.length; + } + + // update geoBounds + getBoundingBox(geoBounds); + } + + + /** + * This method is called when the SceneGraph becomes live. All characters + * used by this.string are tesselated in this method, to avoid wait during + * traversal and rendering. + */ + void setLive(boolean inBackgroundGroup, int refCount) { + // Tesselate all character data and update character transforms + updateCharacterData(); + super.doSetLive(inBackgroundGroup, refCount); + super.markAsLive(); + } + + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Transform3D tempT3D = VirtualUniverse.mc.getTransform3D(null); + GeometryArrayRetained geo = null; + int sIndex = -1; + PickShape newPS; + double x = 0, y = 0, z = 0; + double minDist = Double.MAX_VALUE; + + for (int i=0; i < numChars; i++) { + geo= geometryList[i]; + if (geo != null) { + tempT3D.invert(charTransforms[i]); + newPS = pickShape.transform(tempT3D); + if (geo.intersect(newPS, dist, iPnt)) { + if (dist == null) { + return true; + } + if (dist[0] < minDist) { + sIndex = i; + minDist = dist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + } + + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, tempT3D); + + if (sIndex >= 0) { + // We need to transform iPnt to the vworld to compute the actual distance. + // In this method we'll transform iPnt by its char. offset. Shape3D will + // do the localToVworld transform. + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + dist[0] = minDist; + charTransforms[sIndex].transform(iPnt); + return true; + } + return false; + } + + boolean intersect(Point3d[] pnts) { + Transform3D tempT3D = VirtualUniverse.mc.getTransform3D(null); + GeometryArrayRetained ga; + boolean isIntersect = false; + Point3d transPnts[] = new Point3d[pnts.length]; + for (int j=pnts.length-1; j >= 0; j--) { + transPnts[j] = new Point3d(); + } + + for (int i=numChars-1; i >= 0; i--) { + ga = geometryList[i]; + if ( ga != null) { + tempT3D.invert(charTransforms[i]); + for (int j=pnts.length-1; j >= 0; j--) { + tempT3D.transform(pnts[j], transPnts[j]); + } + if (ga.intersect(transPnts)) { + isIntersect = true; + break; + } + } + } + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, tempT3D); + return isIntersect; + } + + + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) { + GeometryArrayRetained ga; + + for (int i=numChars-1; i >=0; i--) { + ga = geometryList[i]; + if ((ga != null) && ga.intersect(thisToOtherVworld, geom)) { + return true; + } + } + + return false; + } + + boolean intersect(Bounds targetBound) { + GeometryArrayRetained ga; + + for (int i=numChars-1; i >=0; i--) { + ga = geometryList[i]; + if ((ga != null) && ga.intersect(targetBound)) { + return true; + } + } + + return false; + + } + + void setModelViewMatrix(Transform3D vpcToEc, Transform3D drawTransform) { + this.vpcToEc = vpcToEc; + this.drawTransform = drawTransform; + } + + + void execute(Canvas3D cv, RenderAtom ra, boolean isNonUniformScale, + boolean updateAlpha, float alpha, boolean multiScreen, + int screen, + boolean ignoreVertexColors, int pass) { + + Transform3D trans = VirtualUniverse.mc.getTransform3D(null); + + for (int i = 0; i < geometryList.length; i++) { + trans.set(drawTransform); + trans.mul(charTransforms[i]); + cv.setModelViewMatrix(cv.ctx, vpcToEc.mat, trans); + geometryList[i].execute(cv, ra, isNonUniformScale, updateAlpha, alpha, + multiScreen, screen, ignoreVertexColors, + pass); + } + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, trans); + } + + int getClassType() { + return TEXT3D_TYPE; + } + + + ArrayList getUniqueSource(ArrayList shapeList) { + ArrayList uniqueList = new ArrayList(); + int size = shapeList.size(); + Object src; + int i, index; + + for (i=0; i<size; i++) { + src = ((Shape3DRetained)shapeList.get(i)).sourceNode; + index = uniqueList.indexOf(src); + if (index == -1) { + uniqueList.add(src); + } + } + return uniqueList; + } +} + diff --git a/src/classes/share/javax/media/j3d/Texture.java b/src/classes/share/javax/media/j3d/Texture.java new file mode 100644 index 0000000..12b7e9c --- /dev/null +++ b/src/classes/share/javax/media/j3d/Texture.java @@ -0,0 +1,1749 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.Hashtable; + +/** + * The Texture object is a component object of an Appearance object + * that defines the texture properties used when texture mapping is + * enabled. The Texture object is an abstract class and all texture + * objects must be created as either a Texture2D object or a + * Texture3D object. + * <P> + * Each Texture object has the following properties:<P> + * <UL> + * <LI>Boundary color - the texture boundary color. The texture + * boundary color is used when the boundaryModeS and boundaryModeT + * parameters are set to CLAMP or CLAMP_TO_BOUNDARY and if the texture + * boundary is not specified. </LI><P> + * <LI>Boundary Width - the texture boundary width. If the texture boundary + * width is > 0, then all images for all mipmap levels will include boundary + * texels. The actual texture image for level 0, for example, will be of + * dimension (width + 2*boundaryWidth) * (height + 2*boundaryWidth). + * The boundary texels will be used when linear filtering is to be applied. + * </LI><p> + * <LI>Boundary ModeS and Boundary ModeT - the boundary mode for the + * S and T coordinates, respectively. The boundary modes are as + * follows:</LI><P> + * <UL> + * <LI>CLAMP - clamps texture coordinates to be in the range [0,1]. + * Texture boundary texels or the constant boundary color if boundary width + * is 0 will be used for U,V values that fall outside this range.</LI><P> + * <LI>WRAP - repeats the texture by wrapping texture coordinates + * that are outside the range [0,1]. Only the fractional portion + * of the texture coordinates is used. The integer portion is + * discarded</LI><P> + * <LI>CLAMP_TO_EDGE - clamps texture coordinates such that filtering + * will not sample a texture boundary texel. Texels at the edge of the + * texture will be used instead.</LI><P> + * <LI>CLAMP_TO_BOUNDARY - clamps texture coordinates such that filtering + * will sample only texture boundary texels, that is, it will never + * get some samples from the boundary and some from the edge. This + * will ensure clean unfiltered boundaries. If the texture does not + * have a boundary, that is the boundary width is equal to 0, then the + * constant boundary color will be used.</LI></P> + * </UL> + * <LI>Image - an image or an array of images for all the mipmap + * levels. If only one image is provided, the MIPmap mode must be + * set to BASE_LEVEL.</LI><P> + * <LI>Magnification filter - the magnification filter function. + * Used when the pixel being rendered maps to an area less than or + * equal to one texel. The magnification filter functions are as + * follows:</LI><P> + * <UL> + * <LI>FASTEST - uses the fastest available method for processing + * geometry.</LI><P> + * <LI>NICEST - uses the nicest available method for processing + * geometry.</LI><P> + * <LI>BASE_LEVEL_POINT - selects the nearest texel in the base level + * texture image.</LI><P> + * <LI>BASE_LEVEL_LINEAR - performs a bilinear interpolation on the four + * nearest texels in the base level texture image. The texture value T' is + * computed as follows:</LI><P> + * <UL> + * i<sub>0</sub> = trunc(u - 0.5)<P> + * j<sub>0</sub> = trunc(v - 0.5)<P> + * i<sub>1</sub> = i<sub>0</sub> + 1<P> + * j<sub>1</sub> = j<sub>0</sub> + 1<P> + * a = frac(u - 0.5)<P> + * b = frac(v - 0.5)<P> + * T' = (1-a)*(1-b)*T<sub>i<sub>0</sub>j<sub>0</sub></sub> + + * a*(1-b)*T<sub>i<sub>1</sub>j<sub>0</sub></sub> + + * (1-a)*b*T<sub>i<sub>0</sub>j<sub>1</sub></sub> + + * a*b*T<sub>i<sub>1</sub>j<sub>1</sub></sub><P> + * </UL> + * <LI>LINEAR_SHARPEN - sharpens the resulting image by extrapolating + * from the base level plus one image to the base level image of this + * texture object.</LI><P> + * <LI>LINEAR_SHARPEN_RGB - performs linear sharpen filter for the rgb + * components only. The alpha component is computed using BASE_LEVEL_LINEAR + * filter.</LI><P> + * <LI>LINEAR_SHARPEN_ALPHA - performs linear sharpen filter for the alpha + * component only. The rgb components are computed using BASE_LEVEL_LINEAR + * filter.</LI><P> + * <LI>FILTER4 - applies an application-supplied weight function + * on the nearest 4x4 texels in the base level texture image. The + * texture value T' is computed as follows:</LI><P> + * <UL> + * <table cellspacing=10> + * <td>i<sub>1</sub> = trunc(u - 0.5)</td> + * <td>i<sub>2</sub> = i<sub>1</sub> + 1</td> + * <td>i<sub>3</sub> = i<sub>2</sub> + 1</td> + * <td>i<sub>0</sub> = i<sub>1</sub> - 1</td> + * <tr> + * <td>j<sub>1</sub> = trunc(v - 0.5)</td> + * <td>j<sub>3</sub> = j<sub>2</sub> + 1</td> + * <td>j<sub>2</sub> = j<sub>1</sub> + 1</td> + * <td>j<sub>0</sub> = j<sub>1</sub> - 1</td> + * <tr> + * <td>a = frac(u - 0.5)</td> + * <tr> + * <td>b = frac(v - 0.5)</td> + * </table> + * f(x) : filter4 function where 0<=x<=2<P> + * T' = f(1+a) * f(1+b) * T<sub>i<sub>0</sub>j<sub>0</sub></sub> + + * f(a) * f(1+b) * T<sub>i<sub>1</sub>j<sub>0</sub></sub> + + * f(1-a) * f(1+b) * T<sub>i<sub>2</sub>j<sub>0</sub></sub> + + * f(2-a) * f(1+b) * T<sub>i<sub>3</sub>j<sub>0</sub></sub> + <br> + * f(1+a) * f(b) * T<sub>i<sub>0</sub>j<sub>1</sub></sub> + + * f(a) * f(b) * T<sub>i<sub>1</sub>j<sub>1</sub></sub> + + * f(1-a) * f(b) * T<sub>i<sub>2</sub>j<sub>1</sub></sub> + + * f(2-a) * f(b) * T<sub>i<sub>3</sub>j<sub>1</sub></sub> + <br> + * f(1+a) * f(1-b) * T<sub>i<sub>0</sub>j<sub>2</sub></sub> + + * f(a) * f(1-b) * T<sub>i<sub>1</sub>j<sub>2</sub></sub> + + * f(1-a) * f(1-b) * T<sub>i<sub>2</sub>j<sub>2</sub></sub> + + * f(2-a) * f(1-b) * T<sub>i<sub>3</sub>j<sub>2</sub></sub> + <br> + * f(1+a) * f(2-b) * T<sub>i<sub>0</sub>j<sub>3</sub></sub> + + * f(a) * f(2-b) * T<sub>i<sub>1</sub>j<sub>3</sub></sub> + + * f(1-a) * f(2-b) * T<sub>i<sub>2</sub>j<sub>3</sub></sub> + + * f(2-a) * f(2-b) * T<sub>i<sub>3</sub>j<sub>3</sub></sub> <P> + * </UL> + * </UL> + * <LI>Minification filter - the minification filter function. Used + * when the pixel being rendered maps to an area greater than one + * texel. The minifaction filter functions are as follows:</LI><P> + * <UL> + * <LI>FASTEST - uses the fastest available method for processing + * geometry.</LI><P> + * <LI>NICEST - uses the nicest available method for processing + * geometry.</LI><P> + * <LI>BASE_LEVEL_POINT - selects the nearest level in the base level + * texture map.</LI><P> + *<LI>BASE_LEVEL_LINEAR - performs a bilinear interpolation on the four + * nearest texels in the base level texture map.</LI><P> + * <LI>MULTI_LEVEL_POINT - selects the nearest texel in the nearest + * mipmap.</LI><P> + * <LI>MULTI_LEVEL_LINEAR - performs trilinear interpolation of texels + * between four texels each from the two nearest mipmap levels.</LI><P> + * <LI>FILTER4 - applies an application-supplied weight function + * on the nearest 4x4 texels in the base level texture image.</LI><P> + * </UL> + * <LI>MIPmap mode - the mode used for texture mapping for this + * object. The mode is one of the following:</LI><P> + * <UL> + * <LI>BASE_LEVEL - indicates that this Texture object only has a + * base-level image. If multiple levels are needed, they will be + * implicitly computed.</LI><P> + * <LI>MULTI_LEVEL_MIPMAP - indicates that this Texture object has + * multiple images. If MIPmap mode is set + * to MULTI_LEVEL_MIPMAP, images for Base Level through Max Level + * must be set.</LI><P> + * </UL> + * <LI>Format - the data format. The format is one of the + * following:</LI><P> + * <UL> + * <LI>INTENSITY - the texture image contains only texture + * values.</LI><P> + * <LI>LUMINANCE - the texture image contains only + * luminance values.</LI><P> + * <LI>ALPHA - the texture image contains only alpha + * values.</LI><P> + * <LI>LUMINANCE_ALPHA - the texture image contains + * both luminance and alpha values.</LI><P> + * <LI>RGB - the texture image contains red, green, + * and blue values.</LI><P> + * <LI>RGBA - the texture image contains red, green, blue, and alpha + * values.</LI><P></UL> + * <LI>Base Level - specifies the mipmap level to be used when filter + * specifies BASE_LEVEL_POINT or BASE_LEVEL_LINEAR.</LI><P> + * <LI>Maximum Level - specifies the maximum level of image that needs to be + * defined for this texture to be valid. Note, for this texture to be valid, + * images for Base Level through Maximum Level have to be defined.</LI><P> + * <LI>Minimum LOD - specifies the minimum of the LOD range. LOD smaller + * than this value will be clamped to this value.</LI><P> + * <LI>Maximum LOD - specifies the maximum of the LOD range. LOD larger + * than this value will be clamped to this value.</LI><P> + * <LI>LOD offset - specifies the offset to be used in the LOD calculation + * to compensate for under or over sampled texture images.</LI></P> + * <LI>Anisotropic Mode - defines how anisotropic filter is applied for + * this texture object. The anisotropic modes are as follows:</LI><P> + * <UL> + * <LI>ANISOTROPIC_NONE - no anisotropic filtering.</LI><P> + * <LI>ANISOTROPIC_SINGLE_VALUE - applies the degree of anisotropic filter + * in both the minification and magnification filters.</LI><P> + * </UL> + * <LI>Anisotropic Filter Degree - controls the degree of anisotropy. This + * property applies to both minification and magnification filtering. + * If it is equal to 1.0, then an isotropic filtering as specified in the + * minification or magnification filter will be used. If it is greater + * than 1.0, and the anisotropic mode is equal to ANISOTROPIC_SINGLE_VALUE, + * then + * the degree of anisotropy will also be applied in the filtering.</LI><P> + * <LI>Sharpen Texture Function - specifies the function of level-of-detail + * used in combining the texture value computed from the base level image + * and the texture value computed from the base level plus one image. The + * final texture value is computed as follows: </LI><P> + * <UL> + * T' = ((1 + SharpenFunc(LOD)) * T<sub>BaseLevel</sub>) - (SharpenFunc(LOD) * T<sub>BaseLevel+1</sub>) <P> + * </UL> + * <LI>Filter4 Function - specifies the function to be applied to the + * nearest 4x4 texels. This property includes samples of the filter + * function f(x), 0<=x<=2. The number of function values supplied + * has to be equal to 2<sup>m</sup> + 1 for some integer value of m + * greater than or equal to 4. </LI><P> + * </UL> + * + * @see Canvas3D#queryProperties + */ +public abstract class Texture extends NodeComponent { + /** + * Specifies that this Texture object allows reading its + * enable flag. + */ + public static final int + ALLOW_ENABLE_READ = CapabilityBits.TEXTURE_ALLOW_ENABLE_READ; + + /** + * Specifies that this Texture object allows writing its + * enable flag. + */ + public static final int + ALLOW_ENABLE_WRITE = CapabilityBits.TEXTURE_ALLOW_ENABLE_WRITE; + + /** + * Specifies that this Texture object allows reading its + * boundary mode information. + */ + public static final int + ALLOW_BOUNDARY_MODE_READ = CapabilityBits.TEXTURE_ALLOW_BOUNDARY_MODE_READ; + + /** + * Specifies that this Texture object allows reading its + * filter information. + */ + public static final int + ALLOW_FILTER_READ = CapabilityBits.TEXTURE_ALLOW_FILTER_READ; + + /** + * Specifies that this Texture object allows reading its + * image component information. + */ + public static final int + ALLOW_IMAGE_READ = CapabilityBits.TEXTURE_ALLOW_IMAGE_READ; + + /** + * Specifies that this Texture object allows writing its + * image component information. + * + * @since Java 3D 1.2 + */ + public static final int + ALLOW_IMAGE_WRITE = CapabilityBits.TEXTURE_ALLOW_IMAGE_WRITE; + + /** + * Specifies that this Texture object allows reading its + * format information. + * + * @since Java 3D 1.2 + */ + public static final int + ALLOW_FORMAT_READ = CapabilityBits.TEXTURE_ALLOW_FORMAT_READ; + + /** + * Specifies that this Texture object allows reading its + * size information (e.g., width, height, number of mipmap levels, + * boundary width). + * + * @since Java 3D 1.2 + */ + public static final int + ALLOW_SIZE_READ = CapabilityBits.TEXTURE_ALLOW_SIZE_READ; + + /** + * Specifies that this Texture object allows reading its + * mipmap mode information. + */ + public static final int + ALLOW_MIPMAP_MODE_READ = CapabilityBits.TEXTURE_ALLOW_MIPMAP_MODE_READ; + + /** + * Specifies that this Texture object allows reading its + * boundary color information. + */ + public static final int + ALLOW_BOUNDARY_COLOR_READ = CapabilityBits.TEXTURE_ALLOW_BOUNDARY_COLOR_READ; + + /** + * Specifies that this Texture object allows reading its LOD range + * information (e.g., base level, maximum level, minimum lod, + * maximum lod, lod offset) + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_LOD_RANGE_READ = CapabilityBits.TEXTURE_ALLOW_LOD_RANGE_READ; + + /** + * Specifies that this Texture object allows writing its LOD range + * information (e.g., base level, maximum level, minimum lod, + * maximum lod, lod offset) + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_LOD_RANGE_WRITE = CapabilityBits.TEXTURE_ALLOW_LOD_RANGE_WRITE; + + + /** + * Specifies that this Texture object allows reading its anistropic + * filter information (e.g., anisotropic mode, anisotropic filter) + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_ANISOTROPIC_FILTER_READ = CapabilityBits.TEXTURE_ALLOW_ANISOTROPIC_FILTER_READ; + + /** + * Specifies that this Texture object allows reading its sharpen + * texture function information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_SHARPEN_TEXTURE_READ = CapabilityBits.TEXTURE_ALLOW_SHARPEN_TEXTURE_READ; + + /** + * Specifies that this Texture object allows reading its filter4 + * function information. + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_FILTER4_READ = CapabilityBits.TEXTURE_ALLOW_FILTER4_READ; + + + /** + * Uses the fastest available method for processing geometry. + * This value can be used as a parameter to setMinFilter and + * setMagFilter. + * @see #setMinFilter + * @see #setMagFilter + */ + public static final int FASTEST = 0; + /** + * Uses the nicest available method for processing geometry. + * This value can be used as a parameter to setMinFilter and + * setMagFilter. + * @see #setMinFilter + * @see #setMagFilter + */ + public static final int NICEST = 1; + + /** + * Select the nearest texel in level 0 texture map. + * Maps to NEAREST. + * @see #setMinFilter + * @see #setMagFilter + */ + public static final int BASE_LEVEL_POINT = 2; + + /** + * Performs bilinear interpolation on the four nearest texels + * in level 0 texture map. + * Maps to LINEAR. + * @see #setMinFilter + * @see #setMagFilter + */ + public static final int BASE_LEVEL_LINEAR = 3; + + /** + * Selects the nearest texel in the nearest mipmap. + * Maps to NEAREST_MIPMAP_NEAREST. + * @see #setMinFilter + */ + public static final int MULTI_LEVEL_POINT = 4; + + /** + * Performs tri-linear interpolation of texels between four + * texels each from two nearest mipmap levels. + * Maps to LINEAR_MIPMAP_LINEAR, but an implementation can + * fall back to LINEAR_MIPMAP_NEAREST or NEAREST_MIPMAP_LINEAR. + * @see #setMinFilter + */ + public static final int MULTI_LEVEL_LINEAR = 5; + + // NOTE: values 6, 7, and 8 are reserved for the LINEAR_DETAIL* + // filter modes in Texture2D + + /** + * Sharpens the resulting image by extrapolating + * from the base level plus one image to the base level image of this + * texture object. + * + * @since Java 3D 1.3 + * @see #setMagFilter + */ + public static final int LINEAR_SHARPEN = 9; + + /** + * Performs linear sharpen filter for the rgb + * components only. The alpha component is computed using + * BASE_LEVEL_LINEAR filter. + * + * @since Java 3D 1.3 + * @see #setMagFilter + */ + public static final int LINEAR_SHARPEN_RGB = 10; + + /** + * Performs linear sharpen filter for the alpha + * component only. The rgb components are computed using + * BASE_LEVEL_LINEAR filter. + * + * @since Java 3D 1.3 + * @see #setMagFilter + */ + public static final int LINEAR_SHARPEN_ALPHA = 11; + + /** + * Applies an application-supplied weight function + * on the nearest 4x4 texels in the base level texture image. + * + * @since Java 3D 1.3 + * @see #setMinFilter + * @see #setMagFilter + */ + public static final int FILTER4 = 12; + + // Texture boundary mode parameter values + /** + * Clamps texture coordinates to be in the range [0, 1]. + * Texture boundary texels or the constant boundary color if boundary + * width is 0 will be used for U,V values that fall + * outside this range. + */ + public static final int CLAMP = 2; + /** + * Repeats the texture by wrapping texture coordinates that are outside + * the range [0,1]. Only the fractional portion of the texture + * coordinates is used; the integer portion is discarded. + */ + public static final int WRAP = 3; + /** + * Clamps texture coordinates such that filtering + * will not sample a texture boundary texel. Texels at the edge of the + * texture will be used instead. + * + * @since Java 3D 1.3 + */ + public static final int CLAMP_TO_EDGE = 4; + /** + * Clamps texture coordinates such that filtering + * will sample only texture boundary texels. If the texture does not + * have a boundary, that is the boundary width is equal to 0, then the + * constant boundary color will be used.</LI></P> + * + * @since Java 3D 1.3 + */ + public static final int CLAMP_TO_BOUNDARY = 5; + + + /** + * Indicates that Texture object only has one level. If multiple + * levels are needed, they will be implicitly computed. + */ + public static final int BASE_LEVEL = 1; + + /** + * Indicates that this Texture object has multiple images, one for + * each mipmap level. In this mode, there are + * <code>log<sub><font size=-2>2</font></sub>(max(width,height))+1</code> + * separate images. + */ + public static final int MULTI_LEVEL_MIPMAP = 2; + + // Texture format parameter values + + /** + * Specifies Texture contains only Intensity values. + */ + public static final int INTENSITY = 1; + + /** + * Specifies Texture contains only luminance values. + */ + public static final int LUMINANCE = 2; + + /** + * Specifies Texture contains only Alpha values. + */ + public static final int ALPHA = 3; + + /** + * Specifies Texture contains Luminance and Alpha values. + */ + public static final int LUMINANCE_ALPHA = 4; + + /** + * Specifies Texture contains Red, Green and Blue color values. + */ + public static final int RGB = 5; + + /** + * Specifies Texture contains Red, Green, Blue color values + * and Alpha value. + */ + public static final int RGBA = 6; + + /** + * No anisotropic filter. + * + * @since Java 3D 1.3 + * @see #setAnisotropicFilterMode + */ + public static final int ANISOTROPIC_NONE = 0; + + /** + * Uses the degree of anisotropy in both the minification and + * magnification filters. + * + * @since Java 3D 1.3 + * @see #setAnisotropicFilterMode + */ + public static final int ANISOTROPIC_SINGLE_VALUE = 1; + + /** + * Constructs a Texture object with default parameters. + * The default values are as follows: + * <ul> + * enable flag : true<br> + * width : 0<br> + * height : 0<br> + * mipmap mode : BASE_LEVEL<br> + * format : RGB<br> + * boundary mode S : WRAP<br> + * boundary mode T : WRAP<br> + * min filter : BASE_LEVEL_POINT<br> + * mag filter : BASE_LEVEL_POINT<br> + * boundary color : black (0,0,0,0)<br> + * boundary width : 0<br> + * array of images : null<br> + * baseLevel : 0<br> + * maximumLevel : <code>log<sub><font size=-2>2</font></sub>(max(width,height))</code><br> + * minimumLOD : -1000.0<br> + * maximumLOD : 1000.0<br> + * lod offset : (0, 0, 0)<br> + * anisotropic mode : ANISOTROPIC_NONE<br> + * anisotropic filter : 1.0<br> + * sharpen texture func: null<br> + * filter4 func: null<br> + * </ul> + * <p> + * Note that the default constructor creates a texture object with + * a width and height of 0 and is, therefore, not useful. + */ + public Texture() { + // Just use default values + } + + /** + * Constructs an empty Texture object with specified mipMapMode, + * format, width and height. Defaults are used for all other + * parameters. If <code>mipMapMode</code> is set to + * <code>BASE_LEVEL</code>, then the image at level 0 must be set + * by the application (using either the <code>setImage</code> or + * <code>setImages</code> method). If <code>mipMapMode</code> is + * set to <code>MULTI_LEVEL_MIPMAP</code>, then images for levels + * Base Level through Maximum Level must be set. + * + * @param mipMapMode type of mipmap for this Texture: one of + * BASE_LEVEL, MULTI_LEVEL_MIPMAP + * @param format data format of Textures saved in this object. + * One of INTENSITY, LUMINANCE, ALPHA, LUMINANCE_ALPHA, RGB, RGBA + * @param width width of image at level 0. Must be power of 2. + * @param height height of image at level 0. Must be power of 2. + * @exception IllegalArgumentException if width or height are not a + * power of 2, or if an invalid format or mipMapMode is specified. + */ + public Texture(int mipMapMode, + int format, + int width, + int height) { + + if ((mipMapMode != BASE_LEVEL) && (mipMapMode != MULTI_LEVEL_MIPMAP)) + throw new IllegalArgumentException(J3dI18N.getString("Texture0")); + + if ((format != INTENSITY) && (format != LUMINANCE) && + (format != ALPHA) && (format != LUMINANCE_ALPHA) && + (format != RGB) && (format != RGBA)) { + throw new IllegalArgumentException(J3dI18N.getString("Texture1")); + } + + int widPower = getPowerOf2(width); + if (widPower == -1) + throw new IllegalArgumentException(J3dI18N.getString("Texture2")); + + int heiPower = getPowerOf2(height); + if (heiPower == -1) + throw new IllegalArgumentException(J3dI18N.getString("Texture3")); + + ((TextureRetained)this.retained).initialize(format, width, widPower, + height, heiPower, mipMapMode, 0); + } + + /** + * Constructs an empty Texture object with specified mipMapMode, + * format, width, height, and boundaryWidth. + * Defaults are used for all other + * parameters. If <code>mipMapMode</code> is set to + * <code>BASE_LEVEL</code>, then the image at level 0 must be set + * by the application (using either the <code>setImage</code> or + * <code>setImages</code> method). If <code>mipMapMode</code> is + * set to <code>MULTI_LEVEL_MIPMAP</code>, then images for levels + * Base Level through Maximum Level must be set. + * + * @param mipMapMode type of mipmap for this Texture: one of + * BASE_LEVEL, MULTI_LEVEL_MIPMAP + * @param format data format of Textures saved in this object. + * One of INTENSITY, LUMINANCE, ALPHA, LUMINANCE_ALPHA, RGB, RGBA + * @param width width of image at level 0. Must be power of 2. This + * does not include the width of the boundary. + * @param height height of image at level 0. Must be power of 2. This + * does not include the width of the boundary. + * @param boundaryWidth width of the boundary. + * @exception IllegalArgumentException if width or height are not a + * power of 2, if an invalid format or mipMapMode is specified, or + * if the boundaryWidth < 0 + * + * @since Java 3D 1.3 + */ + public Texture(int mipMapMode, + int format, + int width, + int height, + int boundaryWidth) { + + if ((mipMapMode != BASE_LEVEL) && (mipMapMode != MULTI_LEVEL_MIPMAP)) + throw new IllegalArgumentException(J3dI18N.getString("Texture0")); + + if ((format != INTENSITY) && (format != LUMINANCE) && + (format != ALPHA) && (format != LUMINANCE_ALPHA) && + (format != RGB) && (format != RGBA)) { + throw new IllegalArgumentException(J3dI18N.getString("Texture1")); + } + + int widPower = getPowerOf2(width); + if (widPower == -1) + throw new IllegalArgumentException(J3dI18N.getString("Texture2")); + + int heiPower = getPowerOf2(height); + if (heiPower == -1) + throw new IllegalArgumentException(J3dI18N.getString("Texture3")); + + if (boundaryWidth < 0) + throw new IllegalArgumentException(J3dI18N.getString("Texture30")); + + ((TextureRetained)this.retained).initialize(format, width, widPower, + height, heiPower, mipMapMode, boundaryWidth); + } + + /** + * Sets the boundary mode for the S coordinate in this texture object. + * @param boundaryModeS the boundary mode for the S coordinate. + * One of: CLAMP, WRAP, CLAMP_TO_EDGE, or CLAMP_TO_BOUNDARY. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if <code>boundaryModeS</code> + * is a value other than <code>CLAMP</code>, <code>WRAP</code>, + * <code>CLAMP_TO_EDGE</code>, or <code>CLAMP_TO_BOUNDARY</code>. + */ + public void setBoundaryModeS(int boundaryModeS) { + checkForLiveOrCompiled(); + switch (boundaryModeS) { + case Texture.CLAMP: + case Texture.WRAP: + case Texture.CLAMP_TO_EDGE: + case Texture.CLAMP_TO_BOUNDARY: + break; + default: + throw new IllegalArgumentException(J3dI18N.getString("Texture31")); + } + ((TextureRetained)this.retained).initBoundaryModeS(boundaryModeS); + } + + /** + * Retrieves the boundary mode for the S coordinate. + * @return the current boundary mode for the S coordinate. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getBoundaryModeS() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_BOUNDARY_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture4")); + return ((TextureRetained)this.retained).getBoundaryModeS(); + } + + /** + * Sets the boundary mode for the T coordinate in this texture object. + * @param boundaryModeT the boundary mode for the T coordinate. + * One of: CLAMP, WRAP, CLAMP_TO_EDGE, or CLAMP_TO_BOUNDARY. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if <code>boundaryModeT</code> + * is a value other than <code>CLAMP</code>, <code>WRAP</code>, + * <code>CLAMP_TO_EDGE</code>, or <code>CLAMP_TO_BOUNDARY</code>. + */ + public void setBoundaryModeT(int boundaryModeT) { + checkForLiveOrCompiled(); + switch (boundaryModeT) { + case Texture.CLAMP: + case Texture.WRAP: + case Texture.CLAMP_TO_EDGE: + case Texture.CLAMP_TO_BOUNDARY: + break; + default: + throw new IllegalArgumentException(J3dI18N.getString("Texture31")); + } + ((TextureRetained)this.retained).initBoundaryModeT(boundaryModeT); + } + + /** + * Retrieves the boundary mode for the T coordinate. + * @return the current boundary mode for the T coordinate. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getBoundaryModeT() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_BOUNDARY_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture4")); + return ((TextureRetained)this.retained).getBoundaryModeT(); + } + + /** + * Sets the minification filter function. This + * function is used when the pixel being rendered maps to an area + * greater than one texel. + * @param minFilter the minification filter. One of: + * FASTEST, NICEST, BASE_LEVEL_POINT, BASE_LEVEL_LINEAR, + * MULTI_LEVEL_POINT, MULTI_LEVEL_LINEAR, or FILTER4 + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if <code>minFilter</code> + * is a value other than <code>FASTEST</code>, <code>NICEST</code>, + * <code>BASE_LEVEL_POINT</code>, <code>BASE_LEVEL_LINEAR</code>, + * <code>MULTI_LEVEL_POINT</code>, <code>MULTI_LEVEL_LINEAR</code>, or + * <code>FILTER4</code>. + * + * @see Canvas3D#queryProperties + */ + public void setMinFilter(int minFilter) { + checkForLiveOrCompiled(); + + switch (minFilter) { + case FASTEST: + case NICEST: + case BASE_LEVEL_POINT: + case BASE_LEVEL_LINEAR: + case MULTI_LEVEL_POINT: + case MULTI_LEVEL_LINEAR: + case FILTER4: + break; + default: + throw new IllegalArgumentException(J3dI18N.getString("Texture28")); + } + + ((TextureRetained)this.retained).initMinFilter(minFilter); + } + + /** + * Retrieves the minification filter. + * @return the current minification filter function. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getMinFilter() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_FILTER_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture6")); + return ((TextureRetained)this.retained).getMinFilter(); + } + + /** + * Sets the magnification filter function. This + * function is used when the pixel being rendered maps to an area + * less than or equal to one texel. + * @param magFilter the magnification filter, one of: + * FASTEST, NICEST, BASE_LEVEL_POINT, BASE_LEVEL_LINEAR, + * LINEAR_SHARPEN, LINEAR_SHARPEN_RGB, LINEAR_SHARPEN_ALPHA, or FILTER4. + * + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if <code>magFilter</code> + * is a value other than <code>FASTEST</code>, <code>NICEST</code>, + * <code>BASE_LEVEL_POINT</code>, <code>BASE_LEVEL_LINEAR</code>, + * <code>LINEAR_SHARPEN</code>, <code>LINEAR_SHARPEN_RGB</code>, + * <code>LINEAR_SHARPEN_ALPHA</code>, or + * <code>FILTER4</code>. + * + * @see Canvas3D#queryProperties + */ + public void setMagFilter(int magFilter) { + checkForLiveOrCompiled(); + + switch (magFilter) { + case FASTEST: + case NICEST: + case BASE_LEVEL_POINT: + case BASE_LEVEL_LINEAR: + case LINEAR_SHARPEN: + case LINEAR_SHARPEN_RGB: + case LINEAR_SHARPEN_ALPHA: + case FILTER4: + break; + default: + throw new IllegalArgumentException(J3dI18N.getString("Texture29")); + } + + ((TextureRetained)this.retained).initMagFilter(magFilter); + } + + /** + * Retrieves the magnification filter. + * @return the current magnification filter function. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getMagFilter() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_FILTER_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture6")); + return ((TextureRetained)this.retained).getMagFilter(); + } + + /** + * Sets the image for a specified mipmap level. + * @param level mipmap level to set: 0 is the base level + * @param image ImageComponent object containing the texture image + * for the specified mipmap level + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalArgumentException if an ImageComponent3D is + * used in a Texture2D object; if an ImageComponent2D is used in a + * Texture3D object; or if this object is part of a live + * scene graph and the image being set at this level is not the + * same size (width, height, depth) as the old image at this + * level. + */ + public void setImage(int level, ImageComponent image) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture15")); + } + + if (isLive()) + ((TextureRetained)this.retained).setImage(level, image); + else + ((TextureRetained)this.retained).initImage(level, image); + } + + /** + * Retrieves the image for a specified mipmap level. + * @param level mipmap level to get: 0 is the base level + * @return the ImageComponent object containing the texture image at + * the specified mipmap level. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public ImageComponent getImage(int level) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture9")); + } + + return ((TextureRetained)this.retained).getImage(level); + } + + /** + * Sets the array of images for all mipmap levels. + * @param images array of ImageComponent objects + * containing the texture images for all mipmap levels + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @exception IllegalArgumentException if an ImageComponent3D is + * used in a Texture2D object; if an ImageComponent2D is used in a + * Texture3D object; if <code>images.length</code> is not equal to + * the total number of mipmap levels; or if this object is part of + * a live scene graph and the size of each dimension (width, + * height, depth) of the image at a given level in the + * <code>images</code> array is not half the dimension of the + * previous level. + * + * @since Java 3D 1.2 + */ + public void setImages(ImageComponent[] images) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture15")); + } + + if (images == null) + throw new IllegalArgumentException(J3dI18N.getString("Texture20")); + + if (isLive()) + ((TextureRetained)this.retained).setImages(images); + else + ((TextureRetained)this.retained).initImages(images); + } + + /** + * Retrieves the array of images for all mipmap levels. + * @return the array of ImageComponent objects for this Texture. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public ImageComponent[] getImages() { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture9")); + } + return ((TextureRetained)this.retained).getImages(); + } + + /** + * Retrieves the format of this Texture object. + * @return the format of this Texture object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getFormat() { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_FORMAT_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture19")); + } + return ((TextureRetained)this.retained).getFormat(); + } + + /** + * Retrieves the width of this Texture object. + * @return the width of this Texture object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getWidth() { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture16")); + } + return ((TextureRetained)this.retained).getWidth(); + } + + /** + * Retrieves the height of this Texture object. + * @return the height of this Texture object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getHeight() { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture17")); + } + return ((TextureRetained)this.retained).getHeight(); + } + + /** + * Retrieves the width of the boundary of this Texture object. + * @return the width of the boundary of this Texture object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getBoundaryWidth() { + + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture17")); + } + + return ((TextureRetained)this.retained).getBoundaryWidth(); + } + + /** + * Retrieves the number of mipmap levels needed for this Texture object. + * @return (maximum Level - base Level + 1) + * if <code>mipMapMode</code> is + * <code>MULTI_LEVEL_MIPMAP</code>; otherwise it returns 1. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int numMipMapLevels() { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture18")); + } + return ((TextureRetained)this.retained).numMipMapLevels(); + } + + /** + * Sets mipmap mode for texture mapping for this texture object. + * @param mipMapMode the new mipmap mode for this object. One of: + * BASE_LEVEL or MULTI_LEVEL_MIPMAP. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if <code>mipMapMode</code> + * is a value other than <code>BASE_LEVEL</code> or + * <code>MULTI_LEVEL_MIPMAP</code>. + */ + public void setMipMapMode(int mipMapMode) { + checkForLiveOrCompiled(); + ((TextureRetained)this.retained).initMipMapMode(mipMapMode); + } + + /** + * Retrieves current mipmap mode. + * @return current mipmap mode of this texture object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getMipMapMode() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_MIPMAP_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture10")); + return ((TextureRetained)this.retained).getMipMapMode(); + } + + /** + * Enables or disables texture mapping for this + * appearance component object. + * @param state true or false to enable or disable texture mapping + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setEnable(boolean state) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_ENABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture11")); + } + if (isLive()) + ((TextureRetained)this.retained).setEnable(state); + else + ((TextureRetained)this.retained).initEnable(state); + + } + + /** + * Retrieves the state of the texture enable flag. + * @return true if texture mapping is enabled, + * false if texture mapping is disabled + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public boolean getEnable() { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_ENABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture12")); + } + return ((TextureRetained)this.retained).getEnable(); + } + + // Internal j3d usage method + // Returns n if num is 2**n + // Returns -1 if num is 0 or negative or if + // num is NOT power of 2. + // NOTE: ********** Assumes 32 bit integer****************** + static int getPowerOf2(int num) { + + int i, tmp; + // Can only handle positive numbers, return error. + if (num < 1) return -1; + + for (i=0, tmp = num; i < 32;i++) { + // Check if leftmost bit is 1 + if ((tmp & 0x80000000) != 0) { + //Check if any other bit is 1 + if ((tmp & 0x7fffffff) == 0) + return 31-i;//valid power of 2 integer + else + return -1;//invalid non-power-of-2 integer + } + tmp <<= 1; + } + //Can't reach here because we have already checked for 0 + return -1; + } + + /** + * Sets the texture boundary color for this texture object. The + * texture boundary color is used when boundaryModeS or boundaryModeT + * is set to CLAMP or CLAMP_TO_BOUNDARY and if texture boundary is not + * specified. + * @param boundaryColor the new texture boundary color. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + public void setBoundaryColor(Color4f boundaryColor) { + checkForLiveOrCompiled(); + ((TextureRetained)this.retained).initBoundaryColor(boundaryColor); + } + + /** + * Sets the texture boundary color for this texture object. The + * texture boundary color is used when boundaryModeS or boundaryModeT + * is set to CLAMP or CLAMP_TO_BOUNDARY and if texture boundary is not + * specified. + * @param r the red component of the color. + * @param g the green component of the color. + * @param b the blue component of the color. + * @param a the alpha component of the color. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + public void setBoundaryColor(float r, float g, float b, float a) { + checkForLiveOrCompiled(); + ((TextureRetained)this.retained).initBoundaryColor(r, g, b, a); + } + + /** + * Retrieves the texture boundary color for this texture object. + * @param boundaryColor the vector that will receive the + * current texture boundary color. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getBoundaryColor(Color4f boundaryColor) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_BOUNDARY_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture13")); + } + ((TextureRetained)this.retained).getBoundaryColor(boundaryColor); + } + + /** + * Specifies the base level for this texture object. + * @param baseLevel index of the lowest defined mipmap level. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if specified baseLevel < 0, or + * if baseLevel > maximumLevel + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setBaseLevel(int baseLevel) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture32")); + } + } + + if (isLive()) { + ((TextureRetained)this.retained).setBaseLevel(baseLevel); + } else { + ((TextureRetained)this.retained).initBaseLevel(baseLevel); + } + } + + /** + * Retrieves the base level for this texture object. + * @return base level for this texture object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getBaseLevel() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture34")); + } + } + return ((TextureRetained)this.retained).getBaseLevel(); + } + + /** + * Specifies the maximum level for this texture object. + * @param maximumLevel index of the highest defined mipmap level. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if specified + * maximumLevel < baseLevel, or + * if maximumLevel > <code>log<sub><font size=-2>2</font></sub>(max(width,height))</code> + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setMaximumLevel(int maximumLevel) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture33")); + } + } + + if (isLive()) { + ((TextureRetained)this.retained).setMaximumLevel(maximumLevel); + } else { + ((TextureRetained)this.retained).initMaximumLevel(maximumLevel); + } + } + + /** + * Retrieves the maximum level for this texture object. + * @return maximum level for this texture object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getMaximumLevel() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture35")); + } + } + return ((TextureRetained)this.retained).getMaximumLevel(); + } + + /** + * Specifies the minimum level-of-detail for this texture object. + * @param minimumLod the minimum level-of-detail. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if specified lod > maximum lod + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setMinimumLOD(float minimumLod) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture38")); + } + } + + if (isLive()) { + ((TextureRetained)this.retained).setMinimumLOD(minimumLod); + } else { + ((TextureRetained)this.retained).initMinimumLOD(minimumLod); + } + } + + /** + * Retrieves the minimum level-of-detail for this texture object. + * @return the minimum level-of-detail + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public float getMinimumLOD() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture40")); + } + } + return ((TextureRetained)this.retained).getMinimumLOD(); + } + + /** + * Specifies the maximum level-of-detail for this texture object. + * @param maximumLod the maximum level-of-detail. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if specified lod < minimum lod + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setMaximumLOD(float maximumLod) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture39")); + } + } + + if (isLive()) { + ((TextureRetained)this.retained).setMaximumLOD(maximumLod); + } else { + ((TextureRetained)this.retained).initMaximumLOD(maximumLod); + } + } + + /** + * Retrieves the maximum level-of-detail for this texture object. + * @return the maximum level-of-detail + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public float getMaximumLOD() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture41")); + } + } + return ((TextureRetained)this.retained).getMaximumLOD(); + } + + /** + * Specifies the LOD offset for this texture object. + * @param s the s component of the LOD offset + * @param t the t component of the LOD offset + * @param r the r component of the LOD offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setLodOffset(float s, float t, float r) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture44")); + } + } + + if (isLive()) { + ((TextureRetained)this.retained).setLodOffset(s, t, r); + } else { + ((TextureRetained)this.retained).initLodOffset(s, t, r); + } + } + + /** + * Specifies the LOD offset for this texture object. + * @param offset the LOD offset + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setLodOffset(Tuple3f offset) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture44")); + } + } + + if (isLive()) { + ((TextureRetained)this.retained).setLodOffset( + offset.x, offset.y, offset.z); + } else { + ((TextureRetained)this.retained).initLodOffset( + offset.x, offset.y, offset.z); + } + } + + /** + * Retrieves the LOD offset for this texture object. + * @param offset the vector that will receive the + * current LOD offset. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getLodOffset(Tuple3f offset) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_LOD_RANGE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture45")); + } + } + ((TextureRetained)this.retained).getLodOffset(offset); + } + + /** + * Specifies the anisotropic filter mode for this texture object. + * @param mode the anisotropic filter mode. One of + * ANISOTROPIC_NONE or ANISOTROPIC_SINGLE_VALUE. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if + * <code>mode</code> is a value other than + * <code>ANISOTROPIC_NONE</code> or <code>ANISOTROPIC_SINGLE_VALUE</code> + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setAnisotropicFilterMode(int mode) { + checkForLiveOrCompiled(); + if ((mode != ANISOTROPIC_NONE) && + (mode != ANISOTROPIC_SINGLE_VALUE)) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture25")); + } + ((TextureRetained)this.retained).initAnisotropicFilterMode(mode); + } + + /** + * Retrieves the anisotropic filter mode for this texture object. + * @return the currrent anisotropic filter mode of this texture object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getAnisotropicFilterMode() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_ANISOTROPIC_FILTER_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture26")); + } + } + return ((TextureRetained)this.retained).getAnisotropicFilterMode(); + } + + /** + * Specifies the degree of anisotropy to be + * used when the anisotropic filter mode specifies + * ANISOTROPIC_SINGLE_VALUE. + * @param degree degree of anisotropy + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if + * <code>degree</code> < 1.0 or + * <code>degree</code> > the maximum degree of anisotropy. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setAnisotropicFilterDegree(float degree) { + checkForLiveOrCompiled(); + if (degree < 1.0) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture27")); + } + ((TextureRetained)this.retained).initAnisotropicFilterDegree(degree); + } + + /** + * Retrieves the anisotropic filter degree for this texture object. + * @return the current degree of anisotropy of this texture object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public float getAnisotropicFilterDegree() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_ANISOTROPIC_FILTER_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture26")); + } + } + return ((TextureRetained)this.retained).getAnisotropicFilterDegree(); + } + + /** + * sets the sharpen texture LOD function for this texture object. + * @param lod array containing the level-of-detail values. + * @param pts array containing the function values for the corresponding + * level-of-detail values. + * + * @exception IllegalStateException if the length of <code>lod</code> + * does not match the length of <code>pts</code> + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setSharpenTextureFunc(float[] lod, float[] pts) { + checkForLiveOrCompiled(); + if (((lod != null) && (pts != null) && (lod.length == pts.length)) || + ((lod == null) && (pts == null))) { + ((TextureRetained)this.retained).initSharpenTextureFunc(lod, pts); + } else { + throw new IllegalStateException( + J3dI18N.getString("Texture22")); + } + } + + /** + * sets the sharpen texture LOD function for this texture object. + * The Point2f x,y values are defined as follows: x is the lod value, + * y is the corresponding function value. + * + * @param pts array of Point2f containing the lod as well as the + * corresponding function value. + * + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setSharpenTextureFunc(Point2f[] pts) { + checkForLiveOrCompiled(); + ((TextureRetained)this.retained).initSharpenTextureFunc(pts); + } + + /** + * Gets the number of points in the sharpen texture LOD function for this + * texture object. + * + * @return the number of points in the sharpen texture LOD function. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getSharpenTextureFuncPointsCount() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_SHARPEN_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture21")); + } + } + return ((TextureRetained)this.retained).getSharpenTextureFuncPointsCount(); + } + + /** + * Copies the array of sharpen texture LOD function points into the + * specified arrays. The arrays must be large enough to hold all the + * points. + * + * @param lod the array to receive the level-of-detail values. + * @param pts the array to receive the function values for the + * corresponding level-of-detail values. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getSharpenTextureFunc(float[] lod, float[] pts) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_SHARPEN_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture21")); + } + } + ((TextureRetained)this.retained).getSharpenTextureFunc( + lod, pts); + } + + /** + * Copies the array of sharpen texture LOD function points including + * the lod values and the corresponding function values into the + * specified array. The array must be large enough to hold all the points. + * The individual array elements must be allocated by the caller as well. + * + * @param pts the array to receive the sharpen texture LOD function points + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getSharpenTextureFunc(Point2f[] pts) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_SHARPEN_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture21")); + } + } + ((TextureRetained)this.retained).getSharpenTextureFunc(pts); + } + + /** + * sets the filter4 function for this texture object. + * @param weights array containing samples of the filter4 function. + * + * @exception IllegalArgumentException if the length of + * <code>weight</code> < 4 + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setFilter4Func(float[] weights) { + checkForLiveOrCompiled(); + if ((weights == null) || (weights.length < 4)) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture24")); + } else { + ((TextureRetained)this.retained).initFilter4Func(weights); + } + } + + /** + * Retrieves the number of filter4 function values for this + * texture object. + * + * @return the number of filter4 function values + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getFilter4FuncPointsCount() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_FILTER4_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture23")); + } + } + return (((TextureRetained)this.retained).getFilter4FuncPointsCount()); + } + + /** + * Copies the array of filter4 function values into the specified + * array. The array must be large enough to hold all the values. + * + * @param weights the array to receive the function values. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getFilter4Func(float[] weights) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_FILTER4_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture23")); + } + } + ((TextureRetained)this.retained).getFilter4Func(weights); + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> + * into the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + Hashtable hashtable = originalNodeComponent.nodeHashtable; + + TextureRetained tex = (TextureRetained) originalNodeComponent.retained; + TextureRetained rt = (TextureRetained) retained; + + rt.initBoundaryModeS(tex.getBoundaryModeS()); + rt.initBoundaryModeT(tex.getBoundaryModeT()); + rt.initMinFilter(tex.getMinFilter()); + rt.initMagFilter(tex.getMagFilter()); + rt.initMipMapMode(tex.getMipMapMode()); + rt.initEnable(tex.getEnable()); + rt.initAnisotropicFilterMode(tex.getAnisotropicFilterMode()); + rt.initAnisotropicFilterDegree(tex.getAnisotropicFilterDegree()); + rt.initSharpenTextureFunc(tex.getSharpenTextureFunc()); + rt.initFilter4Func(tex.getFilter4Func()); + + rt.initBaseLevel(tex.getBaseLevel()); + rt.initMaximumLevel(tex.getMaximumLevel()); + rt.initMinimumLOD(tex.getMinimumLOD()); + rt.initMaximumLOD(tex.getMaximumLOD()); + + Point3f offset = new Point3f(); + tex.getLodOffset(offset); + rt.initLodOffset(offset.x, offset.y, offset.z); + + Color4f c = new Color4f(); + tex.getBoundaryColor(c); + rt.initBoundaryColor(c); + + // No API available to get the current level + for (int i=tex.maxLevels-1; i>=0; i-- ) { + ImageComponent image = (ImageComponent) + getNodeComponent(tex.getImage(i), + forceDuplicate, + hashtable); + if (image != null) { + rt.initImage(i, image); + } + } + // TODO: clone new v1.2 attributes + } + + /** + * This function is called from getNodeComponent() to see if any of + * the sub-NodeComponents duplicateOnCloneTree flag is true. + * If it is the case, current NodeComponent needs to + * duplicate also even though current duplicateOnCloneTree flag is false. + * This should be overwrite by NodeComponent which contains sub-NodeComponent. + */ + boolean duplicateChild() { + if (getDuplicateOnCloneTree()) + return true; + + int level = ((TextureRetained) this.retained).maxLevels; + TextureRetained rt = (TextureRetained) retained; + + for (int i=0; i < level; i++) { + ImageComponent img = rt.getImage(i); + if ((img != null) && img.getDuplicateOnCloneTree()) + return true; + } + return false; + } +} diff --git a/src/classes/share/javax/media/j3d/Texture2D.java b/src/classes/share/javax/media/j3d/Texture2D.java new file mode 100644 index 0000000..e3e2015 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Texture2D.java @@ -0,0 +1,554 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.*; + + + +/** + * Texture2D is a subclass of Texture class. It extends Texture + * class by adding a constructor and a mutator method for + * setting a 2D texture image. + * <P> + * Each Texture2D object has the following properties:<P> + * <UL> + * <LI>Magnification filter - the magnification filter function. + * Used when the pixel being rendered maps to an area less than or + * equal to one texel. In addition to the magnification filter functions + * defined in the base Texture class, the following values are + * supported:</LI><P> + * <UL> + * <LI>LINEAR_DETAIL - performs linear sampling in both the base level + * texture image and the detail texture image, and combines the two + * texture values according to the detail texture mode.</LI><P> + * <LI>LINEAR_DETAIL_RGB - performs linear detail for the rgb + * components only. The alpha component is computed using BASE_LEVEL_LINEAR + * filter.</LI><P> + * <LI>LINEAR_DETAIL_ALPHA - performs linear detail for the alpha + * component only. The rgb components are computed using BASE_LEVEL_LINEAR + * filter.</LI><P> + * </UL> + * <LI>Detail Texture Image - Detail texture image to be used when the texture + * magnification filter mode specifies LINEAR_DETAIL, LINEAR_DETAIL_ALPHA, or + * LINEAR_DETAIL_RGB; if the detail texture images is null, then + * the texture magnification filter mode will fall back to BASE_LEVEL_LINEAR. + * </LI><P> + * <LI>Detail Texture Mode - specifies how the texture image is combined + * with the detail image. The detail texture modes are as follows: </LI><P> + * <UL> + * <LI>DETAIL_ADD</LI><P> + * <UL> + * T' = T<sub>texture</sub> + DetailFunc(LOD) * (2 * T<sub>detail</sub> - 1)<P> + * </UL> + * <LI>DETAIL_MODULATE</LI><P> + * <UL> + * T' = T<sub>texture</sub> * (1 + DetailFunc(LOD) * (2 * T<sub>detail</sub> - 1))<P> + * </UL> + * </UL> + * where T<sub>texture</sub> is the texture value computed from the base level + * texture image, and T<sub>detail</sub> is the texture value computed from the + * detail texture image.<P> + * <LI>Detail Texture Function - specifies the function of level-of-detail + * used in combining the detail texture with the base level texture of this object.</LI><P> + * <LI>Detail Texture Level - specifies the number of levels that + * separate the base level image of this texture object and the detail + * texture image. This value is used in the linear filter + * calculation of the detail texture image. Note, detail texture will only + * be applied to the level 0 of the texture image. Hence, for detail texture + * to work, base level has to be set to 0.</LI><P> + * </UL> + * + * @see Canvas3D#queryProperties + */ +public class Texture2D extends Texture { + + /** + * Specifies that this Texture object allows reading its detail + * texture information (e.g., detail texture image, detail texture mode, + * detail texture function, detail texture function points count, + * detail texture level) + * + * @since Java 3D 1.3 + */ + public static final int + ALLOW_DETAIL_TEXTURE_READ = CapabilityBits.TEXTURE2D_ALLOW_DETAIL_TEXTURE_READ; + + /** + * Performs linear sampling in both the base level + * texture image and the detail texture image, and combines the two + * texture values according to the detail texture mode. + * + * @since Java 3D 1.3 + * @see #setMagFilter + */ + public static final int LINEAR_DETAIL = 6; + + /** + * Performs linear detail for the rgb + * components only. The alpha component is computed using + * BASE_LEVEL_LINEAR filter. + * + * @since Java 3D 1.3 + * @see #setMagFilter + */ + public static final int LINEAR_DETAIL_RGB = 7; + + /** + * Performs linear detail for the alpha + * component only. The rgb components are computed using + * BASE_LEVEL_LINEAR filter. + * + * @since Java 3D 1.3 + * @see #setMagFilter + */ + public static final int LINEAR_DETAIL_ALPHA = 8; + + /** + * Adds the detail texture image to the level 0 image of this texture + * object + * + * @since Java 3D 1.3 + * @see #setDetailTextureMode + */ + public static final int DETAIL_ADD = 0; + + /** + * Modulates the detail texture image with the level 0 image of this + * texture object + * + * @since Java 3D 1.3 + * @see #setDetailTextureMode + */ + public static final int DETAIL_MODULATE = 1; + + + + /** + * Constructs a texture object using default values. + * + * The default values are as follows: + * <ul> + * detail texture image: null<br> + * detail texture mode: DETAIL_MODULATE<br> + * detail texture func: null<br> + * detail texture level: 2<br> + * </ul> + * <p> + * Note that the default constructor creates a texture object with + * a width and height of 0 and is, therefore, not useful. + */ + public Texture2D() { + super(); + } + + /** + * Constructs an empty Texture2D object with specified mipmapMode + * format, width and height. Image at base level must be set by + * the application using 'setImage' method. If mipmapMode is + * set to MULTI_LEVEL_MIPMAP, images for base level through maximum level + * must be set. + * @param mipMapMode type of mipmap for this Texture: One of + * BASE_LEVEL, MULTI_LEVEL_MIPMAP. + * @param format data format of Textures saved in this object. + * One of INTENSITY, LUMINANCE, ALPHA, LUMINANCE_ALPHA, RGB, RGBA. + * @param width width of image at level 0. Must be power of 2. + * @param height height of image at level 0. Must be power of 2. + * @exception IllegalArgumentException if width or height are NOT + * power of 2 OR invalid format/mipmapMode is specified. + */ + public Texture2D( + int mipMapMode, + int format, + int width, + int height){ + + super(mipMapMode, format, width, height); + } + + + /** + * Constructs an empty Texture2D object with specified mipMapMode, + * format, width, height, and boundaryWidth. + * Defaults are used for all other + * parameters. If <code>mipMapMode</code> is set to + * <code>BASE_LEVEL</code>, then the image at level 0 must be set + * by the application (using either the <code>setImage</code> or + * <code>setImages</code> method). If <code>mipMapMode</code> is + * set to <code>MULTI_LEVEL_MIPMAP</code>, then images for levels + * Base Level through Maximum Level must be set. + * + * @param mipMapMode type of mipmap for this Texture: one of + * BASE_LEVEL, MULTI_LEVEL_MIPMAP + * @param format data format of Textures saved in this object. + * One of INTENSITY, LUMINANCE, ALPHA, LUMINANCE_ALPHA, RGB, RGBA + * @param width width of image at level 0. Must be power of 2. This + * does not include the width of the boundary. + * @param height height of image at level 0. Must be power of 2. This + * does not include the width of the boundary. + * @param boundaryWidth width of the boundary. + * @exception IllegalArgumentException if width or height are not a + * power of 2, if an invalid format or mipMapMode is specified, or + * if the boundaryWidth < 0 + * + * @since Java 3D 1.3 + */ + public Texture2D(int mipMapMode, + int format, + int width, + int height, + int boundaryWidth) { + + super(mipMapMode, format, width, height, boundaryWidth); + } + + /** + * Sets the magnification filter function. This + * function is used when the pixel being rendered maps to an area + * less than or equal to one texel. + * @param magFilter the magnification filter, one of: + * FASTEST, NICEST, BASE_LEVEL_POINT, BASE_LEVEL_LINEAR, + * LINEAR_DETAIL, LINEAR_DETAIL_RGB, LINEAR_DETAIL_ALPHA, + * LINEAR_SHARPEN, LINEAR_SHARPEN_RGB, LINEAR_SHARPEN_ALPHA, or FILTER4. + * + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if <code>minFilter</code> + * is a value other than <code>FASTEST</code>, <code>NICEST</code>, + * <code>BASE_LEVEL_POINT</code>, <code>BASE_LEVEL_LINEAR</code>, + * <code>LINEAR_DETAIL</code>, <code>LINEAR_DETAIL_RGB</code>, + * <code>LINEAR_DETAIL_ALPHA</code>, + * <code>LINEAR_SHARPEN</code>, <code>LINEAR_SHARPEN_RGB</code>, + * <code>LINEAR_SHARPEN_ALPHA</code>, or + * <code>FILTER4</code>. + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setMagFilter(int magFilter) { + checkForLiveOrCompiled(); + + switch (magFilter) { + case FASTEST: + case NICEST: + case BASE_LEVEL_POINT: + case BASE_LEVEL_LINEAR: + case LINEAR_DETAIL: + case LINEAR_DETAIL_RGB: + case LINEAR_DETAIL_ALPHA: + case LINEAR_SHARPEN: + case LINEAR_SHARPEN_RGB: + case LINEAR_SHARPEN_ALPHA: + case FILTER4: + break; + default: + throw new IllegalArgumentException(J3dI18N.getString("Texture29")); + } + + ((Texture2DRetained)this.retained).initMagFilter(magFilter); + } + + /** + * Sets the detail texture image for this texture object. + * @param detailTexture ImageComponent2D object containing the + * detail texture image. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setDetailImage(ImageComponent2D detailTexture) { + checkForLiveOrCompiled(); + ((Texture2DRetained)this.retained).initDetailImage(detailTexture); + } + + /** + * Retrieves the detail texture image for this texture object. + * @return ImageComponent2D object containing the detail texture image. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public ImageComponent2D getDetailImage() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_DETAIL_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture2D0")); + } + } + return ((Texture2DRetained)this.retained).getDetailImage(); + } + + /** + * Sets the detail texture mode for this texture object. + * @param mode detail texture mode. One of: DETAIL_ADD or DETAIL_MODULATE + * + * @exception IllegalArgumentException if + * <code>mode</code> is a value other than + * <code>DETAIL_ADD</code>, or <code>DETAIL_MODULATE</code> + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setDetailTextureMode(int mode) { + checkForLiveOrCompiled(); + if ((mode != DETAIL_ADD) && (mode != DETAIL_MODULATE)) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture2D1")); + } + ((Texture2DRetained)this.retained).initDetailTextureMode(mode); + } + + /** + * Retrieves the detail texture mode for this texture object. + * @return the detail texture mode. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getDetailTextureMode() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_DETAIL_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture2D0")); + } + } + return ((Texture2DRetained)this.retained).getDetailTextureMode(); + } + + /** + * Sets the detail texture level for this texture object. + * @param level the detail texture level. + * + * @exception IllegalArgumentException if <code>level</code> < 0 + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setDetailTextureLevel(int level) { + checkForLiveOrCompiled(); + if (level < 0) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture2D2")); + } + ((Texture2DRetained)this.retained).initDetailTextureLevel(level); + } + + /** + * Retrieves the detail texture level for this texture object. + * @return the detail texture level. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getDetailTextureLevel() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_DETAIL_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture2D0")); + } + } + return ((Texture2DRetained)this.retained).getDetailTextureLevel(); + } + + /** + * sets the detail texture LOD function for this texture object. + * @param lod array containing the level-of-detail values. + * @param pts array containing the function values for the corresponding + * level-of-detail values. + * + * @exception IllegalStateException if the length of <code>lod</code> + * does not match the length of <code>pts</code> + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setDetailTextureFunc(float[] lod, float[] pts) { + checkForLiveOrCompiled(); + if (((lod != null) && (pts != null) && (lod.length == pts.length)) || + ((lod == null) && (pts == null))) { + ((Texture2DRetained)this.retained).initDetailTextureFunc(lod, pts); + } else { + throw new IllegalStateException(J3dI18N.getString("Texture2D3")); + } + } + + /** + * sets the detail texture LOD function for this texture object. + * The Point2f x,y values are defined as follows: x is the lod value, + * y is the corresponding function value. + * + * @param pts array of Point2f containing the lod as well as the + * corresponding function value. + * + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ + public void setDetailTextureFunc(Point2f[] pts) { + checkForLiveOrCompiled(); + ((Texture2DRetained)this.retained).initDetailTextureFunc(pts); + } + + /** + * Gets the number of points in the detail texture LOD function for this + * texture object. + * + * @return the number of points in the detail texture LOD function. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getDetailTextureFuncPointsCount() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_DETAIL_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture2D0")); + } + } + return ((Texture2DRetained)this.retained).getDetailTextureFuncPointsCount(); + } + + /** + * Copies the array of detail texture LOD function points into the + * specified arrays. The arrays must be large enough to hold all the + * points. + * + * @param lod the array to receive the level-of-detail values. + * @param pts the array to receive the function values for the + * corresponding level-of-detail values. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getDetailTextureFunc(float[] lod, float[] pts) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_DETAIL_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture2D0")); + } + } + ((Texture2DRetained)this.retained).getDetailTextureFunc(lod, pts); + } + + /** + * Copies the array of detail texture LOD function points including + * the lod values and the corresponding function values into the + * specified array. The array must be large enough to hold all the points. + * The individual array elements must be allocated by the caller as well. + * + * @param pts the array to receive the detail texture LOD function points + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void getDetailTextureFunc(Point2f[] pts) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_DETAIL_TEXTURE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("Texture2D0")); + } + } + ((Texture2DRetained)this.retained).getDetailTextureFunc(pts); + } + + + /** + * Creates a retained mode Texture2DRetained object that this + * Texture2D component object will point to. + */ + void createRetained() { + this.retained = new Texture2DRetained(); + this.retained.setSource(this); + } + + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + Texture2DRetained t2d = (Texture2DRetained) retained; + + Texture2D t = new Texture2D(t2d.getMipMapMode(), t2d.format, + t2d.width, t2d.height); + t.duplicateNodeComponent(this); + return t; + } + + /** + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @deprecated replaced with duplicateNodeComponent( + * NodeComponent originalNodeComponent, boolean forceDuplicate) + */ + public void duplicateNodeComponent(NodeComponent originalNodeComponent) { + checkDuplicateNodeComponent(originalNodeComponent); + } + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + Texture2DRetained tex = (Texture2DRetained) + originalNodeComponent.retained; + Texture2DRetained rt = (Texture2DRetained) retained; + + + rt.initDetailImage(tex.getDetailImage()); + rt.initDetailTextureMode(tex.getDetailTextureMode()); + rt.initDetailTextureLevel(tex.getDetailTextureLevel()); + rt.initDetailTextureFunc(tex.getDetailTextureFunc()); + } +} + + diff --git a/src/classes/share/javax/media/j3d/Texture2DRetained.java b/src/classes/share/javax/media/j3d/Texture2DRetained.java new file mode 100644 index 0000000..5eb60a4 --- /dev/null +++ b/src/classes/share/javax/media/j3d/Texture2DRetained.java @@ -0,0 +1,357 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; +import javax.vecmath.*; + +/** + * Texture2D is a subclass of Texture class. It extends Texture + * class by adding a constructor and a mutator method for + * setting a 2D texture image. + */ +class Texture2DRetained extends TextureRetained { + + // currently detail image is only applicable to 2D texture + + // detail texture info + + int detailTextureId = -1; + ImageComponent2DRetained detailImage = null; + DetailTextureImage detailTexture = null; + int detailTextureMode = Texture2D.DETAIL_MODULATE; + int detailTextureLevel = 2; + int numDetailTextureFuncPts = 0; + float detailTextureFuncPts[] = null; // array of pairs of floats + // first value for LOD + // second value for the fcn value + + /** + * Set detail texture image + */ + final void initDetailImage(ImageComponent2D image) { + if (image == null) { + detailImage = null; + } else { + detailImage = (ImageComponent2DRetained)image.retained; + detailImage.setTextureRef(); + } + } + + + /** + * Get detail texture image + */ + final ImageComponent2D getDetailImage() { + if (detailImage != null) { + return (ImageComponent2D)detailImage.source; + } else { + return null; + } + } + + + /** + * Set detail texture mode + */ + final void initDetailTextureMode(int mode) { + detailTextureMode = mode; + } + + + /** + * Get detail texture mode + */ + final int getDetailTextureMode() { + return detailTextureMode; + } + + + /** + * Set detail texture level + */ + final void initDetailTextureLevel(int level) { + detailTextureLevel = level; + } + + + /** + * Get detail texture level + */ + final int getDetailTextureLevel() { + return detailTextureLevel; + } + + + /** + * Set detail texture function + */ + final void initDetailTextureFunc(float[] lod, float[] pts) { + if (lod == null) { // pts will be null too. + detailTextureFuncPts = null; + numDetailTextureFuncPts = 0; + } else { + numDetailTextureFuncPts = lod.length; + if ((detailTextureFuncPts == null) || + (detailTextureFuncPts.length != lod.length * 2)) { + detailTextureFuncPts = new float[lod.length * 2]; + } + for (int i = 0, j = 0; i < lod.length; i++) { + detailTextureFuncPts[j++] = lod[i]; + detailTextureFuncPts[j++] = pts[i]; + } + } + } + + final void initDetailTextureFunc(Point2f[] pts) { + if (pts == null) { + detailTextureFuncPts = null; + numDetailTextureFuncPts = 0; + } else { + numDetailTextureFuncPts = pts.length; + if ((detailTextureFuncPts == null) || + (detailTextureFuncPts.length != pts.length * 2)) { + detailTextureFuncPts = new float[pts.length * 2]; + } + for (int i = 0, j = 0; i < pts.length; i++) { + detailTextureFuncPts[j++] = pts[i].x; + detailTextureFuncPts[j++] = pts[i].y; + } + } + } + + final void initDetailTextureFunc(float[] pts) { + if (pts == null) { + detailTextureFuncPts = null; + numDetailTextureFuncPts = 0; + } else { + numDetailTextureFuncPts = pts.length / 2; + if ((detailTextureFuncPts == null) || + (detailTextureFuncPts.length != pts.length)) { + detailTextureFuncPts = new float[pts.length]; + } + for (int i = 0; i < pts.length; i++) { + detailTextureFuncPts[i] = pts[i]; + } + } + } + + /** + * Get number of points in the detail texture LOD function + */ + final int getDetailTextureFuncPointsCount() { + return numDetailTextureFuncPts; + } + + + /** + * Copies the array of detail texture LOD function points into the + * specified arrays + */ + final void getDetailTextureFunc(float[] lod, float[] pts) { + if (detailTextureFuncPts != null) { + for (int i = 0, j = 0; i < numDetailTextureFuncPts; i++) { + lod[i] = detailTextureFuncPts[j++]; + pts[i] = detailTextureFuncPts[j++]; + } + } + } + + final void getDetailTextureFunc(Point2f[] pts) { + if (detailTextureFuncPts != null) { + for (int i = 0, j = 0; i < numDetailTextureFuncPts; i++) { + pts[i].x = detailTextureFuncPts[j++]; + pts[i].y = detailTextureFuncPts[j++]; + } + } + } + + + /** + * internal method only -- returns the detail texture LOD function + */ + final float[] getDetailTextureFunc() { + return detailTextureFuncPts; + } + + synchronized void initMirrorObject() { + + super.initMirrorObject(); + + Texture2DRetained mirrorTexture = (Texture2DRetained)mirror; + + // detail texture info + mirrorTexture.detailImage = detailImage; + mirrorTexture.detailTextureMode = detailTextureMode; + mirrorTexture.detailTextureLevel = detailTextureLevel; + mirrorTexture.detailTexture = null; + mirrorTexture.numDetailTextureFuncPts = numDetailTextureFuncPts; + + if (detailTextureFuncPts == null) { + mirrorTexture.detailTextureFuncPts = null; + } else { + if ((mirrorTexture.detailTextureFuncPts == null) || + (mirrorTexture.detailTextureFuncPts.length != + detailTextureFuncPts.length)) { + mirrorTexture.detailTextureFuncPts = + new float[detailTextureFuncPts.length]; + } + for (int i = 0; i < detailTextureFuncPts.length; i++) { + mirrorTexture.detailTextureFuncPts[i] = + detailTextureFuncPts[i]; + } + + // add detail texture to the user list of the image + // only if detail texture is to be used + if ((mirrorTexture.detailImage != null) && + (mirrorTexture.magFilter >= Texture2D.LINEAR_DETAIL) && + (mirrorTexture.magFilter <= Texture2D.LINEAR_DETAIL_ALPHA)) { + mirrorTexture.detailImage.addUser(mirrorTexture); + } + } + } + + void clearLive(int refCount) { + super.clearLive(refCount); + + // remove detail texture from the user list of the image + if ((detailImage != null) && + (magFilter >= Texture2D.LINEAR_DETAIL) && + (magFilter <= Texture2D.LINEAR_DETAIL_ALPHA)) { + detailImage.removeUser(mirror); + } + } + + // overload the incTextureBinRefCount method to take care + // of detail texture ref as well + // This method is called from RenderBin when a new TextureBin + // is created. And all the referenced textures in that TextureBin + // will be notified to increment the TextureBin reference count. + + void incTextureBinRefCount(TextureBin tb) { + super.incTextureBinRefCount(tb); + + // increment detail texture ref count + + if ((detailImage != null) && + (magFilter >= Texture2D.LINEAR_DETAIL) && + (magFilter <= Texture2D.LINEAR_DETAIL_ALPHA)) { + if (detailTexture == null) { + detailTexture = detailImage.getDetailTexture(); + } + + detailTexture.incTextureBinRefCount(format, tb); + } + } + + // This method is called from AttributeBin when a TextureBin + // is to be removed. And all the referenced textures in that TextureBin + // will be notified to decrement the TextureBin reference count. + // And if detail texture exists, we need to decrement the + // TextureBin reference count of the detail texture as well. + + void decTextureBinRefCount(TextureBin tb) { + super.decTextureBinRefCount(tb); + + // decrement detail texture ref count + + if (detailTexture != null) { + detailTexture.decTextureBinRefCount(format, tb); + } + } + + + + native void bindDetailTexture(long ctx, int objectId); + + native void updateTextureImage(long ctx, + int numLevels, + int level, + int internalFormat, int format, + int width, int height, + int boundaryWidth, byte[] imageYup); + + native void updateTextureSubImage(long ctx, + int level, int xoffset, int yoffset, + int internalFormat,int format, + int imgXOffset, int imgYOffset, + int tilew, + int width, int height, + byte[] image); + + native void updateDetailTextureParameters(long ctx, + int detailTextureMode, + int detailTextureLevel, + int nPts, float[] pts); + // wrapper to the native call + + void updateTextureImage(Canvas3D cv, int face, + int numLevels, int level, + int format, int storedFormat, + int width, int height, + int boundaryWidth, + byte[] data) { + + updateTextureImage(cv.ctx, numLevels, level, format, + storedFormat, width, height, + boundaryWidth, data); + } + + + + // wrapper to the native call + + void updateTextureSubImage(Canvas3D cv, int face, int level, + int xoffset, int yoffset, int format, + int storedFormat, int imgXOffset, + int imgYOffset, int tileWidth, + int width, int height, byte[] data) { + + updateTextureSubImage(cv.ctx, level, xoffset, yoffset, format, + storedFormat, imgXOffset, imgYOffset, + tileWidth, width, height, data); + } + + + void updateNative(Canvas3D cv) { + + // update mipmap texture + + super.updateNative(cv); + + + // update detail texture if exists + + if (detailTexture != null) { + detailTexture.updateNative(cv, format); + } + } + + + // update texture parameters + + void updateTextureFields(Canvas3D cv) { + + super.updateTextureFields(cv); + + // update detail texture parameters if applicable + + if (detailTexture != null) { + + updateDetailTextureParameters(cv.ctx, detailTextureMode, + detailTextureLevel, numDetailTextureFuncPts, + detailTextureFuncPts); + } + } +} + diff --git a/src/classes/share/javax/media/j3d/Texture3D.java b/src/classes/share/javax/media/j3d/Texture3D.java new file mode 100644 index 0000000..6cceb1d --- /dev/null +++ b/src/classes/share/javax/media/j3d/Texture3D.java @@ -0,0 +1,220 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Texture3D is a subclass of Texture class. It extends Texture + * class by adding a third coordinate, constructor and a mutator + * method for setting a 3D texture image. + * If 3D texture mapping is not supported on a particular Canvas3D, + * 3D texture mapping is ignored for that canvas. + * + * @see Canvas3D#queryProperties + */ + +public class Texture3D extends Texture { + + /** + * Constructs a Texture3D object with default parameters. + * The default values are as follows: + * <ul> + * depth : 0<br> + * boundary mode R : WRAP<br> + * </ul> + * <p> + * Note that the default constructor creates a texture object with + * a width, height, and depth of 0 and is, therefore, not useful. + */ + public Texture3D() { + super(); + } + + /** + * Constructs an empty Texture3D object with specified mipmapMode + * format, width, height, and depth. Image at base level must be set by + * the application using 'setImage' method. If mipmapMode is + * set to MULTI_LEVEL_MIPMAP, images for base level through + * maximum level must be set. + * @param mipmapMode type of mipmap for this Texture: One of + * BASE_LEVEL, MULTI_LEVEL_MIPMAP. + * @param format data format of Textures saved in this object. + * One of INTENSITY, LUMINANCE, ALPHA, LUMINANCE_ALPHA, RGB, RGBA. + * @param width width of image at level 0. Must be power of 2. + * @param height height of image at level 0. Must be power of 2. + * @param depth depth of image at level 0. Must be power of 2. + * @exception IllegalArgumentException if width or height are NOT + * power of 2 OR invalid format/mipmapMode is specified. + */ + public Texture3D(int mipmapMode, + int format, + int width, + int height, + int depth) { + + super(mipmapMode, format, width, height); + int depthPower = getPowerOf2(depth); + if (depthPower == -1) + throw new IllegalArgumentException(J3dI18N.getString("Texture3D1")); + + ((Texture3DRetained)this.retained).setDepth(depth); + } + + /** + * Constructs an empty Texture3D object with specified mipmapMode + * format, width, height, depth, and boundaryWidth. + * Image at base level must be set by + * the application using 'setImage' method. If mipmapMode is + * set to MULTI_LEVEL_MIPMAP, images for base level through + * maximum level must be set. + * @param mipmapMode type of mipmap for this Texture: One of + * BASE_LEVEL, MULTI_LEVEL_MIPMAP. + * @param format data format of Textures saved in this object. + * One of INTENSITY, LUMINANCE, ALPHA, LUMINANCE_ALPHA, RGB, RGBA. + * @param width width of image at level 0. Must be power of 2. + * @param height height of image at level 0. Must be power of 2. + * @param depth depth of image at level 0. Must be power of 2. + * @param boundaryWidth width of the boundary. + * @exception IllegalArgumentException if width or height are NOT + * power of 2 OR invalid format/mipmapMode is specified, or + * if the boundaryWidth < 0 + * + * @since Java 3D 1.3 + */ + public Texture3D(int mipmapMode, + int format, + int width, + int height, + int depth, + int boundaryWidth) { + + super(mipmapMode, format, width, height, boundaryWidth); + int depthPower = getPowerOf2(depth); + if (depthPower == -1) + throw new IllegalArgumentException(J3dI18N.getString("Texture3D1")); + + ((Texture3DRetained)this.retained).setDepth(depth); + } + + /** + * Sets the boundary mode for the R coordinate in this texture object. + * @param boundaryModeR the boundary mode for the R coordinate, + * one of: CLAMP, WRAP, CLAMP_TO_EDGE, or CLAMP_TO_BOUNDARY + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if <code>boundaryModeR</code> + * is a value other than <code>CLAMP</code>, <code>WRAP</code>, + * <code>CLAMP_TO_EDGE</code>, or <code>CLAMP_TO_BOUNDARY</code>. + */ + public void setBoundaryModeR(int boundaryModeR) { + checkForLiveOrCompiled(); + switch (boundaryModeR) { + case Texture.CLAMP: + case Texture.WRAP: + case Texture.CLAMP_TO_EDGE: + case Texture.CLAMP_TO_BOUNDARY: + break; + default: + throw new IllegalArgumentException(J3dI18N.getString("Texture31")); + } + ((Texture3DRetained)this.retained).initBoundaryModeR(boundaryModeR); + } + + /** + * Retrieves the boundary mode for the R coordinate. + * @return the current boundary mode for the R coordinate. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + public int getBoundaryModeR() { + if (isLiveOrCompiled()) + if(!this.getCapability(Texture.ALLOW_BOUNDARY_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture3D0")); + return ((Texture3DRetained)this.retained).getBoundaryModeR(); + } + + /** + * Retrieves the depth of this Texture3D object. + * @return the depth of this Texture3D object. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getDepth() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_SIZE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("Texture3D2")); + + return ((Texture3DRetained)this.retained).getDepth(); + } + + /** + * Creates a retained mode Texture3DRetained object that this + * Texture3D component object will point to. + */ + void createRetained() { + this.retained = new Texture3DRetained(); + this.retained.setSource(this); + } + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + Texture3DRetained t3d = (Texture3DRetained) retained; + Texture3D t = new Texture3D(t3d.getMipMapMode(), t3d.format, + t3d.width, t3d.height, t3d.depth); + t.duplicateNodeComponent(this); + return t; + } + + + /** + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @deprecated replaced with duplicateNodeComponent( + * NodeComponent originalNodeComponent, boolean forceDuplicate) + */ + public void duplicateNodeComponent(NodeComponent originalNodeComponent) { + checkDuplicateNodeComponent(originalNodeComponent); + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + ((Texture3DRetained) retained).initBoundaryModeR(((Texture3DRetained) + originalNodeComponent.retained).getBoundaryModeR()); + + } + +} diff --git a/src/classes/share/javax/media/j3d/Texture3DRetained.java b/src/classes/share/javax/media/j3d/Texture3DRetained.java new file mode 100644 index 0000000..04c06fc --- /dev/null +++ b/src/classes/share/javax/media/j3d/Texture3DRetained.java @@ -0,0 +1,199 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.Enumeration; +import java.util.BitSet; + +/** + * Texture3D is a subclass of Texture class. It extends Texture + * class by adding a third co-ordinate, constructor and a mutator + * method for setting a 3D texture image. + */ + +class Texture3DRetained extends TextureRetained { + // Boundary mode for R coordinate (wrap, clamp) + int boundaryModeR = Texture.WRAP; + int depth = 1; // Depth (num slices) of texture map (2**p) + + final void setDepth(int depth) { + this.depth = depth; + } + + final int getDepth() { + return this.depth; + } + + /** + * Sets the boundary mode for the R coordinate in this texture object. + * @param boundaryModeR the boundary mode for the R coordinate, + * one of: CLAMP or WRAP. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final void initBoundaryModeR(int boundaryModeR) { + this.boundaryModeR = boundaryModeR; + } + + /** + * Retrieves the boundary mode for the R coordinate. + * @return the current boundary mode for the R coordinate. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final int getBoundaryModeR() { + return boundaryModeR; + } + + /** + * This method updates the native context. + */ + native void bindTexture(long ctx, int objectId, boolean enable); + + native void updateTextureBoundary(long ctx, + int boundaryModeS, int boundaryModeT, + int boundaryModeR, float boundaryRed, + float boundaryGreen, float boundaryBlue, + float boundaryAlpha); + + native void updateTextureFilterModes(long ctx, + int minFilter, int magFilter); + + native void updateTextureSharpenFunc(long ctx, + int numSharpenTextureFuncPts, + float[] sharpenTextureFuncPts); + + native void updateTextureFilter4Func(long ctx, + int numFilter4FuncPts, + float[] filter4FuncPts); + + native void updateTextureAnisotropicFilter(long ctx, float degree); + + + native void updateTextureImage(long ctx, int numLevels, int level, + int format, int internalFormat, int width, + int height, int depth, + int boundaryWidth, byte[] imageYup); + + native void updateTextureSubImage(long ctx, int level, + int xoffset, int yoffset, int zoffset, + int internalFormat, int format, + int imgXoffset, int imgYoffset, int imgZoffset, + int tilew, int tileh, + int width, int height, int depth, + byte[] imageYup); + + + // get an ID for Texture3D + + int getTextureId() { + return (VirtualUniverse.mc.getTexture3DId()); + } + + + // get a Texture3D Id + + void freeTextureId(int id) { + synchronized (resourceLock) { + if (objectId == id) { + objectId = -1; + VirtualUniverse.mc.freeTexture3DId(id); + } + } + } + + + // load level 0 image with null data pointer, just to enable + // mipmapping when level 0 is not the base level + + void updateTextureDimensions(Canvas3D cv) { + updateTextureImage(cv.ctx, maxLevels, 0, + format, ImageComponentRetained.BYTE_RGBA, + width, height, depth, boundaryWidth, null); + } + + + void updateTextureBoundary(Canvas3D cv) { + updateTextureBoundary(cv.ctx, + boundaryModeS, boundaryModeT, boundaryModeR, + boundaryColor.x, boundaryColor.y, + boundaryColor.z, boundaryColor.w); + + } + + void reloadTextureImage(Canvas3D cv, int face, int level, + ImageComponentRetained image, int numLevels) { + +/* + System.out.println("Texture3D.reloadTextureImage: level= " + level + + " image.imageYup= " + image.imageYup + " w= " + image.width + + " h= " + image.height + " d= " + depth + + " numLevels= " + numLevels); +*/ + + + updateTextureImage(cv.ctx, numLevels, level, format, + image.storedYupFormat, + image.width, image.height, depth, + boundaryWidth, image.imageYup); + + } + + void reloadTextureSubImage(Canvas3D cv, int level, int face, + ImageComponentUpdateInfo info, + ImageComponentRetained image) { + int x = info.x, + y = info.y, + z = info.z, + width = info.width, + height = info.height; + + int xoffset = x - image.minX; + int yoffset = y - image.minY; + + updateTextureSubImage(cv.ctx, level, xoffset, yoffset, z, + format, image.storedYupFormat, + xoffset, yoffset, z, + image.width, image.height, + width, height, 1, image.imageYup); + } + + + + protected void finalize() { + + if (objectId > 0) { + // memory not yet free + // send a message to the request renderer + synchronized (VirtualUniverse.mc.contextCreationLock) { + boolean found = false; + + for (Enumeration e = Screen3D.deviceRendererMap.elements(); + e.hasMoreElements(); ) { + Renderer rdr = (Renderer) e.nextElement(); + J3dMessage renderMessage = VirtualUniverse.mc.getMessage(); + renderMessage.threads = J3dThread.RENDER_THREAD; + renderMessage.type = J3dMessage.RENDER_IMMEDIATE; + renderMessage.universe = null; + renderMessage.view = null; + renderMessage.args[0] = null; + renderMessage.args[1] = new Integer(objectId); + renderMessage.args[2] = "3D"; + rdr.rendererStructure.addMessage(renderMessage); + } + objectId = -1; + } + VirtualUniverse.mc.setWorkForRequestRenderer(); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/TextureAttributes.java b/src/classes/share/javax/media/j3d/TextureAttributes.java new file mode 100644 index 0000000..cb41875 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TextureAttributes.java @@ -0,0 +1,1403 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color4f; + +/** + * The TextureAttributes object defines attributes that apply to + * texture mapping. + * The texture attributes include the following:<P> + * <UL> + * <LI>Texture mode - defines how the object and texture colors + * are blended. The mode may be one of the following:</LI><P> + * <UL> + * <LI>MODULATE - modulates the incoming color with the texture + * color.<P> + * <UL> + * C' = C Ct + * </UL></LI><P> + * <LI>DECAL - applies the texture color to the incoming color as a decal.<P> + * <UL> + * C'<sub>rgb</sub> = C<sub>rgb</sub> (1 - Ct<sub>a</sub>) + Ct<sub>rgb</sub> Ct<sub>a</sub><P> + * C'<sub>a</sub> = C<sub>a</sub> + * </UL></LI><P> + * <LI>BLEND - blends the texture blend color with the incoming color.<P> + * <UL> + * C'<sub>rgb</sub> = C<sub>rgb</sub> (1 - Ct<sub>rgb</sub>) + Cb<sub>rgb</sub> Ct<sub>rgb</sub><P> + * C'<sub>a</sub> = C<sub>a</sub> Ct<sub>a</sub><P> + * </UL> + * Note that if the texture format is INTENSITY, alpha is computed identically + * to red, green, and blue: <P> + * <UL> + * C'<sub>a</sub> = C<sub>a</sub> (1 - Ct<sub>a</sub>) + Cb<sub>a</sub> Ct<sub>a</sub> + * </UL></LI><P> + * <LI>REPLACE - replaces the incoming color with the texture color.<P> + * <UL> + * C' = Ct <P> + * </UL></LI><P> + * <LI>COMBINE - combines the object color with the texture color or texture + * blend color according to the combine operation as specified in the + * texture combine mode. </LI><P> + * <p> + * </UL> + * C = Incoming color to the texture unit state. For texture unit state 0, C is the object color + * Ct = Texture color<br> + * Cb = Texture blend color<br> + * <p> + * <LI>Combine Mode - defines the combine operation when texture mode + * specifies COMBINE. The combine mode includes the following:<p> + * <UL> + * <LI>COMBINE_REPLACE<P> + * <UL> + * C' = C<sub>0</sub> <P> + * </UL></LI><P> + * <LI>COMBINE_MODULATE<P> + * <UL> + * C' = C<sub>0</sub> C<sub>1</sub> + * </UL></LI><P> + * <LI>COMBINE_ADD<P> + * <UL> + * C' = C<sub>0</sub> + C<sub>1</sub> <P> + * </UL></LI><P> + * <LI>COMBINE_ADD_SIGNED <P> + * <UL> + * C' = C<sub>0</sub> + C<sub>1</sub> - 0.5 <P> + * </UL></LI><P> + * <LI>COMBINE_SUBTRACT <P> + * <UL> + * C' = C<sub>0</sub> - C<sub>1</sub> <P> + * </UL></LI><P> + * <LI>COMBINE_INTERPOLATE<P> + * <UL> + * C' = C<sub>0</sub> C<sub>2</sub> + C<sub>1</sub> (1 - C<sub>2</sub>) <P> + * </UL></LI><P> + * <LI>COMBINE_DOT3<P> + * <UL> + * C' = 4 * ( + * (C<sub>0<sub>r</sub></sub> - 0.5) * (C<sub>1<sub>r</sub></sub> - 0.5) + + * (C<sub>0<sub>g</sub></sub> - 0.5) * (C<sub>1<sub>g</sub></sub> - 0.5) + + * (C<sub>0<sub>b</sub></sub> - 0.5) * (C<sub>1<sub>b</sub></sub> - 0.5))<P> + * where C<sub>N<sub>x</sub></sub> is the x component of the Nth color operand + * in the combine operation.<P> + * The value C' will be placed to the all three r,g,b components or the + * a component of the output. + * </UL></LI><P> + * </UL></LI><P> + * where C<sub>0</sub>, C<sub>1</sub> and C<sub>2</sub> are determined by + * the color source, and the color operand. + * </UL></LI><P> + * <UL> + * <LI>Combine Color Source - defines the source for a color operand in the + * combine operation. The color source includes the following:<p> + * <UL> + * <LI> COMBINE_OBJECT_COLOR - object color<P> + * <LI> COMBINE_TEXTURE_COLOR - texture color<P> + * <LI> COMBINE_CONSTANT_COLOR - texture blend color<P> + * <LI> COMBINE_PREVIOUS_TEXTURE_UNIT_STATE - color from the previous texture + * unit state. For texture unit state 0, this is equivalent to + * COMBINE_OBJECT_COLOR.<P> + * </UL></LI><P> + * <LI>Combine Color Function - specifies the function for a color operand + * in the combine operation. The valid values are:<P> + * <UL> + * <LI>COMBINE_SRC_COLOR - the color function is f = C<sub>rgb</sub><P> + * <LI>COMBINE_ONE_MINUS_SRC_COLOR - the color function is f = (1 - C<sub>rgb</sub>)<P> + * <LI>COMBINE_SRC_ALPHA - the color function is f = C<sub>a</sub><P> + * <LI>COMBINE_ONE_MINUS_SRC_ALPHA - the color function is f = (1 - C<sub>a</sub>)<P> + * </UL></LI><P> + * <LI>Combine scale factor - specifies the scale factor to be applied to + * the output color of the combine operation. The valid values include: + * 1, 2, or 4.</LI><P> + * <LI>Transform - the texture transform object used to transform + * texture coordinates. The texture transform can translate, scale, + * or rotate the texture coordinates before the texture is applied + * to the object.</LI><P> + * <LI>Blend color - the constant texture blend color</LI><P> + * <LI>Perspective correction - the perspective correction mode + * used for color and texture coordinate interpolation. One of + * the following:</LI><P> + * <UL> + * <LI>NICEST - uses the nicest (highest quality) available + * method for texture mapping perspective correction.</LI><P> + * <LI>FASTEST - uses the fastest available method for texture + * mapping perspective correction.</LI><P> + * </UL> + * <LI>Texture color table - defines a table that is used to look up + * texture colors before applying the texture mode.</LI> + * </UL> + * + * @see Appearance + * @see Canvas3D#queryProperties + */ +public class TextureAttributes extends NodeComponent { + /** + * Specifies that this TextureAttributes object allows + * reading its texture mode component + * information and perspective correction mode. + */ + public static final int + ALLOW_MODE_READ = CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_MODE_READ; + + /** + * Specifies that this TextureAttributes object allows + * writing its texture mode component + * information and perspective correction mode. + */ + public static final int + ALLOW_MODE_WRITE = CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_MODE_WRITE; + + /** + * Specifies that this TextureAttributes object allows + * reading its texture blend color component + * information. + */ + public static final int + ALLOW_BLEND_COLOR_READ = CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_BLEND_COLOR_READ; + + /** + * Specifies that this TextureAttributes object allows + * writing its texture blend color component + * information. + */ + public static final int + ALLOW_BLEND_COLOR_WRITE = CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_BLEND_COLOR_WRITE; + + /** + * Specifies that this TextureAttributes object allows + * reading its texture transform component + * information. + */ + public static final int + ALLOW_TRANSFORM_READ = CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_TRANSFORM_READ; + + /** + * Specifies that this TextureAttributes object allows + * writing its texture transform component + * information. + */ + public static final int + ALLOW_TRANSFORM_WRITE = CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_TRANSFORM_WRITE; + + /** + * Specifies that this TextureAttributes object allows + * reading its texture color table component + * information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_COLOR_TABLE_READ = + CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_COLOR_TABLE_READ; + + /** + * Specifies that this TextureAttributes object allows + * writing its texture color table component + * information. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_COLOR_TABLE_WRITE = + CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_COLOR_TABLE_WRITE; + + /** + * Specifies that this TextureAttributes object allows + * reading its texture combine mode information. (e.g. combine mode, + * combine color source, combine color function, combine scale factor) + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_COMBINE_READ = + CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_COMBINE_READ; + + /** + * Specifies that this TextureAttributes object allows + * writing its texture combine mode information. (e.g. combine mode, + * combine color source, combine color function, combine scale factor) + * + * @since Java 3D 1.3 + */ + public static final int ALLOW_COMBINE_WRITE = + CapabilityBits.TEXTURE_ATTRIBUTES_ALLOW_COMBINE_WRITE; + + + /** + * Use the fastest available method for perspective correction. + * @see #setPerspectiveCorrectionMode + */ + public static final int FASTEST = 0; + + /** + * Use the nicest (highest quality) available method for texture + * mapping perspective correction. + * @see #setPerspectiveCorrectionMode + */ + public static final int NICEST = 1; + + /** + * Modulate the object color with the texture color. + * @see #setTextureMode + */ + public static final int MODULATE = 2; + + /** + * Apply the texture color to the object as a decal. + * @see #setTextureMode + */ + public static final int DECAL = 3; + + /** + * Blend the texture blend color with the object color. + * @see #setTextureMode + */ + public static final int BLEND = 4; + + /** + * Replace the object color with the texture color. + * @see #setTextureMode + */ + public static final int REPLACE = 5; + + /** + * Combine the object color with texture color as specified in + * the combine mode. + * + * @see #setTextureMode + * @since Java 3D 1.3 + */ + public static final int COMBINE = 6; + + + /** + * Replace the input color with the specified color. + * + * @since Java 3D 1.3 + * @see #setCombineRgbMode + * @see #setCombineAlphaMode + */ + public static final int COMBINE_REPLACE = 0; + + /** + * Modulates one color with another color. + * + * @since Java 3D 1.3 + * @see #setCombineRgbMode + * @see #setCombineAlphaMode + */ + public static final int COMBINE_MODULATE = 1; + + /** + * Add two colors. + * + * @since Java 3D 1.3 + * @see #setCombineRgbMode + * @see #setCombineAlphaMode + */ + public static final int COMBINE_ADD = 2; + + /** + * Add two colors plus an implicit offset. + * + * @since Java 3D 1.3 + * @see #setCombineRgbMode + * @see #setCombineAlphaMode + */ + public static final int COMBINE_ADD_SIGNED = 3; + + /** + * Subtract one color from another color. + * + * @since Java 3D 1.3 + * @see #setCombineRgbMode + * @see #setCombineAlphaMode + */ + public static final int COMBINE_SUBTRACT = 4; + + /** + * Interpolate two colors with a factor. + * + * @since Java 3D 1.3 + * @see #setCombineRgbMode + * @see #setCombineAlphaMode + */ + public static final int COMBINE_INTERPOLATE = 5; + + /** + * Dot product of two colors. + * + * @since Java 3D 1.3 + * @see #setCombineRgbMode + * @see #setCombineAlphaMode + */ + public static final int COMBINE_DOT3 = 6; + + + /** + * Object color coming into the texturing state. + * + * @since Java 3D 1.3 + * @see #setCombineRgbSource + * @see #setCombineAlphaSource + */ + public static final int COMBINE_OBJECT_COLOR = 0; + + /** + * Texture color of the corresponding texture unit state. + * + * @since Java 3D 1.3 + * @see #setCombineRgbSource + * @see #setCombineAlphaSource + */ + public static final int COMBINE_TEXTURE_COLOR = 1; + + /** + * Texture blend color. + * + * @since Java 3D 1.3 + * @see #setCombineRgbSource + * @see #setCombineAlphaSource + */ + public static final int COMBINE_CONSTANT_COLOR = 2; + + /** + * Color from the previous texture unit state. + * + * @since Java 3D 1.3 + * @see #setCombineRgbSource + * @see #setCombineAlphaSource + */ + public static final int COMBINE_PREVIOUS_TEXTURE_UNIT_STATE = 3; + + /** + * Color function is f = C<sub>rgb</sub> + * + * @since Java 3D 1.3 + * @see #setCombineRgbFunction + */ + public static final int COMBINE_SRC_COLOR = 0; + + /** + * Color function is f = (1 - C<sub>rgb</sub>) + * + * @since Java 3D 1.3 + * @see #setCombineRgbFunction + */ + public static final int COMBINE_ONE_MINUS_SRC_COLOR = 1; + + /** + * Color function is f = C<sub>a</sub> + * + * @since Java 3D 1.3 + * @see #setCombineRgbFunction + * @see #setCombineAlphaFunction + */ + public static final int COMBINE_SRC_ALPHA = 2; + + /** + * Color function is f = (1 - C<sub>a</sub>) + * + * @since Java 3D 1.3 + * @see #setCombineRgbFunction + * @see #setCombineAlphaFunction + */ + public static final int COMBINE_ONE_MINUS_SRC_ALPHA = 3; + + /** + * Constructs a TextureAttributes object with default parameters. + * The default values are as follows: + * <ul> + * texture mode : REPLACE<br> + * blend color : black (0,0,0,0)<br> + * transform : identity<br> + * perspective correction mode : NICEST<br> + * texture color table : null<br> + * combine rgb mode : COMBINE_MODULATE<br> + * combine alpha mode : COMBINE_MODULATE<br> + * combine rgb source : + * <ul> + * C<sub>0</sub>=COMBINE_TEXTURE_COLOR<br> + * C<sub>1</sub>=COMBINE_PREVIOUS_TEXTURE_UNIT_STATE<br> + * C<sub>2</sub>=COMBINE_CONSTANT_COLOR<br> + * </ul> + * combine alpha source : + * <ul> + * C<sub>0</sub>=COMBINE_TEXTURE_COLOR<br> + * C<sub>1</sub>=COMBINE_PREVIOUS_TEXTURE_UNIT_STATE<br> + * C<sub>2</sub>=COMBINE_CONSTANT_COLOR<br> + * </ul> + * combine rgb function : COMBINE_SRC_COLOR<br> + * combine alpha function : COMBINE_SRC_ALPHA<br> + * combine rgb scale : 1<br> + * combine alpha scale : 1<br> + * </ul> + */ + public TextureAttributes() { + } + + /** + * Constructs a TextureAttributes object with the specified values. + * @param textureMode the texture mode; one of <code>MODULATE</code>, + * <code>DECAL</code>, <code>BLEND</code>, <code>REPLACE</code>, or + * <code>COMBINE</code> + * @param transform the transform object, used to transform texture + * coordinates + * @param textureBlendColor the texture constant color + * @param perspCorrectionMode the perspective correction mode to + * be used for color and/or texture coordinate interpolation; + * one of <code>NICEST</code> or <code>FASTEST</code> + * @exception IllegalArgumentException if <code>textureMode</code> + * is a value other than <code>MODULATE</code>, + * <code>DECAL</code>, <code>BLEND</code>, <code>REPLACE</code>, or + * <code>COMBINE</code> + * @exception IllegalArgumentException if mode value is other + * than <code>FASTEST</code> or <code>NICEST</code>. + */ + public TextureAttributes(int textureMode, Transform3D transform, + Color4f textureBlendColor, + int perspCorrectionMode) { + + if ((textureMode < MODULATE) || (textureMode > COMBINE)) { + throw new IllegalArgumentException(J3dI18N.getString("TextureAttributes10")); + } + + if ((perspCorrectionMode != FASTEST) && + (perspCorrectionMode!= NICEST)) { + throw new IllegalArgumentException(J3dI18N.getString("TextureAttributes9")); + } + + ((TextureAttributesRetained)this.retained).initTextureMode(textureMode); + ((TextureAttributesRetained)this.retained).initTextureBlendColor(textureBlendColor); + ((TextureAttributesRetained)this.retained).initTextureTransform(transform); + ((TextureAttributesRetained)this.retained).initPerspectiveCorrectionMode(perspCorrectionMode); + } + + /** + * Sets the texture mode parameter for this + * appearance component object. + * @param textureMode the texture mode, one of: <code>MODULATE</code>, + * <code>DECAL</code>, <code>BLEND</code>, <code>REPLACE</code>, or + * <code>COMBINE</code> + * @exception IllegalArgumentException if <code>textureMode</code> + * is a value other than <code>MODULATE</code>, + * <code>DECAL</code>, <code>BLEND</code>, <code>REPLACE</code>, or + * <code>COMBINE</code> + * + * @see Canvas3D#queryProperties + */ + public void setTextureMode(int textureMode) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MODE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes0")); + + if ((textureMode < MODULATE) || (textureMode > COMBINE)) { + throw new IllegalArgumentException(J3dI18N.getString("TextureAttributes10")); + } + + if (isLive()) + ((TextureAttributesRetained)this.retained).setTextureMode(textureMode); + else + ((TextureAttributesRetained)this.retained).initTextureMode(textureMode); + } + + /** + * Gets the texture mode parameter for this + * texture attributes object. + * @return textureMode the texture mode + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getTextureMode() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes1")); + + return ((TextureAttributesRetained)this.retained).getTextureMode(); + } + + /** + * Sets the texture constant color for this + * texture attributes object. + * @param textureBlendColor the texture constant color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setTextureBlendColor(Color4f textureBlendColor) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_BLEND_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes2")); + + if (isLive()) + ((TextureAttributesRetained)this.retained).setTextureBlendColor(textureBlendColor); + else + ((TextureAttributesRetained)this.retained).initTextureBlendColor(textureBlendColor); + } + + /** + * Sets the texture blend color for this + * appearance component object. + * @param r the red component of the color + * @param g the green component of the color + * @param b the blue component of the color + * @param a the alpha component of the color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setTextureBlendColor(float r, float g, float b, float a) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_BLEND_COLOR_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes3")); + + if (isLive()) + ((TextureAttributesRetained)this.retained).setTextureBlendColor(r, g, b, a); + else + ((TextureAttributesRetained)this.retained).initTextureBlendColor(r, g, b, a); + } + + /** + * Gets the texture blend color for this + * appearance component object. + * @param textureBlendColor the vector that will receive the texture + * constant color + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getTextureBlendColor(Color4f textureBlendColor) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_BLEND_COLOR_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes4")); + + ((TextureAttributesRetained)this.retained).getTextureBlendColor(textureBlendColor); + } + + /** + * Sets the texture transform object used to transform texture + * coordinates. A copy of the specified Transform3D object is + * stored in this TextureAttributes object. + * @param transform the new transform object + * @exception CapabilityNotSetException if the method is called + * when this object is part of live or compiled scene graph. + */ + public void setTextureTransform(Transform3D transform) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TRANSFORM_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes5")); + + if (isLive()) + ((TextureAttributesRetained)this.retained).setTextureTransform(transform); + else + ((TextureAttributesRetained)this.retained).initTextureTransform(transform); + } + + /** + * Retrieves a copy of the texture transform object. + * @param transform the transform object that will receive the + * current texture transform + * @exception CapabilityNotSetException if the method is called + * when this object is part of live or compiled scene graph. + */ + public void getTextureTransform(Transform3D transform) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_TRANSFORM_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes6")); + + ((TextureAttributesRetained)this.retained).getTextureTransform(transform); + } + + /** + * Sets perspective correction mode to be used for color + * and/or texture coordinate interpolation. + * A value of <code>NICEST</code> indicates that perspective correction should be + * performed and that the highest quality method should be used. + * A value of <code>FASTEST</code> indicates that the most efficient perspective + * correction method should be used. + * @param mode one of <code>NICEST</code> or <code>FASTEST</code> + * The default value is <code>NICEST</code>. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if mode value is other + * than <code>FASTEST</code> or <code>NICEST</code>. + */ + public void setPerspectiveCorrectionMode(int mode) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MODE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes7")); + + if ((mode != FASTEST) && (mode!= NICEST)) + throw new IllegalArgumentException(J3dI18N.getString("TextureAttributes9")); + + if (isLive()) + ((TextureAttributesRetained)this.retained).setPerspectiveCorrectionMode(mode); + else + ((TextureAttributesRetained)this.retained).initPerspectiveCorrectionMode(mode); + } + + /** + * Gets perspective correction mode value. + * @return mode the value of perspective correction mode + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getPerspectiveCorrectionMode() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes8")); + return ((TextureAttributesRetained)this.retained).getPerspectiveCorrectionMode(); + } + + /** + * Sets the texture color table from the specified table. The + * individual integer array elements are copied. The array is + * indexed first by color component (<i>r</i>, <i>g</i>, <i>b</i>, + * and <i>a</i>, respectively) and then by color value; + * <code>table.length</code> defines the number of color + * components and <code>table[0].length</code> defines the texture + * color table size. If the table is non-null, the number of + * color components must either be 3, for <i>rgb</i> data, or 4, + * for <i>rgba</i> data. The size of each array for each color + * component must be the same and must be a power of 2. If table + * is null or if the texture color table size is 0, the texture + * color table is disabled. If the texture color table size is + * greater than the device-dependent maximum texture color table + * size for a particular Canvas3D, the texture color table is + * ignored for that canvas. + * + * <p> + * When enabled, the texture color table is applied after the + * texture filtering operation and before texture application. + * Each of the <i>r</i>, <i>g</i>, <i>b</i>, and <i>a</i> + * components are clamped to the range [0,1], multiplied by + * <code>textureColorTableSize-1</code>, and rounded to the + * nearest integer. The resulting value for each component is + * then used as an index into the respective table for that + * component. If the texture color table contains 3 components, + * alpha is passed through unmodified. + * + * @param table the new texture color table + * + * @exception IllegalArgumentException if <code>table.length</code> + * is not 3 or 4, or if the arrays for each component are not all + * the same length, or if the texture color table size + * is not a power of 2 + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.2 + */ + public void setTextureColorTable(int[][] table) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLOR_TABLE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes11")); + + if (isLive()) + ((TextureAttributesRetained)this.retained).setTextureColorTable(table); + else + ((TextureAttributesRetained)this.retained).initTextureColorTable(table); + } + + /** + * Retrieves the texture color table and copies it into the + * specified array. If the current texture color table is null, + * no values are copied. + * + * @param table the array that will receive a copy of the + * texture color table from this TextureAttributes object. + * The array must be allocated by the caller and must be large + * enough to hold the entire table (that is, + * <code>int[numTextureColorTableComponents][textureColorTableSize]</code>). + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public void getTextureColorTable(int[][] table) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_COLOR_TABLE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureAttributes12")); + ((TextureAttributesRetained)this.retained).getTextureColorTable(table); + return; + } + + /** + * Retrieves the number of color components in the current texture + * color table. A value of 0 is returned if the texture color + * table is null. + * + * @return the number of color components in the texture color + * table, or 0 if the table is null + * + * @since Java 3D 1.2 + */ + public int getNumTextureColorTableComponents() { + return (((TextureAttributesRetained)this.retained).getNumTextureColorTableComponents()); + } + + /** + * Retrieves the size of the current texture color table. A value + * of 0 is returned if the texture color table is null. + * + * @return the size of the texture color table, or 0 if the table + * is null + * + * @since Java 3D 1.2 + */ + public int getTextureColorTableSize() { + return (((TextureAttributesRetained)this.retained).getTextureColorTableSize()); + } + + + /** + * Sets the combine mode for the rgb components of the output color + * for this object. + * + * @param combineMode the combine mode, one of: + * <code>COMBINE_REPLACE</code>, + * <code>COMBINE_MODULATE</code>, <code>COMBINE_ADD</code>, + * <code>COMBINE_ADD_SIGNED</code>, <code>COMBINE_SUBTRACT</code>, + * <code>COMBINE_INTERPOLATE</code>, or <code>COMBINE_DOT3</code> + * + * @exception IllegalArgumentException if <code>combineMode</code> + * is a value other than <code>COMBINE_REPLACE</code>, + * <code>COMBINE_MODULATE</code>, <code>COMBINE_ADD</code>, + * <code>COMBINE_ADD_SIGNED</code>, <code>COMBINE_SUBTRACT</code>, + * <code>COMBINE_INTERPOLATE</code>, or <code>COMBINE_DOT3</code> + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setCombineRgbMode(int combineMode) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes16")); + } + } + + if ((combineMode < COMBINE_REPLACE) || (combineMode > COMBINE_DOT3)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureAttributes20")); + } + + if (isLive()) { + ((TextureAttributesRetained)this.retained).setCombineRgbMode(combineMode); + } else { + ((TextureAttributesRetained)this.retained).initCombineRgbMode(combineMode); + } + } + + /** + * Sets the combine mode for the alpha component of the output color + * for this object. + * + * @param combineMode the combine mode, one of: + * <code>COMBINE_REPLACE</code>, + * <code>COMBINE_MODULATE</code>, <code>COMBINE_ADD</code>, + * <code>COMBINE_ADD_SIGNED</code>, <code>COMBINE_SUBTRACT</code>, + * <code>COMBINE_INTERPOLATE</code>, or <code>COMBINE_DOT3</code> + * + * @exception IllegalArgumentException if <code>combineMode</code> + * is a value other than <code>COMBINE_REPLACE</code>, + * <code>COMBINE_MODULATE</code>, <code>COMBINE_ADD</code>, + * <code>COMBINE_ADD_SIGNED</code>, <code>COMBINE_SUBTRACT</code>, + * <code>COMBINE_INTERPOLATE</code>, or <code>COMBINE_DOT3</code> + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setCombineAlphaMode(int combineMode) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes18")); + } + } + + if ((combineMode < COMBINE_REPLACE) || (combineMode > COMBINE_DOT3)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureAttributes20")); + } + + if (isLive()) { + ((TextureAttributesRetained)this.retained).setCombineAlphaMode(combineMode); + } else { + ((TextureAttributesRetained)this.retained).initCombineAlphaMode(combineMode); + } + } + + /** + * Retrieves the combine mode for the rgb components of the output color + * for this object. + * @return the combine mode for the rgb components. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getCombineRgbMode() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes17")); + } + } + + return ((TextureAttributesRetained)this.retained).getCombineRgbMode(); + } + + /** + * Retrieves the combine mode for the alpha component of the output color + * for this object. + * @return the combine mode for the alpha component. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getCombineAlphaMode() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes19")); + } + } + + return ((TextureAttributesRetained)this.retained).getCombineAlphaMode(); + } + + /** + * Sets the source for the rgb components of the specified color operand + * for this object. + * + * @param index color operand in the combine operation + * @param src the color source, one of: <code>COMBINE_OBJECT_COLOR</code>, + * <code>COMBINE_TEXTURE_COLOR</code>, + * <code>COMBINE_CONSTANT_COLOR</code>, or + * <code>COMBINE_PREVIOUS_TEXTURE_UNIT_STATE</code> + * + * @exception IndexOutOfBoundsException if <code>index</code> < 0 or + * <code>index</code> > 2 + * @exception IllegalArgumentException if <code>src</code> + * is a value other than <code>COMBINE_OBJECT_COLOR</code>, + * <code>COMBINE_TEXTURE_COLOR</code>, + * <code>COMBINE_CONSTANT_COLOR</code>, or + * <code>COMBINE_PREVIOUS_TEXTURE_UNIT_STATE</code> + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setCombineRgbSource(int index, int src) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes21")); + } + } + + if ((index < 0) || (index > 2)) { + throw new IndexOutOfBoundsException( + J3dI18N.getString("TextureAttributes25")); + } + + if ((src < COMBINE_OBJECT_COLOR) || + (src > COMBINE_PREVIOUS_TEXTURE_UNIT_STATE)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureAttributes26")); + } + + if (isLive()) { + ((TextureAttributesRetained)this.retained).setCombineRgbSource( + index, src); + } else { + ((TextureAttributesRetained)this.retained).initCombineRgbSource( + index, src); + } + } + + /** + * Sets the source for the alpha component of the specified color operand + * for this object. + * + * @param index color operand in the combine operation + * @param src the color source, one of: <code>COMBINE_OBJECT_COLOR</code>, + * <code>COMBINE_TEXTURE_COLOR</code>, + * <code>COMBINE_CONSTANT_COLOR</code>, or + * <code>COMBINE_PREVIOUS_TEXTURE_UNIT_STATE</code> + * + * @exception IndexOutOfBoundsException if <code>index</code> < 0 or + * <code>index</code> > 2 + * @exception IllegalArgumentException if <code>src</code> + * is a value other than <code>COMBINE_OBJECT_COLOR</code>, + * <code>COMBINE_TEXTURE_COLOR</code>, + * <code>COMBINE_CONSTANT_COLOR</code>, or + * <code>COMBINE_PREVIOUS_TEXTURE_UNIT_STATE</code> + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setCombineAlphaSource(int index, int src) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes23")); + } + } + + if ((index < 0) || (index > 2)) { + throw new IndexOutOfBoundsException( + J3dI18N.getString("TextureAttributes25")); + } + + if ((src < COMBINE_OBJECT_COLOR) || + (src > COMBINE_PREVIOUS_TEXTURE_UNIT_STATE)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureAttributes26")); + } + + if (isLive()) { + ((TextureAttributesRetained)this.retained).setCombineAlphaSource( + index, src); + } else { + ((TextureAttributesRetained)this.retained).initCombineAlphaSource( + index, src); + } + } + + /** + * Retrieves the source for the rgb components of the specified + * color operand for this object. + * + * @param index color operand in the combine operation + * + * @return the source for the rgb components of the specified color + * operand for this object + * + * @exception IndexOutOfBoundsException if <code>index</code> < 0 or + * <code>index</code> > 2 + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getCombineRgbSource(int index) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes22")); + } + } + + if ((index < 0) || (index > 2)) { + throw new IndexOutOfBoundsException( + J3dI18N.getString("TextureAttributes25")); + } + + return ((TextureAttributesRetained)this.retained).getCombineRgbSource(index); + } + + /** + * Retrieves the source for the alpha component of the specified + * color operand for this object. + * + * @param index color operand in the combine operation + * + * @return the source for the alpha component of the specified color + * operand for this object + * + * @exception IndexOutOfBoundsException if <code>index</code> < 0 or + * <code>index</code> > 2 + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getCombineAlphaSource(int index) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes24")); + } + } + + if ((index < 0) || (index > 2)) { + throw new IndexOutOfBoundsException( + J3dI18N.getString("TextureAttributes25")); + } + + return ((TextureAttributesRetained)this.retained).getCombineAlphaSource(index); + } + + /** + * Sets the function for the rgb components of the specified color operand + * for this object. + * + * @param index color operand in the combine operation + * @param function the color function, one of: + * <code>COMBINE_SRC_COLOR</code>, + * <code>COMBINE_ONE_MINUS_SRC_COLOR</code>, + * <code>COMBINE_SRC_ALPHA</code>, or + * <code>COMBINE_ONE_MINUS_SRC_ALPHA</code> + * + * @exception IndexOutOfBoundsException if <code>index</code> < 0 or + * <code>index</code> > 2 + * @exception IllegalArgumentException if <code>function</code> + * is a value other than <code>COMBINE_SRC_COLOR</code>, + * <code>COMBINE_ONE_MINUS_SRC_COLOR</code>, + * <code>COMBINE_SRC_ALPHA</code>, or + * <code>COMBINE_ONE_MINUS_SRC_ALPHA</code> + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setCombineRgbFunction(int index, int function) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes27")); + } + } + + if ((index < 0) || (index > 2)) { + throw new IndexOutOfBoundsException( + J3dI18N.getString("TextureAttributes25")); + } + + if ((function < COMBINE_SRC_COLOR) || + (function > COMBINE_ONE_MINUS_SRC_ALPHA)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureAttributes31")); + } + + if (isLive()) { + ((TextureAttributesRetained)this.retained).setCombineRgbFunction( + index, function); + } else { + ((TextureAttributesRetained)this.retained).initCombineRgbFunction( + index, function); + } + } + + /** + * Sets the function for the alpha component of the specified color operand + * for this object. + * + * @param index color operand in the combine operation + * @param function the color function, one of: + * <code>COMBINE_SRC_ALPHA</code>, or + * <code>COMBINE_ONE_MINUS_SRC_ALPHA</code> + * + * @exception IndexOutOfBoundsException if <code>index</code> < 0 or + * <code>index</code> > 2 + * @exception IllegalArgumentException if <code>function</code> + * is a value other than + * <code>COMBINE_SRC_ALPHA</code> or + * <code>COMBINE_ONE_MINUS_SRC_ALPHA</code> + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setCombineAlphaFunction(int index, int function) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes29")); + } + } + + if ((index < 0) || (index > 2)) { + throw new IndexOutOfBoundsException( + J3dI18N.getString("TextureAttributes25")); + } + + if ((function < COMBINE_SRC_ALPHA) || + (function > COMBINE_ONE_MINUS_SRC_ALPHA)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureAttributes31")); + } + + if (isLive()) { + ((TextureAttributesRetained)this.retained).setCombineAlphaFunction( + index, function); + } else { + ((TextureAttributesRetained)this.retained).initCombineAlphaFunction( + index, function); + } + } + + /** + * Retrieves the function for the rgb components of the specified color + * operand for this object. + * + * @param index color operand in the combine operation + * + * @return the function for the rgb components of the specified color + * operand for this object. + * + * @exception IndexOutOfBoundsException if <code>index</code> < 0 or + * <code>index</code> > 2 + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getCombineRgbFunction(int index) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes28")); + } + } + + if ((index < 0) || (index > 2)) { + throw new IndexOutOfBoundsException( + J3dI18N.getString("TextureAttributes25")); + } + + return ((TextureAttributesRetained)this.retained).getCombineRgbFunction(index); + } + + /** + * Retrieves the function for the alpha component of the specified color + * operand for this object. + * + * @param index color operand in the combine operation + * + * @return the function for the alpha component of the specified color + * operand for this object. + * + * @exception IndexOutOfBoundsException if <code>index</code> < 0 or + * <code>index</code> > 2 + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getCombineAlphaFunction(int index) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes30")); + } + } + + if ((index < 0) || (index > 2)) { + throw new IndexOutOfBoundsException( + J3dI18N.getString("TextureAttributes25")); + } + + return ((TextureAttributesRetained)this.retained).getCombineAlphaFunction(index); + } + + /** + * Sets the scale factor for the rgb components of the output color + * for this object. + * + * @param scale the scale factor for the rgb components of the output + * color. It must be one of the following: 1, 2, or 4. + * + * @exception IllegalArgumentException if <code>scale</code> is a + * value other than 1, 2, or 4. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setCombineRgbScale(int scale) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes32")); + } + } + + + if ((scale != 1) && (scale != 2) && (scale != 4)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureAttributes36")); + } + + if (isLive()) { + ((TextureAttributesRetained)this.retained).setCombineRgbScale(scale); + } else { + ((TextureAttributesRetained)this.retained).initCombineRgbScale(scale); + } + } + + /** + * Sets the scale factor for the alpha component of the output color + * for this object. + * + * @param scale the scale factor for the alpha component of the output + * color. It must be one of the following: 1, 2, or 4. + * + * @exception IllegalArgumentException if <code>scale</code> is a + * value other than 1, 2, or 4. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @see Canvas3D#queryProperties + * + * @since Java 3D 1.3 + */ + public void setCombineAlphaScale(int scale) { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_WRITE)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes34")); + } + } + + if ((scale != 1) && (scale != 2) && (scale != 4)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureAttributes36")); + } + + if (isLive()) { + ((TextureAttributesRetained)this.retained).setCombineAlphaScale(scale); + } else { + ((TextureAttributesRetained)this.retained).initCombineAlphaScale(scale); + } + } + + /** + * Retrieves the scale factor for the rgb components of the output color + * for this object. + * + * @return the scale factor for the rgb components of the output color + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getCombineRgbScale() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes33")); + } + } + + return ((TextureAttributesRetained)this.retained).getCombineRgbScale(); + } + + /** + * Retrieves the scale factor for the alpha component of the output color + * for this object. + * + * @return the scale factor for the alpha component of the output color + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int getCombineAlphaScale() { + if (isLiveOrCompiled()) { + if (!this.getCapability(ALLOW_COMBINE_READ)) { + throw new CapabilityNotSetException( + J3dI18N.getString("TextureAttributes35")); + } + } + + return ((TextureAttributesRetained)this.retained).getCombineAlphaScale(); + } + + + /** + * Creates a retained mode TextureAttributesRetained object that this + * TextureAttributes component object will point to. + */ + void createRetained() { + this.retained = new TextureAttributesRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + TextureAttributes ta = new TextureAttributes(); + ta.duplicateNodeComponent(this); + return ta; + } + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + TextureAttributesRetained attr = + (TextureAttributesRetained) originalNodeComponent.retained; + TextureAttributesRetained rt = (TextureAttributesRetained) retained; + + Color4f c = new Color4f(); + attr.getTextureBlendColor(c); + Transform3D t = new Transform3D(); + attr.getTextureTransform(t); + + rt.initTextureMode(attr.getTextureMode()); + rt.initPerspectiveCorrectionMode(attr.getPerspectiveCorrectionMode()); + rt.initTextureBlendColor(c); + rt.initTextureTransform(t); + + if ((attr.getNumTextureColorTableComponents() != 0) && + (attr.getTextureColorTableSize() != 0)) { + int table[][] = new + int[attr.getNumTextureColorTableComponents()][attr.getTextureColorTableSize()]; + attr.getTextureColorTable(table); + rt.initTextureColorTable(table); + } + } +} diff --git a/src/classes/share/javax/media/j3d/TextureAttributesRetained.java b/src/classes/share/javax/media/j3d/TextureAttributesRetained.java new file mode 100644 index 0000000..46066af --- /dev/null +++ b/src/classes/share/javax/media/j3d/TextureAttributesRetained.java @@ -0,0 +1,1015 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color4f; +import java.util.ArrayList; + +/** + * The TextureAttributes object defines attributes that apply to + * to texture mapping. + */ +class TextureAttributesRetained extends NodeComponentRetained { + + // A list of pre-defined bits to indicate which component + // in this TextureAttributes object changed. + static final int TRANSFORM_CHANGED = 0x0001; + static final int MODE_CHANGED = 0x0002; + static final int COLOR_CHANGED = 0x0004; + static final int CORRECTION_CHANGED = 0x0008; + static final int TEXTURE_COLOR_TABLE_CHANGED = 0x0010; + static final int COMBINE_RGB_MODE_CHANGED = 0x0020; + static final int COMBINE_ALPHA_MODE_CHANGED = 0x0040; + static final int COMBINE_RGB_SRC_CHANGED = 0x0080; + static final int COMBINE_ALPHA_SRC_CHANGED = 0x0100; + static final int COMBINE_RGB_FCN_CHANGED = 0x0200; + static final int COMBINE_ALPHA_FCN_CHANGED = 0x0400; + static final int COMBINE_RGB_SCALE_CHANGED = 0x0800; + static final int COMBINE_ALPHA_SCALE_CHANGED = 0x1000; + + // static class variable for commands used in messages + static Integer commandInt[] = null; + + // static class variable for enums. Currently only supports 0 - 9. + static Integer enums[] = null; + + // Texture transform + Transform3D transform = new Transform3D(); + + // Texture mode + int textureMode = TextureAttributes.REPLACE; + + // Texture blend color + Color4f textureBlendColor = new Color4f(0.0f, 0.0f, 0.0f, 0.0f); + + // Texture color table + int textureColorTable[] = null; + int numTextureColorTableComponents = 0; + int textureColorTableSize = 0; + + // Texture Combine Mode + + int combineRgbMode = TextureAttributes.COMBINE_MODULATE; + int combineAlphaMode = TextureAttributes.COMBINE_MODULATE; + + // the following fields are only applicable if textureMode specifies + // COMBINE. If COMBINE mode is specified, then each of the following + // fields will be referencing an array of 3 integers, each representing + // an operand in the combine equation. + int [] combineRgbSrc = null; + int [] combineAlphaSrc = null; + int [] combineRgbFcn = null; + int [] combineAlphaFcn = null; + + int combineRgbScale = 1; + int combineAlphaScale = 1; + + //Perspective correction mode, used for color/texCoord interpolation + int perspCorrectionMode = TextureAttributes.NICEST; + + // true when mirror texCoord component set + boolean mirrorCompDirty = false; + + static final void initTextureEnums() { + // create some of the enums Integer to be used in the messages + // this can be eliminated if the message is modified to take + // integer itself + // + // NOTE: check with the actual enum value before using this + // list. This list only supports 0 - 9 + if (enums == null) { + enums = new Integer[10]; + for (int i = 0; i < 10; i++) { + enums[i] = new Integer(i); + } + } + } + + + TextureAttributesRetained() { + initTextureEnums(); + } + + // initCombineMode -- initializes the combine mode related fields + // delay the allocation of memory to minimize + // memory footprint + + final void initCombineMode(TextureAttributesRetained tr) { + tr.combineRgbSrc = new int[3]; + tr.combineAlphaSrc = new int[3]; + tr.combineRgbFcn = new int[3]; + tr.combineAlphaFcn = new int[3]; + + //default values + + tr.combineRgbSrc[0] = TextureAttributes.COMBINE_TEXTURE_COLOR; + tr.combineRgbSrc[1] = TextureAttributes.COMBINE_PREVIOUS_TEXTURE_UNIT_STATE; + tr.combineRgbSrc[2] = TextureAttributes.COMBINE_CONSTANT_COLOR; + + tr.combineAlphaSrc[0] = TextureAttributes.COMBINE_TEXTURE_COLOR; + tr.combineAlphaSrc[1] = TextureAttributes.COMBINE_PREVIOUS_TEXTURE_UNIT_STATE; + tr.combineAlphaSrc[2] = TextureAttributes.COMBINE_CONSTANT_COLOR; + + tr.combineRgbFcn[0] = TextureAttributes.COMBINE_SRC_COLOR; + tr.combineRgbFcn[1] = TextureAttributes.COMBINE_SRC_COLOR; + tr.combineRgbFcn[2] = TextureAttributes.COMBINE_SRC_COLOR; + + tr.combineAlphaFcn[0] = TextureAttributes.COMBINE_SRC_ALPHA; + tr.combineAlphaFcn[1] = TextureAttributes.COMBINE_SRC_ALPHA; + tr.combineAlphaFcn[2] = TextureAttributes.COMBINE_SRC_ALPHA; + } + + final void initTextureMode(int textureMode) { + this.textureMode = textureMode; + + if (textureMode == TextureAttributes.COMBINE) { + if (combineRgbSrc == null) { + initCombineMode(this); + } + } + } + + /** + * Sets the texture mode parameter for this + * appearance component object. + * @param textureMode the texture mode, one of: MODULATE, + * DECAL, BLEND, or REPLACE + */ + final void setTextureMode(int textureMode) { + initTextureMode(textureMode); + sendMessage(MODE_CHANGED, enums[textureMode], null); + } + + /** + * Gets the texture mode parameter for this + * texture attributes object. + * @return textureMode the texture mode + */ + final int getTextureMode() { + return textureMode; + } + + final void initTextureBlendColor(Color4f textureBlendColor) { + this.textureBlendColor.set(textureBlendColor); + + } + + /** + * Sets the texture blend color for this + * texture attributes object. + * @param textureBlendColor the texture blend color used when + * the mode is BLEND + */ + final void setTextureBlendColor(Color4f textureBlendColor) { + this.textureBlendColor.set(textureBlendColor); + sendMessage(COLOR_CHANGED, new Color4f(textureBlendColor), null); + } + + + final void initTextureBlendColor(float r, float g, float b, float a) { + this.textureBlendColor.set(r, g, b, a); + } + + + /** + * Sets the texture blend color for this + * appearance component object. This color is used when + * the mode is BLEND. + * @param r the red component of the color + * @param g the green component of the color + * @param b the blue component of the color + * @param a the alpha component of the color + */ + final void setTextureBlendColor(float r, float g, float b, float a) { + this.textureBlendColor.set(r, g, b, a); + sendMessage(COLOR_CHANGED, new Color4f(r, g, b, a), null); + } + + + /** + * Gets the texture blend color for this + * appearance component object. + * @param textureBlendColor the vector that will receive the texture + * blend color used when the mode is BLEND + */ + final void getTextureBlendColor(Color4f textureBlendColor) { + textureBlendColor.set(this.textureBlendColor); + } + + + final void initTextureTransform(Transform3D transform) { + this.transform.set(transform); + } + + + /** + * Sets the texture transform object used to transform texture + * coordinates. A copy of the specified Transform3D object is + * stored in this TextureAttributes object. + * @param transform the new transform object + */ + final void setTextureTransform(Transform3D transform) { + this.transform.set(transform); + sendMessage(TRANSFORM_CHANGED, + VirtualUniverse.mc.getTransform3D(transform), null); + } + + + /** + * Retrieves a copy of the texture transform object. + * @param transform the transform object that will receive the + * current texture transform. + */ + final void getTextureTransform(Transform3D transform) { + transform.set(this.transform); + } + + + final void initPerspectiveCorrectionMode(int mode) { + this.perspCorrectionMode = mode; + } + + /** + * Sets perspective correction mode to be used for color + * and/or texture coordinate interpolation. + * A value of NICEST indicates that perspective correction should be + * performed and that the highest quality method should be used. + * A value of FASTEST indicates that the most efficient perspective + * correction method should be used. + * @param mode one of NICEST or FASTEST. + * The default value is NICEST. + */ + final void setPerspectiveCorrectionMode(int mode) { + this.perspCorrectionMode = mode; + sendMessage(CORRECTION_CHANGED, enums[mode], null); + } + + /** + * Gets perspective correction mode value. + * @return mode the value of perspective correction mode. + */ + final int getPerspectiveCorrectionMode() { + return perspCorrectionMode; + } + + final void setTextureColorTable(int[][] table) { + initTextureColorTable(table); + + //clone a copy of the texture for the mirror object + if (table == null) { + sendMessage(TEXTURE_COLOR_TABLE_CHANGED, null, null); + } else { + int ctable[] = new int[textureColorTableSize * + numTextureColorTableComponents]; + System.arraycopy(textureColorTable, 0, ctable, 0, + textureColorTable.length); + Object args[] = new Object[3]; + + args[0] = new Integer(numTextureColorTableComponents); + args[1] = new Integer(textureColorTableSize); + args[2] = ctable; + sendMessage(TEXTURE_COLOR_TABLE_CHANGED, args, null); + } + } + + final void initTextureColorTable(int[][] table) { + + numTextureColorTableComponents = 0; + textureColorTableSize = 0; + + if (table == null) { + textureColorTable = null; + return; + } + + if (table.length < 3 || table.length > 4) { + throw new IllegalArgumentException(J3dI18N.getString("TextureAttributes13")); + } + + if (Texture.getPowerOf2(table[0].length) == -1) { + throw new IllegalArgumentException(J3dI18N.getString("TextureAttributes14")); + } + + for (int i = 1; i < table.length; i++) { + if (table[i].length != table[0].length) + throw new IllegalArgumentException(J3dI18N.getString("TextureAttributes15")); + } + + numTextureColorTableComponents = table.length; + textureColorTableSize = table[0].length; + + if (textureColorTable == null || + textureColorTable.length != numTextureColorTableComponents * + textureColorTableSize) { + textureColorTable = new int[numTextureColorTableComponents * + textureColorTableSize]; + } + + int k = 0; + for (int i = 0; i < textureColorTableSize; i++) { + for (int j = 0; j < numTextureColorTableComponents; j++) { + textureColorTable[k++] = table[j][i]; + } + } + } + + + final void getTextureColorTable(int[][] table) { + + if (textureColorTable == null) + return; + + int k = 0; + for (int i = 0; i < textureColorTableSize; i++) { + for (int j = 0; j < numTextureColorTableComponents; j++) { + table[j][i] = textureColorTable[k++]; + } + } + } + + final int getNumTextureColorTableComponents() { + return numTextureColorTableComponents; + } + + final int getTextureColorTableSize() { + return textureColorTableSize; + } + + + final void initCombineRgbMode(int mode) { + combineRgbMode = mode; + } + + final void setCombineRgbMode(int mode) { + initCombineRgbMode(mode); + sendMessage(COMBINE_RGB_MODE_CHANGED, enums[mode], null); + } + + final int getCombineRgbMode() { + return combineRgbMode; + } + + final void initCombineAlphaMode(int mode) { + combineAlphaMode = mode; + } + + final void setCombineAlphaMode(int mode) { + initCombineAlphaMode(mode); + sendMessage(COMBINE_ALPHA_MODE_CHANGED, enums[mode], null); + } + + final int getCombineAlphaMode() { + return combineAlphaMode; + } + + final void initCombineRgbSource(int index, int src) { + if (combineRgbSrc == null) { + // it is possible to set the combineRgbSource before + // setting the texture mode to COMBINE, so need to initialize + // the combine mode related fields here + initCombineMode(this); + } + combineRgbSrc[index] = src; + } + + final void setCombineRgbSource(int index, int src) { + initCombineRgbSource(index, src); + sendMessage(COMBINE_RGB_SRC_CHANGED, enums[index], enums[src]); + } + + final int getCombineRgbSource(int index) { + if (combineRgbSrc == null) { + // it is possible to do a get before + // setting the texture mode to COMBINE, so need to initialize + // the combine mode related fields here + initCombineMode(this); + } + return combineRgbSrc[index]; + } + + final void initCombineAlphaSource(int index, int src) { + if (combineRgbSrc == null) { + // it is possible to set the combineAlphaSource before + // setting the texture mode to COMBINE, so need to initialize + // the combine mode related fields here + initCombineMode(this); + } + combineAlphaSrc[index] = src; + } + + final void setCombineAlphaSource(int index, int src) { + initCombineAlphaSource(index, src); + sendMessage(COMBINE_ALPHA_SRC_CHANGED, enums[index], enums[src]); + } + + final int getCombineAlphaSource(int index) { + if (combineRgbSrc == null) { + // it is possible to do a get before + // setting the texture mode to COMBINE, so need to initialize + // the combine mode related fields here + initCombineMode(this); + } + return combineAlphaSrc[index]; + } + + final void initCombineRgbFunction(int index, int fcn) { + if (combineRgbSrc == null) { + // it is possible to set the combineRgbFcn before + // setting the texture mode to COMBINE, so need to initialize + // the combine mode related fields here + initCombineMode(this); + } + combineRgbFcn[index] = fcn; + } + + final void setCombineRgbFunction(int index, int fcn) { + initCombineRgbFunction(index, fcn); + sendMessage(COMBINE_RGB_FCN_CHANGED, enums[index], enums[fcn]); + } + + final int getCombineRgbFunction(int index) { + if (combineRgbSrc == null) { + // it is possible to do a get before + // setting the texture mode to COMBINE, so need to initialize + // the combine mode related fields here + initCombineMode(this); + } + return combineRgbFcn[index]; + } + + final void initCombineAlphaFunction(int index, int fcn) { + if (combineRgbSrc == null) { + // it is possible to set the combineAlphaFcn before + // setting the texture mode to COMBINE, so need to initialize + // the combine mode related fields here + initCombineMode(this); + } + combineAlphaFcn[index] = fcn; + } + + final void setCombineAlphaFunction(int index, int fcn) { + initCombineAlphaFunction(index, fcn); + sendMessage(COMBINE_ALPHA_FCN_CHANGED, enums[index], enums[fcn]); + } + + final int getCombineAlphaFunction(int index) { + if (combineRgbSrc == null) { + // it is possible to do a get before + // setting the texture mode to COMBINE, so need to initialize + // the combine mode related fields here + initCombineMode(this); + } + return combineAlphaFcn[index]; + } + + final void initCombineRgbScale(int scale) { + combineRgbScale = scale; + } + + final void setCombineRgbScale(int scale) { + initCombineRgbScale(scale); + sendMessage(COMBINE_RGB_SCALE_CHANGED, enums[scale], null); + } + + final int getCombineRgbScale() { + return combineRgbScale; + } + + final void initCombineAlphaScale(int scale) { + combineAlphaScale = scale; + } + + final void setCombineAlphaScale(int scale) { + initCombineAlphaScale(scale); + sendMessage(COMBINE_ALPHA_SCALE_CHANGED, enums[scale], null); + } + + final int getCombineAlphaScale() { + return combineAlphaScale; + } + + // These methods update the native context. + native void updateNative(long ctx, + double[] transform, boolean isIdentity, int textureMode, + int perspCorrectionMode, float red, + float green, float blue, float alpha, + int textureFormat); + + native void updateNativeRegisterCombiners(long ctx, + double[] transform, boolean isIdentity, int textureMode, + int perspCorrectionMode, float red, + float green, float blue, float alpha, + int textureFormat, + int combineRgbMode, int combineAlphaMode, + int[] combineRgbSrc, int[] combineAlphaSrc, + int[] combineRgbFcn, int[] combineAlphaFcn, + int combineRgbScale, int combineAlphaScale); + + native void updateTextureColorTableNative(long ctx, int numComponents, + int colorTableSize, + int[] colorTable); + + native void updateCombinerNative(long ctx, + int combineRgbMode, int combineAlphaMode, + int[] combineRgbSrc, int[] combineAlphaSrc, + int[] combineRgbFcn, int[] combineAlphaFcn, + int combineRgbScale, int combineAlphaScale); + + native void restoreBlend1Pass(long ctx); + native void updateBlend2Pass(long ctx); + + + void updateNative(Canvas3D cv, boolean simulate, int textureFormat) { + + //System.out.println("TextureAttributes/updateNative: simulate= " + simulate + " " + this); + + //if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_COLOR_TABLE) + // == 0) && textureColorTable != null) { + // System.out.println("TextureColorTable Not supported"); + //} + + //System.out.println("textureMode= " + textureMode); + boolean isIdentity = + ((transform.getType() & Transform3D.IDENTITY) != 0); + + if (simulate == false) { + if (VirtualUniverse.mc.useCombiners && + (cv.textureExtendedFeatures & + Canvas3D.TEXTURE_REGISTER_COMBINERS) != 0) { + updateNativeRegisterCombiners(cv.ctx, + transform.mat, isIdentity, textureMode, perspCorrectionMode, + textureBlendColor.x, textureBlendColor.y, + textureBlendColor.z, textureBlendColor.w, + textureFormat, combineRgbMode, combineAlphaMode, + combineRgbSrc, combineAlphaSrc, + combineRgbFcn, combineAlphaFcn, + combineRgbScale, combineAlphaScale); + } else { + if (textureMode == TextureAttributes.COMBINE) { + + if ((cv.textureExtendedFeatures & + Canvas3D.TEXTURE_COMBINE) != 0) { + + // Texture COMBINE is supported by the underlying layer + + int _combineRgbMode = combineRgbMode; + int _combineAlphaMode = combineAlphaMode; + + updateNative(cv.ctx, transform.mat, isIdentity, textureMode, + perspCorrectionMode, + textureBlendColor.x, textureBlendColor.y, + textureBlendColor.z, textureBlendColor.w, + textureFormat); + + + if (((combineRgbMode == TextureAttributes.COMBINE_DOT3) && + ((cv.textureExtendedFeatures & + Canvas3D.TEXTURE_COMBINE_DOT3) == 0)) || + ((combineRgbMode == TextureAttributes.COMBINE_SUBTRACT) && + ((cv.textureExtendedFeatures & + Canvas3D.TEXTURE_COMBINE_SUBTRACT) == 0))) { + + // Combine DOT3/SUBTRACT is not supported by the + // underlying layer, fallback to COMBINE_REPLACE + + _combineRgbMode = TextureAttributes.COMBINE_REPLACE; + } + + if (((combineAlphaMode == TextureAttributes.COMBINE_DOT3) && + ((cv.textureExtendedFeatures & + Canvas3D.TEXTURE_COMBINE_DOT3) == 0)) || + ((combineAlphaMode == TextureAttributes.COMBINE_SUBTRACT) && + ((cv.textureExtendedFeatures & + Canvas3D.TEXTURE_COMBINE_SUBTRACT) == 0))) { + + // Combine DOT3/SUBTRACT is not supported by the + // underlying layer, fallback to COMBINE_REPLACE + + _combineAlphaMode = TextureAttributes.COMBINE_REPLACE; + } + + updateCombinerNative(cv.ctx, + _combineRgbMode, _combineAlphaMode, + combineRgbSrc, combineAlphaSrc, + combineRgbFcn, combineAlphaFcn, + combineRgbScale, combineAlphaScale); + + } else { + + // Texture COMBINE is not supported by the underlying + // layer, fallback to REPLACE + + updateNative(cv.ctx, transform.mat, isIdentity, + TextureAttributes.REPLACE, + perspCorrectionMode, + textureBlendColor.x, textureBlendColor.y, + textureBlendColor.z, textureBlendColor.w, + textureFormat); + } + } else { + updateNative(cv.ctx, transform.mat, isIdentity, textureMode, + perspCorrectionMode, + textureBlendColor.x, textureBlendColor.y, + textureBlendColor.z, textureBlendColor.w, + textureFormat); + } + } + + + if (((cv.textureExtendedFeatures & Canvas3D.TEXTURE_COLOR_TABLE) + != 0) && textureColorTable != null) { + + updateTextureColorTableNative(cv.ctx, + numTextureColorTableComponents, + textureColorTableSize, textureColorTable); + } + } else { + // we are in the multi-pass mode, + // in this case, set the texture Mode to replace and use + // blending to simulate the original textureMode + updateNative(cv.ctx, transform.mat, isIdentity, TextureAttributes.REPLACE, + perspCorrectionMode, + textureBlendColor.x, textureBlendColor.y, + textureBlendColor.z, textureBlendColor.w, textureFormat); + + if (((cv.textureExtendedFeatures & Canvas3D.TEXTURE_COLOR_TABLE) + != 0) && textureColorTable != null) { + + updateTextureColorTableNative(cv.ctx, numTextureColorTableComponents, + textureColorTableSize, textureColorTable); + } + + switch (textureMode) { + case TextureAttributes.COMBINE: + case TextureAttributes.REPLACE: + cv.setBlendFunc(cv.ctx, + TransparencyAttributesRetained.BLEND_ONE, + TransparencyAttributesRetained.BLEND_ZERO); + break; + case TextureAttributes.MODULATE: + cv.setBlendFunc(cv.ctx, + TransparencyAttributesRetained.BLEND_DST_COLOR, + TransparencyAttributesRetained.BLEND_ZERO); + break; + case TextureAttributes.DECAL: + if (textureFormat == Texture.RGBA) { + cv.setBlendFunc(cv.ctx, + TransparencyAttributesRetained.BLEND_SRC_ALPHA, + TransparencyAttributesRetained.BLEND_ONE_MINUS_SRC_ALPHA); + } else { + cv.setBlendFunc(cv.ctx, + TransparencyAttributesRetained.BLEND_ONE, + TransparencyAttributesRetained.BLEND_ZERO); + } + break; + case TextureAttributes.BLEND: + cv.setBlendColor(cv.ctx, textureBlendColor.x, textureBlendColor.y, + textureBlendColor.z, textureBlendColor.w); + cv.setBlendFunc(cv.ctx, + TransparencyAttributesRetained.BLEND_CONSTANT_COLOR, + TransparencyAttributesRetained.BLEND_ONE_MINUS_SRC_COLOR); + break; + } + } + } + + + /** + * Creates and initializes a mirror object, point the mirror object + * to the retained object if the object is not editable + */ + synchronized void createMirrorObject() { + + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror = this; + } else { + TextureAttributesRetained mirrorTa = new TextureAttributesRetained(); + mirrorTa.source = source; + mirrorTa.set(this); + mirror = mirrorTa; + } + } else { + ((TextureAttributesRetained)mirror).set(this); + } + } + + /** + * Initializes a mirror object + */ + synchronized void initMirrorObject() { + ((TextureAttributesRetained)mirror).set(this); + } + + + /** + * Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value, + Object value2) { + TextureAttributesRetained mirrorTa = (TextureAttributesRetained)mirror; + mirrorTa.mirrorCompDirty = true; + + if ((component & TRANSFORM_CHANGED) != 0) { + mirrorTa.transform.set((Transform3D)value); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + (Transform3D)value); + } + else if ((component & MODE_CHANGED) != 0) { + mirrorTa.textureMode = ((Integer)value).intValue(); + + if ((mirrorTa.textureMode == TextureAttributes.COMBINE) && + (mirrorTa.combineRgbSrc == null)) { + initCombineMode(mirrorTa); + } + } + else if ((component & COLOR_CHANGED) != 0) { + mirrorTa.textureBlendColor.set((Color4f)value); + } + else if ((component & CORRECTION_CHANGED) != 0) { + mirrorTa.perspCorrectionMode = ((Integer)value).intValue(); + } + else if ((component & TEXTURE_COLOR_TABLE_CHANGED) != 0) { + if (value == null) { + mirrorTa.textureColorTable = null; + mirrorTa.numTextureColorTableComponents = 0; + mirrorTa.textureColorTableSize = 0; + } else { + Object args[] = (Object[])value; + mirrorTa.textureColorTable = (int[])args[2]; + mirrorTa.numTextureColorTableComponents = + ((Integer)args[0]).intValue(); + mirrorTa.textureColorTableSize = + ((Integer)args[1]).intValue(); + } + } + else if ((component & COMBINE_RGB_MODE_CHANGED) != 0) { + mirrorTa.combineRgbMode = ((Integer)value).intValue(); + } + else if ((component & COMBINE_ALPHA_MODE_CHANGED) != 0) { + mirrorTa.combineAlphaMode = ((Integer)value).intValue(); + } + else if ((component & COMBINE_RGB_SRC_CHANGED) != 0) { + if (mirrorTa.combineRgbSrc == null) { + //initialize the memory for combine mode + initCombineMode(mirrorTa); + } + int index = ((Integer)value).intValue(); + mirrorTa.combineRgbSrc[index] = ((Integer)value2).intValue(); + } + else if ((component & COMBINE_ALPHA_SRC_CHANGED) != 0) { + if (mirrorTa.combineRgbSrc == null) { + //initialize the memory for combine mode + initCombineMode(mirrorTa); + } + int index = ((Integer)value).intValue(); + mirrorTa.combineAlphaSrc[index] = ((Integer)value2).intValue(); + } + else if ((component & COMBINE_RGB_FCN_CHANGED) != 0) { + if (mirrorTa.combineRgbSrc == null) { + //initialize the memory for combine mode + initCombineMode(mirrorTa); + } + int index = ((Integer)value).intValue(); + mirrorTa.combineRgbFcn[index] = ((Integer)value2).intValue(); + } + else if ((component & COMBINE_ALPHA_FCN_CHANGED) != 0) { + if (mirrorTa.combineRgbSrc == null) { + //initialize the memory for combine mode + initCombineMode(mirrorTa); + } + int index = ((Integer)value).intValue(); + mirrorTa.combineAlphaFcn[index] = ((Integer)value2).intValue(); + } + else if ((component & COMBINE_RGB_SCALE_CHANGED) != 0) { + mirrorTa.combineRgbScale = ((Integer)value).intValue(); + } + else if ((component & COMBINE_ALPHA_SCALE_CHANGED) != 0) { + mirrorTa.combineAlphaScale = ((Integer)value).intValue(); + } + } + + + boolean equivalent(TextureAttributesRetained tr) { + + if (tr == null) { + return (false); + + } else if ((this.changedFrequent != 0) || (tr.changedFrequent != 0)) { + return (this == tr); + } + + if (!(tr.transform.equals(transform) && + tr.textureBlendColor.equals(textureBlendColor) && + (tr.textureMode == textureMode) && + (tr.perspCorrectionMode == perspCorrectionMode))) { + return false; + } + + + // now check for combine mode attributes if textureMode specifies + // COMBINE + + if (textureMode == TextureAttributes.COMBINE) { + + if ((tr.combineRgbMode != combineRgbMode) || + (tr.combineAlphaMode != combineAlphaMode) || + (tr.combineRgbScale != combineRgbScale) || + (tr.combineAlphaScale != combineAlphaScale)) { + return false; + } + + // now check if the operands for the combine equations are + // equivalent + + int nOpNeeded = 0; + + if (combineRgbMode == TextureAttributes.COMBINE_REPLACE) { + nOpNeeded = 1; + } else if (combineRgbMode == TextureAttributes.COMBINE_INTERPOLATE) { + nOpNeeded = 3; + } else { + nOpNeeded = 2; + } + + for (int i = 0; i < nOpNeeded; i++) { + if ((tr.combineRgbSrc[i] != combineRgbSrc[i]) || + (tr.combineAlphaSrc[i] != combineAlphaSrc[i]) || + (tr.combineRgbFcn[i] != combineRgbFcn[i]) || + (tr.combineAlphaFcn[i] != combineAlphaFcn[i])) { + return false; + } + } + } + + // now check for texture color table + + if (tr.textureColorTable == null) { + if (this.textureColorTable == null) + return true; + else + return false; + } else if (this.textureColorTable == null) { + // tr.textureColorTable != null + return false; + } else { + if (tr.textureColorTable.length != this.textureColorTable.length) + return false; + + for (int i = 0; i < this.textureColorTable.length; i++) { + if (this.textureColorTable[i] != tr.textureColorTable[i]) + return false; + } + + return true; + } + + } + + + protected Object clone() { + TextureAttributesRetained tr = (TextureAttributesRetained)super.clone(); + tr.transform = new Transform3D(transform); + tr.textureBlendColor = new Color4f(textureBlendColor); + if (textureColorTable != null) { + tr.textureColorTable = new int[textureColorTable.length]; + System.arraycopy(textureColorTable, 0, tr.textureColorTable, 0, + textureColorTable.length); + } else { + tr.textureColorTable = null; + } + + // clone the combine mode attributes + if (combineRgbSrc != null) { + tr.combineRgbSrc = new int[3]; + tr.combineAlphaSrc = new int[3]; + tr.combineRgbFcn = new int[3]; + tr.combineAlphaFcn = new int[3]; + + for (int i = 0; i < 3; i++) { + tr.combineRgbSrc[i] = combineRgbSrc[i]; + tr.combineAlphaSrc[i] = combineAlphaSrc[i]; + tr.combineRgbFcn[i] = combineRgbFcn[i]; + tr.combineAlphaFcn[i] = combineAlphaFcn[i]; + } + } + + // other attributes are copied in super.clone() + return tr; + } + + protected void set(TextureAttributesRetained tr) { + super.set(tr); + transform.set(tr.transform); + textureBlendColor.set(tr.textureBlendColor); + textureMode = tr.textureMode; + perspCorrectionMode = tr.perspCorrectionMode; + + // set texture color table + + if (tr.textureColorTable != null) { + if (textureColorTable == null || + textureColorTable.length != tr.textureColorTable.length) { + textureColorTable = new int[tr.textureColorTable.length]; + } + System.arraycopy(tr.textureColorTable, 0, textureColorTable, 0, + tr.textureColorTable.length); + } else { + textureColorTable = null; + } + numTextureColorTableComponents = tr.numTextureColorTableComponents; + textureColorTableSize = tr.textureColorTableSize; + + + // set the combine mode attributes + + combineRgbMode = tr.combineRgbMode; + combineAlphaMode = tr.combineAlphaMode; + combineRgbScale = tr.combineRgbScale; + combineAlphaScale = tr.combineAlphaScale; + + if (tr.combineRgbSrc != null) { + if (combineRgbSrc == null) { + combineRgbSrc = new int[3]; + combineAlphaSrc = new int[3]; + combineRgbFcn = new int[3]; + combineAlphaFcn = new int[3]; + } + + for (int i = 0; i < 3; i++) { + combineRgbSrc[i] = tr.combineRgbSrc[i]; + combineAlphaSrc[i] = tr.combineAlphaSrc[i]; + combineRgbFcn[i] = tr.combineRgbFcn[i]; + combineAlphaFcn[i] = tr.combineAlphaFcn[i]; + } + } + } + + + final void sendMessage(int attrMask, Object attr1, Object attr2) { + + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.TEXTUREATTRIBUTES_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1] = new Integer(attrMask); + createMessage.args[2] = attr1; + createMessage.args[3] = attr2; + createMessage.args[4] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.TEXTUREATTRIBUTES_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1] = new Integer(attrMask); + createMessage.args[2] = attr1; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + } + + void handleFrequencyChange(int bit) { + switch (bit) { + case TextureAttributes.ALLOW_MODE_WRITE: + case TextureAttributes.ALLOW_BLEND_COLOR_WRITE: + case TextureAttributes.ALLOW_TRANSFORM_WRITE: + case TextureAttributes.ALLOW_COLOR_TABLE_WRITE: + case TextureAttributes.ALLOW_COMBINE_WRITE: { + setFrequencyChangeMask(bit, bit); + } + default: + break; + } + } +} diff --git a/src/classes/share/javax/media/j3d/TextureBin.java b/src/classes/share/javax/media/j3d/TextureBin.java new file mode 100644 index 0000000..a34eb47 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TextureBin.java @@ -0,0 +1,1683 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.*; + +/** + * The TextureBin manages a collection of TextureSetting objects. + * All objects in the TextureBin share the same Texture reference. + */ + + +//class TextureBin extends Object implements ObjectUpdate, NodeComponentUpdate { +class TextureBin extends Object implements ObjectUpdate { + + TextureUnitStateRetained [] texUnitState = null; + + // last active texture unit + int lastActiveTexUnitIndex; + + // number of active texture unit + int numActiveTexUnit; + + /** + * The RenderBin for this object + */ + RenderBin renderBin = null; + + /** + * The AttribureBin that this TextureBin resides + */ + AttributeBin attributeBin = null; + + /** + * The references to the next and previous TextureBins in the + * list. + */ + TextureBin next = null; + TextureBin prev = null; + + /** + * Oring of the equivalence bits for all appearance attrs under + * this renderBin + */ + int equivalent = 0; + + /** + * If any of the texture reference in an appearance is frequently + * changable, then a separate TextureBin will be created for this + * appearance, and this TextureBin is marked as the sole user of + * this appearance, and app will be pointing to the appearance + * being referenced by this TextureBin. Otherwise, app is null + */ + AppearanceRetained app = null; + + + /** + * Sole user node component dirty mask. The first bit is reserved + * for node component reference dirty bit. It is set if any of the + * texture related node component reference in the appearance is + * being modified. The second bit onwords are for the individual + * TextureUnitState dirty bit. The ith bit set means the (i-1) + * texture unit state is modified. Note, this mask only supports + * 30 texture unit states. If the appearance uses more than 31 + * texture unit states, then the modification of the 32nd texture + * unit state and up will have the first bit set, that means + * the TextureBin will be reset, rather than only the particular + * texture unit state will be reset. + */ + int soleUserCompDirty; + + static final int SOLE_USER_DIRTY_REF = 0x1; + static final int SOLE_USER_DIRTY_TA = 0x2; + static final int SOLE_USER_DIRTY_TC = 0x4; + static final int SOLE_USER_DIRTY_TEXTURE = 0x8; + static final int SOLE_USER_DIRTY_TUS = 0x10; + + + /** + * The hashMap of RenderMolecules in this TextureBin + * this is used in rendering, the key used is localToVworld + */ + HashMap addOpaqueRMs = new HashMap(); + HashMap addTransparentRMs = new HashMap(); + + + // A hashmap based on localToVworld for fast + // insertion of new renderMolecules + HashMap opaqueRenderMoleculeMap = new HashMap(); + HashMap transparentRenderMoleculeMap = new HashMap(); + + // List of renderMolecules - used in rendering .. + RenderMolecule opaqueRMList = null; + + RenderMolecule transparentRMList = null; + TransparentRenderingInfo parentTInfo; + + int numRenderMolecules = 0; + int numEditingRenderMolecules = 0; + + int tbFlag = 0; // a general bitmask for TextureBin + + // Following are the bits used in flag + + final static int ON_RENDER_BIN_LIST = 0x0001; + final static int ON_UPDATE_LIST = 0x0002; + final static int SOLE_USER = 0x0004; + final static int CONTIGUOUS_ACTIVE_UNITS = 0x0008; + final static int RESORT = 0x0010; + final static int ON_UPDATE_CHECK_LIST = 0x0020; + + final static int USE_DISPLAYLIST = -2; + final static int USE_VERTEXARRAY = -1; + + TextureBin(TextureUnitStateRetained[] state, AppearanceRetained app, + RenderBin rb) { + renderBin = rb; + tbFlag = 0; + reset(state, app); + } + + + /** + * For now, clone everything just like the other NodeComponent + */ + void reset(TextureUnitStateRetained[] state, AppearanceRetained app) { + + prev = null; + next = null; + opaqueRMList = null; + transparentRMList = null; + numEditingRenderMolecules = 0; + + // determine if this appearance is a sole user of this + // TextureBin + if ((app != null) && + (app.changedFrequent & + (AppearanceRetained.TEXTURE | + AppearanceRetained.TEXCOORD_GEN | + AppearanceRetained.TEXTURE_ATTR | + AppearanceRetained.TEXTURE_UNIT_STATE)) != 0) { + tbFlag |= TextureBin.SOLE_USER; + this.app = app; + + } else { + tbFlag &= ~TextureBin.SOLE_USER; + this.app = null; + } + + resetTextureState(state); + + if ((tbFlag & TextureBin.ON_RENDER_BIN_LIST) == 0) { + renderBin.addTextureBin(this); + tbFlag |= TextureBin.ON_RENDER_BIN_LIST; + } + + } + + void resetTextureState(TextureUnitStateRetained[] state) { + + int i, j; + boolean foundDisableUnit = false; + numActiveTexUnit = 0; + boolean d3dBlendMode = false; + lastActiveTexUnitIndex = 0; + boolean soleUser = ((tbFlag & TextureBin.SOLE_USER) != 0); + TextureRetained prevFirstTexture = null; + TextureRetained tex; + + + tbFlag |= TextureBin.CONTIGUOUS_ACTIVE_UNITS; + + if (state != null) { + + foundDisableUnit = false; + + if (texUnitState == null || (texUnitState.length != state.length)) { + texUnitState = new TextureUnitStateRetained[state.length]; + } else if (texUnitState.length > 0 && texUnitState[0] != null) { + prevFirstTexture = texUnitState[0].texture; + } + + for (i = 0; i < state.length; i++) { + if (state[i] == null) { + texUnitState[i] = null; + foundDisableUnit = true; + } else { + + // create a clone texture unit state + if (texUnitState[i] == null) { + texUnitState[i] = new TextureUnitStateRetained(); + } + + // for sole user TextureUnitState, save + // the node component reference in the mirror reference + // of the cloned copy for equal test, and + // for native download optimization + if (soleUser || state[i].changedFrequent != 0) { + texUnitState[i].mirror = state[i]; + } + + // for the lowest level of node component in + // TextureBin, clone it only if it is not + // changedFrequent; in other words, if the + // lowest level of texture related node components + // such as TextureAttributes & TexCoordGen is + // changedFrequent, have the cloned texUnitState + // reference the mirror of those node components + // directly. For Texture, we'll always reference + // the mirror. + + // decrement the TextureBin ref count of the previous + // texture + tex = texUnitState[i].texture; + if (tex != null) { + tex.decTextureBinRefCount(this); + if (soleUser && + (tex.textureBinRefCount == 0) && + (tex != state[i].texture)) { + // In this case texture change but + // TextureBin will not invoke clear() to reset. + // So we need to free the texture resource here. + renderBin.addTextureResourceFreeList(tex); + } + } + + texUnitState[i].texture = state[i].texture; + + // increment the TextureBin ref count of the new + // texture + + if (texUnitState[i].texture != null) { + texUnitState[i].texture.incTextureBinRefCount(this); + } + + if (state[i].texAttrs != null) { + + if (state[i].texAttrs.changedFrequent != 0) { + texUnitState[i].texAttrs = state[i].texAttrs; + + } else { + + // need to check for texAttrs.source because + // texAttrs could be pointing to the mirror + // in the last frame, so don't want to + // overwrite the mirror + + if (texUnitState[i].texAttrs == null || + texUnitState[i].texAttrs.source != null) { + texUnitState[i].texAttrs = + new TextureAttributesRetained(); + } + texUnitState[i].texAttrs.set( + state[i].texAttrs); + texUnitState[i].texAttrs.mirrorCompDirty = true; + + // for sole user TextureBin, we are saving + // the mirror node component in the mirror + // reference in the clone object. This + // will be used in state download to + // avoid redundant download + + if (soleUser) { + texUnitState[i].texAttrs.mirror = + state[i].texAttrs; + } else { + texUnitState[i].texAttrs.mirror = null; + } + + } + } else { + texUnitState[i].texAttrs = null; + } + + + if (state[i].texGen != null) { + if (state[i].texGen.changedFrequent != 0) { + texUnitState[i].texGen = state[i].texGen; + } else { + + // need to check for texGen.source because + // texGen could be pointing to the mirror + // in the last frame, so don't want to + // overwrite the mirror + + if (texUnitState[i].texGen == null || + texUnitState[i].texGen.source != null) { + texUnitState[i].texGen = + new TexCoordGenerationRetained(); + } + + texUnitState[i].texGen.set(state[i].texGen); + texUnitState[i].texGen.mirrorCompDirty = true; + + + // for sole user TextureBin, we are saving + // the mirror node component in the mirror + // reference in the clone object. This + // will be used in state download to + // avoid redundant download + + if (soleUser) { + texUnitState[i].texGen.mirror = state[i].texGen; + } else { + texUnitState[i].texGen.mirror = null; + } + } + } else { + texUnitState[i].texGen = null; + } + + + // track the last active texture unit + // and the total number of active texture unit + if (texUnitState[i].isTextureEnabled()) { + numActiveTexUnit++; + lastActiveTexUnitIndex = i; + + if (foundDisableUnit) { + + // mark that active texture units are not + // contiguous + tbFlag &= ~TextureBin.CONTIGUOUS_ACTIVE_UNITS; + } + } else { + foundDisableUnit = true; + } + } + } + + // check to see if the TextureBin sorting criteria is + // modified for this textureBin; if yes, mark that + // resorting is needed + + if ((texUnitState[0] == null && prevFirstTexture != null) || + (texUnitState[0] != null && + texUnitState[0].texture != prevFirstTexture)) { + tbFlag |= TextureBin.RESORT; + } + + } else { + + // check to see if the TextureBin sorting criteria is + // modified for this textureBin; if yes, mark that + // resorting is needed + + if (texUnitState != null && texUnitState[0].texture != null) { + tbFlag |= TextureBin.RESORT; + } + texUnitState = null; + } + + soleUserCompDirty = 0; + } + + + /** + * The TextureBin is to be removed from RenderBin, + * do the proper unsetting of any references + */ + void clear() { + + // make sure there is no reference to the scenegraph + app = null; + + // for each texture referenced in the texture units, decrement + // the reference count. If the reference count == 0, tell + // the renderer to free up the resource + if (texUnitState != null) { + + TextureRetained tex; + + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null) { + if (texUnitState[i].texture != null) { + tex = texUnitState[i].texture; + tex.decTextureBinRefCount(this); + + if (tex.textureBinRefCount == 0) { + renderBin.addTextureResourceFreeList(tex); + } + + texUnitState[i].texture = null; + } + + // make sure there is no more reference to the scenegraph + + texUnitState[i].mirror = null; + texUnitState[i].texture = null; + if (texUnitState[i].texAttrs != null && + texUnitState[i].texAttrs.source != null) { + texUnitState[i].texAttrs = null; + } + if (texUnitState[i].texGen != null && + texUnitState[i].texGen.source != null) { + texUnitState[i].texGen = null; + } + } + } + } + } + + + + /** + * This tests if the qiven textureUnitState matches this TextureBin + */ + boolean equals(TextureUnitStateRetained state[], RenderAtom ra) { + + int i, j, k = 0; + TextureRetained texture; + + // if this TextureBin is a soleUser case or the incoming + // app has changedFrequent bit set for any of the texture + // related component, then either the current TextureBin + // or the incoming app requires the same app match + if (((tbFlag & TextureBin.SOLE_USER) != 0) || + ((ra.app != null) && + (ra.app.changedFrequent & + (AppearanceRetained.TEXTURE | + AppearanceRetained.TEXCOORD_GEN | + AppearanceRetained.TEXTURE_ATTR | + AppearanceRetained.TEXTURE_UNIT_STATE)) != 0)) { + + if (app == ra.app) { + + // if this textureBin is currently on a zombie state, + // we'll need to put it on the update list to reevaluate + // the state, because while it is on a zombie state, + // texture state could have been changed. Example, + // application could have detached an appearance, + // made changes to the texture references, and then + // reattached the appearance. In this case, the texture + // changes would not have reflected to the textureBin + + if (numEditingRenderMolecules == 0) { + + //System.out.println("===> TB in zombie state " + this); + + if (soleUserCompDirty == 0) { + this.renderBin.tbUpdateList.add(this); + } + soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_REF; + } + return true; + + } else { + return false; + } + } + + if (texUnitState == null && state == null) + return (true); + + if (texUnitState == null || state == null) + return (false); + + if (state.length != texUnitState.length) + return (false); + + for (i = 0; i < texUnitState.length; i++) { + // If texture Unit State is null + if (texUnitState[i] == null) { + if (state[i] != null) + return (false); + } + else { + if (!texUnitState[i].equivalent(state[i])) { + return (false); + } + } + } + + // Check if the image component has changed(may be a clearLive texture + // change img component. setLive case) + // + if ((tbFlag & TextureBin.ON_RENDER_BIN_LIST) == 0) { + renderBin.addTextureBin(this); + tbFlag |= TextureBin.ON_RENDER_BIN_LIST; + } + + return (true); + + } + + + /* + // updateNodeComponentCheck is called for each soleUser TextureBin + // into which new renderAtom has been added. This method is called before + // updateNodeComponent() to allow TextureBin to catch any node + // component changes that have been missed because the changes + // come when there is no active renderAtom associated with the + // TextureBin. See bug# 4503926 for details. + public void updateNodeComponentCheck() { + + //System.out.println("TextureBin.updateNodeComponentCheck()"); + + tbFlag &= ~TextureBin.ON_UPDATE_CHECK_LIST; + + if ((soleUserCompDirty & SOLE_USER_DIRTY_REF) != 0) { + return ; + } + + if ((app.compChanged & (AppearanceRetained.TEXTURE | + AppearanceRetained.TEXCOORD_GEN | + AppearanceRetained.TEXTURE_ATTR | + AppearanceRetained.TEXTURE_UNIT_STATE)) != 0) { + if (soleUserCompDirty == 0) { + this.renderBin.tbUpdateList.add(this); + } + soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_REF; + + } else if (app.texUnitState != null) { + + // if one texture unit state has to be reevaluated, then + // it's enough update checking because reevaluating texture unit + // state will automatically take care of its node component + // updates. + + boolean done = false; + + for (int i = 0; i < app.texUnitState.length && !done; i++) { + if (app.texUnitState[i] != null) { + if (app.texUnitState[i].compChanged != 0) { + if (soleUserCompDirty == 0) { + this.renderBin.tbUpdateList.add(this); + } + soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TUS; + done = true; + } else { + if (app.texUnitState[i].texAttrs != null && + app.texUnitState[i].texAttrs.compChanged != 0) { + if (soleUserCompDirty == 0) { + this.renderBin.tbUpdateList.add(this); + } + soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TA; + } + if (app.texUnitState[i].texGen != null && + app.texUnitState[i].texGen.compChanged != 0) { + if (soleUserCompDirty == 0) { + this.renderBin.tbUpdateList.add(this); + } + soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TC; + } + if (app.texUnitState[i].texture != null && + ((app.texUnitState[i].texture.compChanged & + TextureRetained.ENABLE_CHANGED) != 0)) { + if (soleUserCompDirty == 0) { + this.renderBin.tbUpdateList.add(this); + } + soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TEXTURE; + } + } + } + } + } + } + */ + + + + + /** + * updateNodeComponent is called from RenderBin to update the + * clone copy of the sole user node component in TextureBin when the + * corresponding node component is being modified + */ + public void updateNodeComponent() { + + // don't bother to update if the TextureBin is already + // removed from RenderBin + + if ((tbFlag & TextureBin.ON_RENDER_BIN_LIST) == 0) + return; + + // if any of the texture reference in the appearance referenced + // by a sole user TextureBin is being modified, just do a reset + + if (((tbFlag & TextureBin.SOLE_USER) != 0) && + ((soleUserCompDirty & TextureBin.SOLE_USER_DIRTY_REF) != 0)) { + + resetTextureState(app.texUnitState); + return; + } + + if (texUnitState == null) { + soleUserCompDirty = 0; + return; + } + + if ((soleUserCompDirty & TextureBin.SOLE_USER_DIRTY_TUS) != 0) { + + // Now take care of the Texture Unit State changes + TextureUnitStateRetained tus, mirrorTUS = null; + boolean soleUser = ((tbFlag & TextureBin.SOLE_USER) != 0); + + for (int i = 0; i < texUnitState.length; i++) { + tus = texUnitState[i]; + if (tus != null) { + if (tus.mirror != null) { + + mirrorTUS = (TextureUnitStateRetained)tus.mirror; + + if (tus.texture != mirrorTUS.texture) { + if (tus.texture != null) { + tus.texture.decTextureBinRefCount(this); + } + tus.texture = mirrorTUS.texture; + if (tus.texture != null) { + tus.texture.incTextureBinRefCount(this); + } + + // the first texture (TextureBin sorting + // criteria) is modified, so needs to resort + + if (i == 0) { + tbFlag |= TextureBin.RESORT; + } + } + + + if (mirrorTUS.texAttrs != null) { + if (mirrorTUS.texAttrs.changedFrequent != 0) { + tus.texAttrs = mirrorTUS.texAttrs; + } else { + if (tus.texAttrs == null || + tus.texAttrs.source != null) { + tus.texAttrs = + new TextureAttributesRetained(); + } + tus.texAttrs.set(mirrorTUS.texAttrs); + tus.texAttrs.mirrorCompDirty = true; + + if (soleUser) { + tus.texAttrs.mirror = mirrorTUS.texAttrs; + } else { + tus.texAttrs.mirror = null; + } + } + } else { + tus.texAttrs = null; + } + + if (mirrorTUS.texGen != null) { + if (mirrorTUS.texGen.changedFrequent != 0) { + tus.texGen = mirrorTUS.texGen; + } else { + if (tus.texGen == null || + tus.texGen.source != null) { + tus.texGen = + new TexCoordGenerationRetained(); + } + tus.texGen.set(mirrorTUS.texGen); + tus.texGen.mirrorCompDirty = true; + + if (soleUser) { + tus.texGen.mirror = mirrorTUS.texGen; + } else { + tus.texGen.mirror = null; + } + } + } else { + tus.texGen = null; + } + } + } + } + + // need to reEvaluate # of active textures after the update + soleUserCompDirty |= TextureBin.SOLE_USER_DIRTY_TEXTURE; + + // TextureUnitState update automatically taken care of + // TextureAttributes & TexCoordGeneration update + + soleUserCompDirty &= ~(TextureBin.SOLE_USER_DIRTY_TA | + TextureBin.SOLE_USER_DIRTY_TC); + } + + if ((soleUserCompDirty & TextureBin.SOLE_USER_DIRTY_TEXTURE) != 0) { + + + + boolean foundDisableUnit = false; + + numActiveTexUnit = 0; + lastActiveTexUnitIndex = 0; + tbFlag |= TextureBin.CONTIGUOUS_ACTIVE_UNITS; + for (int i = 0; i < texUnitState.length; i++) { + + // track the last active texture unit + // and the total number of active texture unit + if (texUnitState[i] != null && + texUnitState[i].isTextureEnabled()) { + numActiveTexUnit++; + lastActiveTexUnitIndex = i; + + if (foundDisableUnit) { + + // mark that active texture units are not + // contiguous + tbFlag &= ~TextureBin.CONTIGUOUS_ACTIVE_UNITS; + } + } else { + foundDisableUnit = true; + } + } + } + + if ((soleUserCompDirty & TextureBin.SOLE_USER_DIRTY_TA) != 0) { + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null && + texUnitState[i].texAttrs != null && + texUnitState[i].texAttrs.mirror != null && + texUnitState[i].texAttrs.mirror.changedFrequent != 0) { + texUnitState[i].texAttrs = (TextureAttributesRetained) + texUnitState[i].texAttrs.mirror; + } + } + } + + if ((soleUserCompDirty & TextureBin.SOLE_USER_DIRTY_TC) != 0) { + for (int i = 0; i < texUnitState.length; i++) { + if (texUnitState[i] != null && + texUnitState[i].texGen != null && + texUnitState[i].texGen.mirror != null && + texUnitState[i].texGen.mirror.changedFrequent != 0) { + texUnitState[i].texGen = (TexCoordGenerationRetained) + texUnitState[i].texGen.mirror; + } + } + } + + soleUserCompDirty = 0; + } + + public void updateObject() { + if (!addOpaqueRMs.isEmpty()) { + opaqueRMList = addAll(opaqueRenderMoleculeMap, addOpaqueRMs, + opaqueRMList, true); + } + if (!addTransparentRMs.isEmpty()) { + // If transparent and not in bg geometry and inodepth + // sorted transparency + if (transparentRMList == null && + (renderBin.transpSortMode == View.TRANSPARENCY_SORT_NONE || + attributeBin.environmentSet.lightBin.geometryBackground != null)) { + // System.out.println("========> addTransparentTextureBin "+this); + transparentRMList = addAll(transparentRenderMoleculeMap, + addTransparentRMs, transparentRMList, false); + // Eventhough we are adding to transparentList , if all the RMS + // have been switched already due to changeLists, then there is + // nothing to add, and TBIN does not have any transparentRMList + if (transparentRMList != null) { + renderBin.addTransparentObject(this); + } + + } + else { + transparentRMList = addAll(transparentRenderMoleculeMap, + addTransparentRMs, transparentRMList, false); + } + } + tbFlag &= ~TextureBin.ON_UPDATE_LIST; + + } + + + /** + * Each list of renderMoledule with the same localToVworld + * is connect by rm.next and rm.prev. + * At the end of the list (i.e. rm.next = null) the field + * rm.nextMap is link to another list (with the same + * localToVworld). So during rendering it will traverse + * rm.next until this is null, then follow the .nextMap + * to access another list and use rm.next to continue + * until both rm.next and rm.nextMap are null. + * + * renderMoleculeMap is use to assist faster location of + * renderMolecule List with the same localToVWorld. The + * start of renderMolecule in the list with same + * localToVworld is insert in renderMoleculeMap. This + * map is clean up at removeRenderMolecule(). TextureBin + * also use the map for quick location of renderMolecule + * with the same localToVworld and attributes in + * findRenderMolecule(). + */ + RenderMolecule addAll(HashMap renderMoleculeMap, HashMap addRMs, + RenderMolecule startList, + boolean opaqueList) { + int i; + RenderMolecule r; + Collection c = addRMs.values(); + Iterator listIterator = c.iterator(); + RenderMolecule renderMoleculeList, head; + + while (listIterator.hasNext()) { + boolean changed = false; + ArrayList curList = (ArrayList)listIterator.next(); + r = (RenderMolecule)curList.get(0); + // If this is a opaque one , but has been switched to a transparentList or + // vice-versa (dur to changeLists function called before this), then + // do nothing! + // For changedFrequent case: Consider the case when a RM is added + // (so is in the addRM list) and then + // a change in transparent value occurs that make it from opaque to + // transparent (the switch is handled before this function is called) + if (r.isOpaqueOrInOG != opaqueList) { + continue; + } + // Get the list of renderMolecules for this transform + renderMoleculeList = (RenderMolecule)renderMoleculeMap.get( + r.localToVworld); + if (renderMoleculeList == null) { + renderMoleculeList = r; + renderMoleculeMap.put(r.localToVworld, renderMoleculeList); + // Add this renderMolecule at the beginning of RM list + if (startList == null) { + startList = r; + r.nextMap = null; + r.prevMap = null; + startList.dirtyAttrsAcrossRms = RenderMolecule.ALL_DIRTY_BITS; + } + else { + r.nextMap = startList; + startList.prevMap = r; + startList = r; + startList.nextMap.checkEquivalenceWithLeftNeighbor(r, + RenderMolecule.ALL_DIRTY_BITS); + } + + } + else { + // Insert the renderMolecule next to a RM that has equivalent + // texture unit state + if ((head = insertRenderMolecule(r, renderMoleculeList)) != null) { + if (renderMoleculeList.prevMap != null) { + renderMoleculeList.prevMap.nextMap = head; + } + head.prevMap = renderMoleculeList.prevMap; + renderMoleculeList.prevMap = null; + renderMoleculeList = head; + changed = true; + } + } + for (i = 1; i < curList.size(); i++) { + r = (RenderMolecule)curList.get(i); + // If this is a opaque one , but has been switched to a transparentList or + // vice-versa (dur to changeLists function called before this), then + // do nothing! + // For changedFrequent case: Consider the case when a RM is added + // (so is in the addRM list) and then + // a change in transparent value occurs that make it from opaque to + // transparent (the switch is handled before this function is called) + if (r.isOpaqueOrInOG != opaqueList) + continue; + if ((head = insertRenderMolecule(r, renderMoleculeList)) != null) { + if (renderMoleculeList.prevMap != null) { + renderMoleculeList.prevMap.nextMap = head; + } + head.prevMap = renderMoleculeList.prevMap; + renderMoleculeList.prevMap = null; + renderMoleculeList = head; + changed = true; + } + + } + if (changed) { + renderMoleculeMap.put(r.localToVworld, renderMoleculeList); + if (renderMoleculeList.prevMap != null) { + renderMoleculeList.checkEquivalenceWithLeftNeighbor( + renderMoleculeList.prevMap, + RenderMolecule.ALL_DIRTY_BITS); + } + else { + startList = renderMoleculeList; + startList.dirtyAttrsAcrossRms = + RenderMolecule.ALL_DIRTY_BITS; + } + } + } + + addRMs.clear(); + return startList; + } + + + // TODO: Could the analysis be done during insertRenderMolecule? + // Return the head of the list, + // if the insertion occurred at beginning of the list + RenderMolecule insertRenderMolecule(RenderMolecule r, + RenderMolecule renderMoleculeList) { + RenderMolecule rm, retval; + + // Look for a RM that has an equivalent material + rm = renderMoleculeList; + while (rm != null) { + if (rm.material == r.material || + (rm.definingMaterial != null && + rm.definingMaterial.equivalent(r.definingMaterial))) { + // Put it here + r.next = rm; + r.prev = rm.prev; + if (rm.prev == null) { + renderMoleculeList = r; + retval = renderMoleculeList; + } else { + rm.prev.next = r; + retval = null; + } + rm.prev = r; + r.checkEquivalenceWithBothNeighbors(RenderMolecule.ALL_DIRTY_BITS); + return retval; + } + // If they are not equivalent, then skip to the first one + // that has a different material using the dirty bits + else { + rm = rm.next; + while (rm != null && + ((rm.dirtyAttrsAcrossRms & RenderMolecule.MATERIAL_DIRTY) == 0)) { + rm = rm.next; + } + } + } + // Just put it up front + r.next = renderMoleculeList; + renderMoleculeList.prev = r; + renderMoleculeList = r; + r.checkEquivalenceWithBothNeighbors(RenderMolecule.ALL_DIRTY_BITS); + return renderMoleculeList; + } + + + /** + * Adds the given RenderMolecule to this TextureBin + */ + void addRenderMolecule(RenderMolecule r, RenderBin rb) { + RenderMolecule rm; + ArrayList list; + HashMap map; + r.textureBin = this; + + if (r.isOpaqueOrInOG) + map = addOpaqueRMs; + else + map = addTransparentRMs; + + if ((list = (ArrayList)map.get(r.localToVworld)) == null) { + list = new ArrayList(); + map.put(r.localToVworld, list); + } + list.add(r); + + if ((tbFlag & TextureBin.ON_UPDATE_LIST) == 0) { + tbFlag |= TextureBin.ON_UPDATE_LIST; + rb.objUpdateList.add(this); + } + } + + /** + * Removes the given RenderMolecule from this TextureBin + */ + void removeRenderMolecule(RenderMolecule r) { + ArrayList list; + int index; + boolean found = false; + RenderMolecule renderMoleculeList, rmlist; + HashMap addMap; + HashMap allMap; + r.textureBin = null; + + if (r.isOpaqueOrInOG) { + rmlist = opaqueRMList; + allMap = opaqueRenderMoleculeMap; + addMap = addOpaqueRMs; + } + else { + rmlist = transparentRMList; + allMap = transparentRenderMoleculeMap; + addMap = addTransparentRMs; + } + // If the renderMolecule being remove is contained in addRMs, then + // remove the renderMolecule from the addList + if ((list = (ArrayList) addMap.get(r.localToVworld)) != null) { + if ((index = list.indexOf(r)) != -1) { + list.remove(index); + // If this was the last element for this localToVworld, then remove + // the entry from the addRMs list + if (list.isEmpty()) { + addMap.remove(r.localToVworld); + } + + r.prev = null; + r.next = null; + renderBin.renderMoleculeFreelist.add(r); + found = true; + } + } + if (!found) { + RenderMolecule head = removeOneRM(r, allMap, rmlist); + + r.soleUserCompDirty = 0; + r.onUpdateList = 0; + if (r.definingPolygonAttributes != null && + (r.definingPolygonAttributes.changedFrequent != 0)) + r.definingPolygonAttributes = null; + + if (r.definingLineAttributes != null && + (r.definingLineAttributes.changedFrequent != 0)) + r.definingLineAttributes = null; + + if (r.definingPointAttributes != null && + (r.definingPointAttributes.changedFrequent != 0)) + r.definingPointAttributes = null; + + if (r.definingMaterial != null && + (r.definingMaterial.changedFrequent != 0)) + r.definingMaterial = null; + + if (r.definingColoringAttributes != null && + (r.definingColoringAttributes.changedFrequent != 0)) + r.definingColoringAttributes = null; + + if (r.definingTransparency != null && + (r.definingTransparency.changedFrequent != 0)) + r.definingTransparency = null; + + renderBin.removeRenderMolecule(r); + if (r.isOpaqueOrInOG) { + opaqueRMList = head; + } + else { + transparentRMList = head; + } + + } + // If the renderMolecule removed is not opaque then .. + if (!r.isOpaqueOrInOG && transparentRMList == null && (renderBin.transpSortMode == View.TRANSPARENCY_SORT_NONE || + attributeBin.environmentSet.lightBin.geometryBackground != null)) { + renderBin.removeTransparentObject(this); + } + // If the rm removed is the one that is referenced in the tinfo + // then change this reference + else if (parentTInfo != null && parentTInfo.rm == r) { + parentTInfo.rm = transparentRMList; + } + // Removal of this texture setting from the texCoordGenartion + // is done during the removeRenderAtom routine in RenderMolecule.java + // Only remove this texture bin if there are no more renderMolcules + // waiting to be added + if (opaqueRenderMoleculeMap.isEmpty() && addOpaqueRMs.isEmpty() && + transparentRenderMoleculeMap.isEmpty() && addTransparentRMs.isEmpty()) { + if ((tbFlag & TextureBin.ON_RENDER_BIN_LIST) != 0) { + tbFlag &= ~TextureBin.ON_RENDER_BIN_LIST; + renderBin.removeTextureBin(this); + } + + attributeBin.removeTextureBin(this); + texUnitState = null; + } + } + + + /** + * This method is called to update the state for this + * TextureBin. This is only applicable in the single-pass case. + * Multi-pass render will have to take care of its own + * state update. + */ + void updateAttributes(Canvas3D cv, int pass) { + + boolean dirty = ((cv.canvasDirty & (Canvas3D.TEXTUREBIN_DIRTY| + Canvas3D.TEXTUREATTRIBUTES_DIRTY)) != 0); + + + if (cv.textureBin == this && !dirty) { + return; + } + + cv.textureBin = this; + + // save the current number of active texture unit so as + // to be able to reset the one that is not enabled in this bin + + int lastActiveTexUnitIdx = -1; + + // set the number active texture unit in Canvas3D + cv.setNumActiveTexUnit(numActiveTexUnit); + + // state update + if (numActiveTexUnit <= 0) { + if (cv.getLastActiveTexUnit() >= 0) { + // no texture units enabled + + // when the canvas supports multi texture units, + // we'll need to reset texture for all texture units + if (cv.multiTexAccelerated) { + for (int i = 0; i <= cv.getLastActiveTexUnit(); i++) { + cv.resetTexture(cv.ctx, i); + } + // set the active texture unit back to 0 + cv.setNumActiveTexUnit(0); + cv.activeTextureUnit(cv.ctx, 0); + } else { + cv.resetTexture(cv.ctx, -1); + } + cv.setLastActiveTexUnit(-1); + } + } else if (pass < 0) { + int j = 0; + boolean oneToOneMapping; + + if ((pass == USE_VERTEXARRAY) || VirtualUniverse.mc.isD3D()) { + // d3d or when the texUnitStateMap requires more texture + // units than what is supported by the canvas, then + // we'll need a compact texture unit mapping, that is, + // only the enabled texUnitStates will be mapped to + // texture units. And as a matter of fact, the + // render atoms will be rendered as vertex array. + oneToOneMapping = false; + } else { + oneToOneMapping = true; + } + + for (int i = 0; i < texUnitState.length; i++) { + + if (j >= cv.texUnitState.length) { + // We finish enabling the texture state. + // Note that it is possible + // texUnitState.length > cv.texUnitState.length + + break; + } + + if ((texUnitState[i] != null) && + texUnitState[i].isTextureEnabled()) { + if (dirty || + cv.texUnitState[j].mirror == null || + cv.texUnitState[j].mirror != texUnitState[i].mirror) { + // update the texture unit state + texUnitState[i].updateNative(j, cv, false, false); + cv.texUnitState[j].mirror = texUnitState[i].mirror; + } + + // create a mapping that maps an active texture + // unit to a texture unit state + + lastActiveTexUnitIdx = j; + cv.setTexUnitStateMap(i, j++); + + + } else if (oneToOneMapping) { + // one to one mapping is needed when display list + // is used to render multi-textured geometries, + // since when display list is created, the texture + // unit state to texture unit mapping is based on + // the geometry texCoordMap only. At render time, + // the texture unit state enable flags could have + // been changed. In keeping a one to one mapping, + // we'll not need to rebuild the display list + if (j <= cv.getLastActiveTexUnit()) { + cv.resetTexture(cv.ctx, j); + } + + cv.setTexUnitStateMap(i, j++); + } + } + + // make sure to disable the remaining texture units + // since they could have been enabled from the previous + // texture bin + + for (int i = j; i <= cv.getLastActiveTexUnit(); i++) { + cv.resetTexture(cv.ctx, i); + } + + cv.setLastActiveTexUnit(lastActiveTexUnitIdx); + // tell the underlying library the texture unit mapping + + if ((pass == USE_DISPLAYLIST) && + (cv.numActiveTexUnit > 0)) { + cv.updateTexUnitStateMap(); + } + + // set the active texture unit back to 0 + cv.activeTextureUnit(cv.ctx, 0); + + } else { + // update the last active texture unit state + if (dirty || cv.texUnitState[0].mirror == null || + cv.texUnitState[0].mirror != + texUnitState[lastActiveTexUnitIndex].mirror) { + texUnitState[lastActiveTexUnitIndex].updateNative( + -1, cv, false, false); + cv.texUnitState[0].mirror = + texUnitState[lastActiveTexUnitIndex].mirror; + + cv.setTexUnitStateMap(0, 0); + cv.setLastActiveTexUnit(0); + } + } + cv.canvasDirty &= ~Canvas3D.TEXTUREBIN_DIRTY; + } + + + /** + * Renders this TextureBin + */ + void render(Canvas3D cv) { + render(cv, (Object) opaqueRMList); + } + + void render(Canvas3D cv, Object rlist) { + + boolean d3dBlendMode = false; + cv.texLinearMode = false; + + /* + System.out.println("TextureBin/render " + this + + " numActiveTexUnit= " + numActiveTexUnit + + " numTexUnitSupported= " + cv.numTexUnitSupported); + */ + + // include this TextureBin to the to-be-updated state set in canvas + cv.setStateToUpdate(Canvas3D.TEXTUREBIN_BIT, this); + + if ((texUnitState != null) && + VirtualUniverse.mc.isD3D()) { + TextureUnitStateRetained tus; + // use multi-pass if one of the stage use blend mode + for (int i = 0; i < texUnitState.length; i++) { + tus = texUnitState[i]; + if ((tus != null) && + tus.isTextureEnabled()) { + if (tus.needBlend2Pass(cv)) { + d3dBlendMode = true; + } + if ((tus.texGen != null) && + (tus.texGen.genMode == + TexCoordGeneration.OBJECT_LINEAR)) { + cv.texLinearMode = true; + } + } + } + } + + if ((numActiveTexUnit > cv.numTexUnitSupported) || + d3dBlendMode) { + multiPassRender(cv, rlist); + } else if ((numActiveTexUnit > 0) && + !VirtualUniverse.mc.isD3D() && + (texUnitState.length > cv.numTexUnitSupported) && + ((tbFlag & TextureBin.CONTIGUOUS_ACTIVE_UNITS) == 0)) { + renderList(cv, USE_VERTEXARRAY, rlist); + } else { + renderList(cv, USE_DISPLAYLIST, rlist); + } + } + + + /** + * render a render list + */ + void renderList(Canvas3D cv, int pass, Object rlist) { + + if (rlist instanceof RenderMolecule) { + renderList(cv, pass, (RenderMolecule) rlist); + } else if (rlist instanceof TransparentRenderingInfo) { + renderList(cv, pass, (TransparentRenderingInfo) rlist); + } + } + + + /** + * render list of RenderMolecule + */ + void renderList(Canvas3D cv, int pass, RenderMolecule rlist) { + + // bit mask of all attr fields that are equivalent across + // renderMolecules thro. ORing of invisible RMs. + int combinedDirtyBits = 0; + boolean rmVisible = true; + RenderMolecule rm = rlist; + + while (rm != null) { + if(rmVisible) { + combinedDirtyBits = rm.dirtyAttrsAcrossRms; + } + else { + combinedDirtyBits |= rm.dirtyAttrsAcrossRms; + } + + rmVisible = rm.render(cv, pass, combinedDirtyBits); + + + // next render molecule or the nextmap + if (rm.next == null) { + rm = rm.nextMap; + } + else { + rm = rm.next; + } + } + } + + + /** + * render sorted transparent list + */ + void renderList(Canvas3D cv, int pass, TransparentRenderingInfo tinfo) { + + RenderMolecule rm = tinfo.rm; + if (rm.isSwitchOn()) { + rm.transparentSortRender(cv, pass, tinfo); + } + } + + + + /** + * multi rendering pass to simulate multiple texture units + */ + void multiPassRender(Canvas3D cv, Object rlist) { + + boolean startToSimulate = false; + boolean isFogEnabled = false; + + // No lazy download of texture for multi-pass, + // update the texture states here now + + // update the environment state + + // no need to update the texture state in updateAttributes(), the state + // will be explicitly updated in the multi-pass + cv.setStateIsUpdated(Canvas3D.TEXTUREBIN_BIT); + cv.textureBin = this; + cv.canvasDirty &= ~Canvas3D.TEXTUREBIN_DIRTY; + cv.updateEnvState(); + + + // first reset those texture units that are currently enabled + + if (cv.multiTexAccelerated) { + int activeTexUnit = cv.getNumActiveTexUnit(); + for (int i = 0; i < activeTexUnit; i++) { + cv.resetTexture(cv.ctx, i); + } + // set the active texture unit back to 0 + cv.activeTextureUnit(cv.ctx, 0); + } + + // only texture unit 0 will be used in multi-pass case + cv.setNumActiveTexUnit(1); + cv.setLastActiveTexUnit(0); + + // first check if there is fog in the path + // if there is, then turn off fog now and turn it back on + // for the last pass only + isFogEnabled = (attributeBin.environmentSet.fog != null); + + TextureUnitStateRetained tus; + + for (int i = 0; i < texUnitState.length; i++) { + tus = texUnitState[i]; + + if (tus != null && tus.isTextureEnabled()) { + + + // update the canvas texture unit state cache + cv.texUnitState[0].mirror = tus.mirror; + + tus.updateNative(-1, cv, false, startToSimulate); + + if (!startToSimulate) { + startToSimulate = true; + if (isFogEnabled) { + cv.setFogEnableFlag(cv.ctx, false); + } + } + + if (!tus.needBlend2Pass(cv)) { + // turn on fog again in the last pass + + if (i == lastActiveTexUnitIndex && isFogEnabled) { + cv.setFogEnableFlag(cv.ctx, true); + } + renderList(cv, i, rlist); + + } else { + // D3d needs two passes to simulate Texture.Blend mode + tus.texAttrs.updateNative(cv, false, tus.texture.format); + renderList(cv, i, rlist); + + tus.texAttrs.updateBlend2Pass(cv.ctx); + + // turn on fog again in the last pass + + if (i == lastActiveTexUnitIndex && isFogEnabled) { + cv.setFogEnableFlag(cv.ctx, true); + } + renderList(cv, i, rlist); + + // restore original blend mode in case + tus.texAttrs.restoreBlend1Pass(cv.ctx); + } + } + } + + // adjust the depth test back to what it was + // and adjust the blend func to what it was + if (startToSimulate) { + cv.setStateToUpdate(Canvas3D.TRANSPARENCY_BIT); + } + } + + + void changeLists(RenderMolecule r) { + RenderMolecule renderMoleculeList, rmlist = null, head; + HashMap allMap = null; + ArrayList list; + int index; + boolean newRM = false; + // System.out.println("changeLists r = "+r+" tBin = "+this); + // If its a new RM then do nothing, otherwise move lists + if (r.isOpaqueOrInOG) { + if (opaqueRMList == null && + (r.prev == null && r.prevMap == null && r.next == null && + r.nextMap == null)) { + newRM = true; + } + else { + rmlist = opaqueRMList; + allMap = opaqueRenderMoleculeMap; + } + + } + else { + if (transparentRMList == null && + (r.prev == null && r.prevMap == null && r.next == null && + r.nextMap == null) ){ + newRM = true; + } + else { + rmlist = transparentRMList; + allMap = transparentRenderMoleculeMap; + } + } + if (!newRM) { + head = removeOneRM(r, allMap, rmlist); + + if (r.isOpaqueOrInOG) { + opaqueRMList = head; + } + else { + transparentRMList = head; + if (transparentRMList == null && + (renderBin.transpSortMode == View.TRANSPARENCY_SORT_NONE || + attributeBin.environmentSet.lightBin.geometryBackground != null)) { + renderBin.removeTransparentObject(this); + } + } + } + HashMap renderMoleculeMap; + RenderMolecule startList; + + // Now insert in the other bin + r.evalAlphaUsage(attributeBin.definingRenderingAttributes, texUnitState); + r.isOpaqueOrInOG = r.isOpaque() ||r.inOrderedGroup; + if (r.isOpaqueOrInOG) { + startList = opaqueRMList; + renderMoleculeMap = opaqueRenderMoleculeMap; + markDlistAsDirty(r); + } + else { + startList = transparentRMList; + renderMoleculeMap = transparentRenderMoleculeMap; + if ((r.primaryMoleculeType &RenderMolecule.DLIST_MOLECULE) != 0 && + renderBin.transpSortMode != View.TRANSPARENCY_SORT_NONE) { + renderBin.addDisplayListResourceFreeList(r); + renderBin.removeDirtyRenderMolecule(r); + + r.vwcBounds.set(null); + r.displayListId = 0; + r.displayListIdObj = null; + // Change the group type for all the rlistInfo in the primaryList + RenderAtomListInfo rinfo = r.primaryRenderAtomList; + while (rinfo != null) { + rinfo.groupType = RenderAtom.SEPARATE_DLIST_PER_RINFO; + if (rinfo.renderAtom.dlistIds == null) { + rinfo.renderAtom.dlistIds = new int[rinfo.renderAtom.rListInfo.length]; + + for (int k = 0; k < rinfo.renderAtom.dlistIds.length; k++) { + rinfo.renderAtom.dlistIds[k] = -1; + } + } + if (rinfo.renderAtom.dlistIds[rinfo.index] == -1) { + rinfo.renderAtom.dlistIds[rinfo.index] = VirtualUniverse.mc.getDisplayListId().intValue(); + renderBin.addDlistPerRinfo.add(rinfo); + } + rinfo = rinfo.next; + } + r.primaryMoleculeType = RenderMolecule.SEPARATE_DLIST_PER_RINFO_MOLECULE; + } + else { + markDlistAsDirty(r); + } + + } + renderMoleculeList = (RenderMolecule)renderMoleculeMap.get(r.localToVworld); + + if (renderMoleculeList == null) { + renderMoleculeList = r; + renderMoleculeMap.put(r.localToVworld, renderMoleculeList); + // Add this renderMolecule at the beginning of RM list + if (startList == null) { + startList = r; + r.nextMap = null; + r.prevMap = null; + startList.dirtyAttrsAcrossRms = RenderMolecule.ALL_DIRTY_BITS; + } + else { + r.nextMap = startList; + startList.prevMap = r; + startList = r; + startList.nextMap.checkEquivalenceWithLeftNeighbor(r,RenderMolecule.ALL_DIRTY_BITS); + } + + } + else { + // Insert the renderMolecule next to a RM that has equivalent + // texture unit state + if ((head = insertRenderMolecule(r, renderMoleculeList)) != null) { + if (renderMoleculeList.prevMap != null) { + renderMoleculeList.prevMap.nextMap = head; + } + head.prevMap = renderMoleculeList.prevMap; + renderMoleculeList.prevMap = null; + renderMoleculeList = head; + renderMoleculeMap.put(r.localToVworld, renderMoleculeList); + if (renderMoleculeList.prevMap != null) { + renderMoleculeList.checkEquivalenceWithLeftNeighbor(renderMoleculeList.prevMap, + RenderMolecule.ALL_DIRTY_BITS); + } + else { + startList.dirtyAttrsAcrossRms = RenderMolecule.ALL_DIRTY_BITS; + startList = renderMoleculeList; + } + } + + } + if (r.isOpaqueOrInOG) { + opaqueRMList = startList; + } + else { + // If transparent and not in bg geometry and inodepth sorted transparency + if (transparentRMList == null&& + (renderBin.transpSortMode == View.TRANSPARENCY_SORT_NONE || + attributeBin.environmentSet.lightBin.geometryBackground != null)) { + transparentRMList = startList; + renderBin.addTransparentObject(this); + } + else { + transparentRMList = startList; + } + + } + } + + RenderMolecule removeOneRM(RenderMolecule r, HashMap allMap, RenderMolecule list) { + RenderMolecule rmlist = list; + // In the middle, just remove and update + if (r.prev != null && r.next != null) { + r.prev.next = r.next; + r.next.prev = r.prev; + r.next.checkEquivalenceWithLeftNeighbor(r.prev,RenderMolecule.ALL_DIRTY_BITS); + } + // If whats is removed is at the end of an entry + else if (r.prev != null && r.next == null) { + r.prev.next = r.next; + r.prev.nextMap = r.nextMap; + if (r.nextMap != null) { + r.nextMap.prevMap = r.prev; + r.nextMap.checkEquivalenceWithLeftNeighbor(r.prev,RenderMolecule.ALL_DIRTY_BITS); + } + } + else if (r.prev == null && r.next != null) { + r.next.prev = null; + r.next.prevMap = r.prevMap; + if (r.prevMap != null) { + r.prevMap.nextMap = r.next; + r.next.checkEquivalenceWithLeftNeighbor(r.prevMap,RenderMolecule.ALL_DIRTY_BITS); + } + // Head of the rmList + else { + rmlist = r.next; + rmlist.dirtyAttrsAcrossRms = RenderMolecule.ALL_DIRTY_BITS; + } + allMap.put(r.localToVworld, r.next); + } + // Update the maps and remove this entry from the map list + else if (r.prev == null && r.next == null) { + if (r.prevMap != null) { + r.prevMap.nextMap = r.nextMap; + + } + else { + rmlist = r.nextMap; + if (r.nextMap != null) { + rmlist.dirtyAttrsAcrossRms = RenderMolecule.ALL_DIRTY_BITS; + } + } + if (r.nextMap != null) { + r.nextMap.prevMap = r.prevMap; + if (r.prevMap != null) { + r.nextMap.checkEquivalenceWithLeftNeighbor(r.prevMap,RenderMolecule.ALL_DIRTY_BITS); + } + + } + + allMap.remove(r.localToVworld); + + + } + r.prev = null; + r.next = null; + r.prevMap = null; + r.nextMap = null; + return rmlist; + } + + void markDlistAsDirty(RenderMolecule r) { + + if (r.primaryMoleculeType == RenderMolecule.DLIST_MOLECULE) { + renderBin.addDirtyRenderMolecule(r); + } + else if (r.primaryMoleculeType == RenderMolecule.SEPARATE_DLIST_PER_RINFO_MOLECULE) { + RenderAtomListInfo ra = r.primaryRenderAtomList; + while (ra != null) { + renderBin.addDlistPerRinfo.add(ra); + ra = ra.next; + } + } + } + + + void decrActiveRenderMolecule() { + numEditingRenderMolecules--; + + if (numEditingRenderMolecules == 0) { + + // if number of editing renderMolecules goes to 0, + // inform the attributeBin that this textureBin goes to + // zombie state + + attributeBin.decrActiveTextureBin(); + } + } + + void incrActiveRenderMolecule() { + + if (numEditingRenderMolecules == 0) { + + // if this textureBin is in zombie state, inform + // the attributeBin that this textureBin is activated again. + + attributeBin.incrActiveTextureBin(); + } + + numEditingRenderMolecules++; + } +} + + diff --git a/src/classes/share/javax/media/j3d/TextureCubeMap.java b/src/classes/share/javax/media/j3d/TextureCubeMap.java new file mode 100644 index 0000000..6927cbf --- /dev/null +++ b/src/classes/share/javax/media/j3d/TextureCubeMap.java @@ -0,0 +1,344 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * TextureCubeMap is a subclass of Texture class. It defines + * a special kind of texture mapping which is composed of a set of six + * 2D images representating the six faces of a cube. The texture coordinate + * (s,t,r) is used as a 3D direction vector emanating from the center + * of a cube to select a particular face of the cube based on the + * largest magnitude coordinate (the major axis). A new 2D texture coordinate + * (s,t) is then determined by dividing the other two coordinates (the minor + * axes) by the major axis value. The new coordinate is then used for + * texel lookup from the selected texture image of this cube map. + * + * The TextureCubeMap image is defined by specifying the images for each + * face of the cube. The cube map texture can be thought of as centered at + * the orgin of and aligned to an XYZ coordinate system. The names + * of the cube faces are: + * + * <UL> + * <LI>POSITIVE_X</LI> + * <LI>NEGATIVE_X</LI> + * <LI>POSITIVE_Y</LI> + * <LI>NEGATIVE_Y</LI> + * <LI>POSITIVE_Z</LI> + * <LI>NEGATIVE_Z</LI> + * </UL> + * + * @since Java 3D 1.3 + * @see Canvas3D#queryProperties + */ +public class TextureCubeMap extends Texture { + + /** + * Specifies the face of the cube that is pierced by the positive x axis + */ + public static final int POSITIVE_X = 0; + + /** + * Specifies the face of the cube that is pierced by the negative x axis + */ + public static final int NEGATIVE_X = 1; + + /** + * Specifies the face of the cube that is pierced by the positive y axis + */ + public static final int POSITIVE_Y = 2; + + /** + * Specifies the face of the cube that is pierced by the negative y axis + */ + public static final int NEGATIVE_Y = 3; + + /** + * Specifies the face of the cube that is pierced by the positive z axis + */ + public static final int POSITIVE_Z = 4; + + /** + * Specifies the face of the cube that is pierced by the negative z axis + */ + public static final int NEGATIVE_Z = 5; + + + /** + * Constructs a texture object using default values. + * Note that the default constructor creates a texture object with + * a width of 0 and is, therefore, not useful. + */ + public TextureCubeMap() { + super(); + } + + /** + * Constructs an empty TextureCubeMap object with specified mipmapMode + * format, and width. Image at base level + * must be set by + * the application using 'setImage' method. If mipmapMode is + * set to MULTI_LEVEL_MIPMAP, images for base level through maximum level + * must be set. + * Note that cube map is square in dimensions, hence specifying width + * is sufficient. + * @param mipmapMode type of mipmap for this Texture: One of + * BASE_LEVEL, MULTI_LEVEL_MIPMAP. + * @param format data format of Textures saved in this object. + * One of INTENSITY, LUMINANCE, ALPHA, LUMINANCE_ALPHA, RGB, RGBA. + * @param width width of image at level 0. Must be power of 2. + * @exception IllegalArgumentException if width is NOT + * power of 2 OR invalid format/mipmapMode is specified. + */ + public TextureCubeMap( + int mipmapMode, + int format, + int width){ + + super(mipmapMode, format, width, width); + } + + /** + * Constructs an empty TextureCubeMap object with specified mipmapMode + * format, width, and boundary width. Image at base level + * must be set by + * the application using 'setImage' method. If mipmapMode is + * set to MULTI_LEVEL_MIPMAP, images for base level through maximum level + * must be set. + * Note that cube map is square in dimensions, hence specifying width + * is sufficient. + * @param mipmapMode type of mipmap for this Texture: One of + * BASE_LEVEL, MULTI_LEVEL_MIPMAP. + * @param format data format of Textures saved in this object. + * One of INTENSITY, LUMINANCE, ALPHA, LUMINANCE_ALPHA, RGB, RGBA. + * @param width width of image at level 0. Must be power of 2. + * @param boundaryWidth width of the boundary. + * + * @exception IllegalArgumentException if width is NOT + * power of 2 OR invalid format/mipmapMode is specified. + */ + public TextureCubeMap( + int mipmapMode, + int format, + int width, + int boundaryWidth){ + + super(mipmapMode, format, width, width, boundaryWidth); + } + + /** + * Sets the image for a specified mipmap level of a specified face + * of the cube map + * + * @param level mipmap level + * @param face face of the cube map. One of: + * <code>POSITIVE_X</code>, <code>NEGATIVE_X</code>, + * <code>POSITIVE_Y</code>, <code>NEGATIVE_Y</code>, + * <code>POSITIVE_Z</code> or <code>NEGATIVE_Z</code>. + * @param image ImageComponent2D object containing the image + * + * @exception IllegalArgumentException if + * <code>face</code> has a value other + * than <code>POSITIVE_X</code>, <code>NEGATIVE_X</code>, + * <code>POSITIVE_Y</code>, <code>NEGATIVE_Y</code>, + * <code>POSITIVE_Z</code> or <code>NEGATIVE_Z</code>. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + */ + public void setImage(int level, int face, ImageComponent2D image) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_WRITE)) + throw new CapabilityNotSetException( + J3dI18N.getString("TextureCubeMap1")); + } + + if (isLive()) + ((TextureCubeMapRetained)this.retained).setImage(level, face, image); + else + ((TextureCubeMapRetained)this.retained).initImage(level, face, image); + } + + /** + * Sets the array of images for mipmap levels from base level through + * max level for a specified face of the cube map + * + * @param face face of the cube map. One of: + * <code>POSITIVE_X</code>, <code>NEGATIVE_X</code>, + * <code>POSITIVE_Y</code>, <code>NEGATIVE_Y</code>, + * <code>POSITIVE_Z</code> or <code>NEGATIVE_Z</code>. + * @param images array of ImageComponent2D objects containing the images + * + * @exception IllegalArgumentException if + * <code>face</code> has a value other + * than <code>POSITIVE_X</code>, <code>NEGATIVE_X</code>, + * <code>POSITIVE_Y</code>, <code>NEGATIVE_Y</code>, + * <code>POSITIVE_Z</code> or <code>NEGATIVE_Z</code>. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + */ + public void setImages(int face, ImageComponent2D[] images) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_WRITE)) + throw new CapabilityNotSetException( + J3dI18N.getString("TextureCubeMap1")); + } + + if (isLive()) + ((TextureCubeMapRetained)this.retained).setImages(face, images); + else + ((TextureCubeMapRetained)this.retained).initImages(face, images); + + } + + + /** + * Retrieves the image for a specified mipmap level of a particular + * face of the cube map. + * @param level mipmap level to get. + * @param face face of the cube map. One of: + * <code>POSITIVE_X</code>, <code>NEGATIVE_X</code>, + * <code>POSITIVE_Y</code>, <code>NEGATIVE_Y</code>, + * <code>POSITIVE_Z</code> or <code>NEGATIVE_Z</code>. + * @return the ImageComponent object containing the texture image at + * the specified mipmap level. + * + * @exception IllegalArgumentException if + * <code>face</code> has a value other + * than <code>POSITIVE_X</code>, <code>NEGATIVE_X</code>, + * <code>POSITIVE_Y</code>, <code>NEGATIVE_Y</code>, + * <code>POSITIVE_Z</code> or <code>NEGATIVE_Z</code>. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public ImageComponent getImage(int level, int face) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException( + J3dI18N.getString("TextureCubeMap2")); + } + + return ((TextureCubeMapRetained)this.retained).getImage(level, face); + } + + /** + * Retrieves the array of images for all mipmap level of a particular + * face of the cube map. + * @param face face of the cube map. One of: + * <code>POSITIVE_X</code>, <code>NEGATIVE_X</code>, + * <code>POSITIVE_Y</code>, <code>NEGATIVE_Y</code>, + * <code>POSITIVE_Z</code> or <code>NEGATIVE_Z</code>. + * @return an array of ImageComponent object for the particular face of + * of the cube map. + * + * @exception IllegalArgumentException if + * <code>face</code> has a value other + * than <code>POSITIVE_X</code>, <code>NEGATIVE_X</code>, + * <code>POSITIVE_Y</code>, <code>NEGATIVE_Y</code>, + * <code>POSITIVE_Z</code> or <code>NEGATIVE_Z</code>. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public ImageComponent[] getImages(int face) { + if (isLiveOrCompiled()) { + if(!this.getCapability(ALLOW_IMAGE_READ)) + throw new CapabilityNotSetException( + J3dI18N.getString("TextureCubeMap2")); + } + + return ((TextureCubeMapRetained)this.retained).getImages(face); + } + + + /** + * This method is not supported for TextureCubeMap. + * A face of the cube map has to be specified when setting an image + * for a particular level of the cube map. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setImage(int level, ImageComponent image) { + throw new UnsupportedOperationException(); + } + + + /** + * This method is not supported for TextureCubeMap. + * A face of the cube map has to be specified when setting images + * for the cube map. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public void setImages(ImageComponent[] images) { + throw new UnsupportedOperationException(); + } + + /** + * This method is not supported for TextureCubeMap. + * A face of the cube map has to be specified when retrieving an image + * for a particular level of the cube map. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public ImageComponent getImage(int level) { + throw new UnsupportedOperationException(); + } + + + /** + * This method is not supported for TextureCubeMap. + * A face of the cube map has to be specified when retrieving images + * for the cube map. + * + * @exception UnsupportedOperationException this method is not supported + * + * @since Java 3D 1.3 + */ + public ImageComponent[] getImages() { + throw new UnsupportedOperationException(); + } + + + /** + * Creates a retained mode TextureCubeMapRetained object that this + * TextureCubeMap component object will point to. + */ + void createRetained() { + this.retained = new TextureCubeMapRetained(); + this.retained.setSource(this); + } + + /** + * NOTE: Applications should not call this method directly. + * It should only be called by the cloneNode method. + * + * @deprecated replaced with duplicateNodeComponent( + * NodeComponent originalNodeComponent, boolean forceDuplicate) + */ + public void duplicateNodeComponent(NodeComponent originalNodeComponent) { + checkDuplicateNodeComponent(originalNodeComponent); + } +} + diff --git a/src/classes/share/javax/media/j3d/TextureCubeMapRetained.java b/src/classes/share/javax/media/j3d/TextureCubeMapRetained.java new file mode 100644 index 0000000..4283b2e --- /dev/null +++ b/src/classes/share/javax/media/j3d/TextureCubeMapRetained.java @@ -0,0 +1,301 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; +import javax.vecmath.Color4f; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; + +/** + * TextureCubeMap is a subclass of Texture class. + */ +class TextureCubeMapRetained extends TextureRetained { + + + static final int NUMFACES = 6; + + + void initialize(int format, int width, int widPower, + int height, int heiPower, int mipmapMode, + int boundaryWidth) { + + this.numFaces = 6; + + super.initialize(format, width, widPower, height, heiPower, + mipmapMode, boundaryWidth); + } + + + /** + * Sets a specified mipmap level for a particular face of the cubemap. + */ + void initImage(int level, int face, ImageComponent image) { + if (this.images == null) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureRetained0")); + } + + if (image instanceof ImageComponent3D) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureCubeMap3")); + } + + + if (face < TextureCubeMap.POSITIVE_X || + face > TextureCubeMap.NEGATIVE_Z) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureCubeMap4")); + } + + if (this.source.isLive()) { + if (this.images[face][level] != null) { + this.images[face][level].clearLive(refCount); + } + + + if (image != null) { + ((ImageComponentRetained)image.retained).setLive( + inBackgroundGroup, refCount); + } + } + + ((ImageComponent2DRetained)image.retained).setTextureRef(); + + if (image != null) { + this.images[face][level] = (ImageComponentRetained)image.retained; + } else { + this.images[face][level] = null; + } + } + + final void setImage(int level, int face, ImageComponent image) { + + checkImageSize(level, image); + + initImage(level, face, image); + + Object arg[] = new Object[3]; + arg[0] = new Integer(level); + arg[1] = image; + arg[2] = new Integer(face); + sendMessage(IMAGE_CHANGED, arg); + + // If the user has set enable to true, then if the image is null + // turn off texture enable + if (userSpecifiedEnable) { + enable = userSpecifiedEnable; + if (image != null && level < maxLevels) { + ImageComponentRetained img= (ImageComponentRetained)image.retained; + if (img.isByReference()) { + if (img.bImage[0] == null) { + enable = false; + } + } + else { + if (img.imageYup == null) { + enable = false; + } + } + if (!enable) + sendMessage(ENABLE_CHANGED, Boolean.FALSE); + } + } + } + + void initImages(int face, ImageComponent[] images) { + + if (images.length != maxLevels) + throw new IllegalArgumentException(J3dI18N.getString("Texture20")); + + for (int i = 0; i < images.length; i++) { + initImage(i, face, images[i]); + } + } + + final void setImages(int face, ImageComponent[] images) { + + int i; + ImageComponentRetained[] imagesRet = + new ImageComponentRetained[images.length]; + for (i = 0; i < images.length; i++) { + imagesRet[i] = (ImageComponentRetained)images[i].retained; + } + checkSizes(imagesRet); + + initImages(face, images); + + ImageComponent [] imgs = new ImageComponent[images.length]; + for (i = 0; i < images.length; i++) { + imgs[i] = images[i]; + } + + Object args[] = new Object[2]; + args[0] = imgs; + args[1] = new Integer(face); + + sendMessage(IMAGES_CHANGED, args); + // If the user has set enable to true, then if the image is null + // turn off texture enable + if (userSpecifiedEnable) { + enable = userSpecifiedEnable; + i = 0; + while (enable && i < maxLevels) { + if (images[i] != null) { + ImageComponentRetained img= (ImageComponentRetained)images[i].retained; + if (img.isByReference()) { + if (img.bImage[0] == null) { + enable = false; + } + } + else { + if (img.imageYup == null) { + enable = false; + } + } + } + i++; + } + if (!enable) { + sendMessage(ENABLE_CHANGED, Boolean.FALSE); + } + } + } + + + + + /** + * Gets a specified mipmap level of a particular face of the cube map. + * @param level mipmap level to get + * @param face face of the cube map + * @return the pixel array object containing the texture image + */ + final ImageComponent getImage(int level, int face) { + + if (face < TextureCubeMap.POSITIVE_X || + face > TextureCubeMap.NEGATIVE_Z) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureCubeMap4")); + } + + return (((images != null) && (images[face][level] != null)) ? + (ImageComponent)images[face][level].source : null); + } + + + /** + * Gets an array of image for a particular face of the cube map. + * @param face face of the cube map + * @return the pixel array object containing the texture image + */ + final ImageComponent[] getImages(int face) { + + if (images == null) + return null; + + if (face < TextureCubeMap.POSITIVE_X || + face > TextureCubeMap.NEGATIVE_Z) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureCubeMap4")); + } + + ImageComponent [] rImages = new ImageComponent[images[face].length]; + for (int i = 0; i < images[face].length; i++) { + if (images[face][i] != null) + rImages[i] = (ImageComponent)images[face][i].source; + else + rImages[i] = null; + } + return rImages; + } + + + native void bindTexture(long ctx, int objectId, boolean enable); + + native void updateTextureFilterModes(long ctx, int minFilter, + int magFilter); + + native void updateTextureBoundary(long ctx, + int boundaryModeS, int boundaryModeT, + float boundaryRed, float boundaryGreen, + float boundaryBlue, float boundaryAlpha); + + native void updateTextureSharpenFunc(long ctx, + int numSharpenTextureFuncPts, + float[] sharpenTextureFuncPts); + + native void updateTextureFilter4Func(long ctx, + int numFilter4FuncPts, + float[] filter4FuncPts); + + native void updateTextureAnisotropicFilter(long ctx, float degree); + + native void updateTextureImage(long ctx, + int face, + int numLevels, + int level, + int internalFormat, int format, + int width, int height, + int boundaryWidth, byte[] imageYup); + + native void updateTextureSubImage(long ctx, int face, + int level, int xoffset, int yoffset, + int internalFormat,int format, + int imgXOffset, int imgYOffset, + int tilew, + int width, int height, + byte[] image); + + + + /** + * Load level 0 explicitly with null data pointer to allow + * mipmapping when level 0 is not the base level + */ + void updateTextureDimensions(Canvas3D cv) { + for (int i = 0; i < 6; i++) { + updateTextureImage(cv.ctx, i, maxLevels, 0, + format, ImageComponentRetained.BYTE_RGBA, + width, height, boundaryWidth, null); + } + } + + + + // This is just a wrapper of the native method. + + void updateTextureImage(Canvas3D cv, int face, int numLevels, int level, + int format, int storedFormat, + int width, int height, + int boundaryWidth, byte[] data) { + + updateTextureImage(cv.ctx, face, numLevels, level, format, + storedFormat, width, height, + boundaryWidth, data); + } + + + // This is just a wrapper of the native method. + + void updateTextureSubImage(Canvas3D cv, int face, int level, + int xoffset, int yoffset, int format, + int storedFormat, int imgXOffset, + int imgYOffset, int tileWidth, + int width, int height, byte[] data) { + + updateTextureSubImage(cv.ctx, face, level, xoffset, yoffset, format, + storedFormat, imgXOffset, imgYOffset, + tileWidth, width, height, data); + } +} diff --git a/src/classes/share/javax/media/j3d/TextureRetained.java b/src/classes/share/javax/media/j3d/TextureRetained.java new file mode 100644 index 0000000..c392881 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TextureRetained.java @@ -0,0 +1,2597 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; +import javax.vecmath.*; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; + +/** + * The Texture object is a component object of an Appearance object + * that defines the texture properties used when texture mapping is + * enabled. Texture object is an abstract class and all texture + * objects must be created as either a Texture2D object or a + * Texture3D object. + */ +abstract class TextureRetained extends NodeComponentRetained { + // A list of pre-defined bits to indicate which component + // in this Texture object changed. + static final int ENABLE_CHANGED = 0x001; + static final int COLOR_CHANGED = 0x002; + static final int IMAGE_CHANGED = 0x004; + static final int STATE_CHANGED = 0x008; + static final int UPDATE_IMAGE = 0x010; + static final int IMAGES_CHANGED = 0x020; + static final int BASE_LEVEL_CHANGED = 0x040; + static final int MAX_LEVEL_CHANGED = 0x080; + static final int MIN_LOD_CHANGED = 0x100; + static final int MAX_LOD_CHANGED = 0x200; + static final int LOD_OFFSET_CHANGED = 0x400; + + // constants for min and mag filter + static final int MIN_FILTER = 0; + static final int MAG_FILTER = 1; + + // Boundary width + int boundaryWidth = 0; + + // Boundary modes (wrap, clamp, clamp_to_edge, clamp_to_boundary) + int boundaryModeS = Texture.WRAP; + int boundaryModeT = Texture.WRAP; + + // Filter modes + int minFilter = Texture.BASE_LEVEL_POINT; + int magFilter = Texture.BASE_LEVEL_POINT; + + // Integer flag that contains bitset to indicate + // which field changed. + int isDirty = 0xffff; + + // Texture boundary color + Color4f boundaryColor = new Color4f(0.0f, 0.0f, 0.0f, 0.0f); + + // Texture Object Id used by native code. + int objectId = -1; + + int mipmapMode = Texture.BASE_LEVEL; // Type of mip-mapping + int format = Texture.RGB; // Texture format + int width = 1; // Width in pixels (2**n) + int height = 1; // Height in pixels (2**m) + + ImageComponentRetained images[][]; // Array of images (one for each mipmap level) + boolean imagesLoaded = false; // TRUE if all mipmap levels are loaded + int mipmapLevels; // Number of MIPMAP levels needed + int maxLevels = 0; // maximum number of levels needed for + // the mipmapMode of this texture + int maxMipMapLevels = 0; // maximum number of mipmap levels that + // can be defined for this texture + + int numFaces = 1; // For CubeMap, it is 6 + + int baseLevel = 0; + int maximumLevel = 0; + float minimumLod = -1000.0f; + float maximumLod = 1000.0f; + Point3f lodOffset = null; + + + // Texture mapping enable switch + // This enable is derived from the user specified enable + // and whether the buf image in the imagecomp is null + boolean enable = true; + + // User specified enable + boolean userSpecifiedEnable = true; + + + // true if alpha channel need update during rendering + boolean isAlphaNeedUpdate = false; + + // sharpen texture info + int numSharpenTextureFuncPts = 0; + float sharpenTextureFuncPts[] = null; // array of pairs of floats + // first value for LOD + // second value for the fcn value + + // filter4 info + float filter4FuncPts[] = null; + + // anisotropic filter info + int anisotropicFilterMode = Texture.ANISOTROPIC_NONE; + float anisotropicFilterDegree = 1.0f; + + + // Each bit corresponds to a unique renderer if shared context + // or a unique canvas otherwise. + // This mask specifies which renderer/canvas has loaded the + // texture images. 0 means no renderer/canvas has loaded the texture. + // 1 at the particular bit means that renderer/canvas has loaded the + // texture. 0 means otherwise. + int resourceCreationMask = 0x0; + + // Each bit corresponds to a unique renderer if shared context + // or a unique canvas otherwise + // This mask specifies if texture images are up-to-date. + // 0 at a particular bit means texture images are not up-to-date. + // 1 means otherwise. If it specifies 0, then it needs to go + // through the imageUpdateInfo to update the images accordingly. + // + int resourceUpdatedMask = 0x0; + + // Each bit corresponds to a unique renderer if shared context + // or a unique canvas otherwise + // This mask specifies if texture lod info is up-to-date. + // 0 at a particular bit means texture lod info is not up-to-date. + // 1 means otherwise. + // + int resourceLodUpdatedMask = 0x0; + + // Each bit corresponds to a unique renderer if shared context + // or a unique canvas otherwise + // This mask specifies if texture is in the resource reload list + // 0 at a particular bit means texture is not in reload list + // 1 means otherwise. + // + int resourceInReloadList = 0x0; + + // image update info + ArrayList imageUpdateInfo[][]; + + + int imageUpdatePruneMask[]; + + + // textureBin reference counter + int textureBinRefCount = 0; + + // This is used for D3D only to check whether texture need to + // resend down + private int texTimestamp = 0; + + // need to synchronize access from multiple rendering threads + Object resourceLock = new Object(); + + + void initialize(int format, int width, int widPower, + int height, int heiPower, int mipmapMode, + int boundaryWidth) { + + this.mipmapMode = mipmapMode; + this.format = format; + this.width = width; + this.height = height; + this.boundaryWidth = boundaryWidth; + + // determine the maximum number of mipmap levels that can be + // defined from the specified dimension + + if (widPower > heiPower) { + maxMipMapLevels = widPower + 1; + } else { + maxMipMapLevels = heiPower + 1; + } + + + // determine the maximum number of mipmap levels that will be + // needed with the current mipmapMode + + if (mipmapMode != Texture.BASE_LEVEL) { + baseLevel = 0; + maximumLevel = maxMipMapLevels - 1; + maxLevels = maxMipMapLevels; + } else { + baseLevel = 0; + maximumLevel = 0; + maxLevels = 1; + } + + images = new ImageComponentRetained[numFaces][maxLevels]; + + for (int j = 0; j < numFaces; j++) { + for (int i = 0; i < maxLevels; i++) { + images[j][i] = null; + } + } + imagesLoaded = false; + + } + + final int getFormat() { + return this.format; + } + + final int getWidth() { + return this.width; + } + + final int getHeight() { + return this.height; + } + + final int numMipMapLevels() { + return (maximumLevel - baseLevel + 1); + } + + /** + * Sets the boundary mode for the S coordinate in this texture object. + * @param boundaryModeS the boundary mode for the S coordinate, + * one of: CLAMP or WRAP. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final void initBoundaryModeS(int boundaryModeS) { + this.boundaryModeS = boundaryModeS; + } + + /** + * Retrieves the boundary mode for the S coordinate. + * @return the current boundary mode for the S coordinate. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final int getBoundaryModeS() { + return boundaryModeS; + } + + /** + * Sets the boundary mode for the T coordinate in this texture object. + * @param boundaryModeT the boundary mode for the T coordinate, + * one of: CLAMP or WRAP. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final void initBoundaryModeT(int boundaryModeT) { + this.boundaryModeT = boundaryModeT; + } + + /** + * Retrieves the boundary mode for the T coordinate. + * @return the current boundary mode for the T coordinate. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final int getBoundaryModeT() { + return boundaryModeT; + } + + /** + * Retrieves the boundary width. + * @return the boundary width of this texture. + */ + final int getBoundaryWidth() { + return boundaryWidth; + } + + /** + * Sets the minification filter function. This + * function is used when the pixel being rendered maps to an area + * greater than one texel. + * @param minFilter the minification filter, one of: + * FASTEST, NICEST, BASE_LEVEL_POINT, BASE_LEVEL_LINEAR, + * MULTI_LEVEL_POINT, MULTI_LEVEL_LINEAR. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final void initMinFilter(int minFilter) { + this.minFilter = minFilter; + } + + /** + * Retrieves the minification filter. + * @return the current minification filter function. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final int getMinFilter() { + return minFilter; + } + + /** + * Sets the magnification filter function. This + * function is used when the pixel being rendered maps to an area + * less than or equal to one texel. + * @param magFilter the magnification filter, one of: + * FASTEST, NICEST, BASE_LEVEL_POINT, or BASE_LEVEL_LINEAR. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final void initMagFilter(int magFilter) { + this.magFilter = magFilter; + } + + /** + * Retrieves the magnification filter. + * @return the current magnification filter function. + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final int getMagFilter() { + return magFilter; + } + + /** + * Sets a specified mipmap level. + * @param level mipmap level to set: 0 is the base level + * @param image pixel array object containing the texture image + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + * @exception IllegalArgumentException if an ImageComponent3D + * is used in a Texture2D or ImageComponent2D in Texture3D + * power of 2 OR invalid format/mipmapMode is specified. + */ + void initImage(int level, ImageComponent image) { + if (this.images == null) { + throw new IllegalArgumentException(J3dI18N.getString("TextureRetained0")); + } + + if (this.source instanceof Texture2D) { + if (image instanceof ImageComponent3D) + throw new IllegalArgumentException(J3dI18N.getString("Texture8")) +; + } else { + + if (image instanceof ImageComponent2D) + throw new IllegalArgumentException(J3dI18N.getString("Texture14") +); + } + + + if (this.source.isLive()) { + + if (this.images[0][level] != null) { + this.images[0][level].clearLive(refCount); + } + + + if (image != null) { + ((ImageComponentRetained)image.retained).setLive(inBackgroundGroup, refCount); + } + } + + if (this instanceof Texture2DRetained) { + ((ImageComponent2DRetained)image.retained).setTextureRef(); + } else { + ((ImageComponent3DRetained)image.retained).setTextureRef(); + } + + if (image != null) { + this.images[0][level] = (ImageComponentRetained)image.retained; + + } else { + this.images[0][level] = null; + } + } + + final void checkImageSize(int level, ImageComponent image) { + if (image != null) { + int imgHeight = ((ImageComponentRetained)image.retained).height; + int imgWidth = ((ImageComponentRetained)image.retained).width; + int i, tmp = 1; + // calculate tmp = 2**level + for (i=0; i < level; i++,tmp *= 2); + + int hgt = height/tmp, wdh = width / tmp; + if (hgt < 1) hgt = 1; + if (wdh < 1) wdh = 1; + + if ((hgt != imgHeight) || (wdh != imgWidth)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureRetained1")); + } + } + } + + final void checkSizes(ImageComponentRetained images[]) { + // check that the image at each level is w/2 h/2 of the image at the + // previous level + if (images != null) { + + // only need to check if there is more than 1 level + if (images.length > 1) { + int compareW = images[0].width/2; + int compareH = images[0].height/2; + int w, h; + for (int i = 1; i < images.length; i++) { + w = images[i].width; + h = images[i].height; + if (compareW < 1) compareW = 1; + if (compareH < 1) compareH = 1; + if ((w != compareW) && (h != compareH)) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureRetained1")); + } + compareW = w/2; + compareH = h/2; + } + } + } + } + + final void setImage(int level, ImageComponent image) { + + checkImageSize(level, image); + + initImage(level, image); + + Object arg[] = new Object[3]; + arg[0] = new Integer(level); + arg[1] = image; + arg[2] = new Integer(0); + sendMessage(IMAGE_CHANGED, arg); + + // If the user has set enable to true, then if the image is null + // turn off texture enable + + if (userSpecifiedEnable) { + enable = userSpecifiedEnable; + if (image != null && level >= baseLevel && level <= maximumLevel) { + ImageComponentRetained img= (ImageComponentRetained)image.retained; + if (img.isByReference()) { + if (img.bImage[0] == null) { + enable = false; + } + } + else { + if (img.imageYup == null) { + enable = false; + } + } + if (!enable) + sendMessage(ENABLE_CHANGED, Boolean.FALSE); + } + } + } + + void initImages(ImageComponent[] images) { + + if (images.length != maxLevels) + throw new IllegalArgumentException(J3dI18N.getString("Texture20")); + + for (int i = 0; i < images.length; i++) { + initImage(i, images[i]); + } + } + + final void setImages(ImageComponent[] images) { + + int i; + ImageComponentRetained[] imagesRet = + new ImageComponentRetained[images.length]; + for (i = 0; i < images.length; i++) { + imagesRet[i] = (ImageComponentRetained)images[i].retained; + } + checkSizes(imagesRet); + + initImages(images); + + ImageComponent [] imgs = new ImageComponent[images.length]; + for (i = 0; i < images.length; i++) { + imgs[i] = images[i]; + } + + Object arg[] = new Object[2]; + arg[0] = imgs; + arg[1] = new Integer(0); + + sendMessage(IMAGES_CHANGED, arg); + + // If the user has set enable to true, then if the image is null + // turn off texture enable + + if (userSpecifiedEnable) { + enable = userSpecifiedEnable; + for (i = baseLevel; i <= maximumLevel && enable; i++) { + if (images[i] != null) { + ImageComponentRetained img= + (ImageComponentRetained)images[i].retained; + if (img.isByReference()) { + if (img.bImage[0] == null) { + enable = false; + } + } + else { + if (img.imageYup == null) { + enable = false; + } + } + } + } + if (!enable) { + sendMessage(ENABLE_CHANGED, Boolean.FALSE); + } + } + } + + + + + /** + * Gets a specified mipmap level. + * @param level mipmap level to get: 0 is the base level + * @return the pixel array object containing the texture image + * @exception RestrictedAccessException if the method is called + * when this object is part of live or compiled scene graph. + */ + final ImageComponent getImage(int level) { + return (((images != null) && (images[0][level] != null)) ? + (ImageComponent)images[0][level].source : null); + } + + final ImageComponent[] getImages() { + if (images == null) + return null; + + ImageComponent [] rImages = new ImageComponent[images[0].length]; + for (int i = 0; i < images[0].length; i++) { + if (images[0][i] != null) + rImages[i] = (ImageComponent)images[0][i].source; + else + rImages[i] = null; + } + return rImages; + } + + /** + * Sets mipmap mode for texture mapping for this texture object. + * @param mipMapMode the new mipmap mode for this object. One of: + * BASE_LEVEL or MULTI_LEVEL_MIPMAP. + * @exception RestrictedAccessException if the method is called + */ + final void initMipMapMode(int mipmapMode) { + + if (this.mipmapMode == mipmapMode) + return; + + + int prevMaxLevels = maxLevels; // previous maxLevels + + this.mipmapMode = mipmapMode; + + if (mipmapMode != Texture.BASE_LEVEL) { + maxLevels = maxMipMapLevels; + } else { + baseLevel = 0; + maximumLevel = 0; + maxLevels = 1; + } + + + ImageComponentRetained[][] newImages = + new ImageComponentRetained[numFaces][maxLevels]; + + if (prevMaxLevels < maxLevels) { + for (int f = 0; f < numFaces; f++) { + for (int i = 0; i < prevMaxLevels; i++) { + newImages[f][i] = images[f][i]; + } + + for (int j = prevMaxLevels; j < maxLevels; j++) { + newImages[f][j] = null; + } + } + } else { + for (int f = 0; f < numFaces; f++) { + for (int i = 0; i < maxLevels; i++) + newImages[f][i] = images[f][i]; + } + } + images = newImages; + } + + /** + * Retrieves current mipmap mode. + * @return current mipmap mode of this texture object. + * @exception RestrictedAccessException if the method is called + */ + final int getMipMapMode() { + return this.mipmapMode; + } + + /** + * Enables or disables texture mapping for this + * appearance component object. + * @param state true or false to enable or disable texture mapping + */ + final void initEnable(boolean state) { + userSpecifiedEnable = state; + } + + /** + * Enables or disables texture mapping for this + * appearance component object and sends a + * message notifying the interested structures of the change. + * @param state true or false to enable or disable texture mapping + */ + final void setEnable(boolean state) { + + initEnable(state); + + if (state == enable) { + // if enable flag is same as user specified one + // this is only possible when enable is false + // because one of the images is null and user specifies false + return; + } + + enable = state; + + for (int j = 0; j < numFaces && enable; j++) { + for (int i = baseLevel; i <= maximumLevel && enable; i++) { + if (images[j][i].isByReference()) { + if (images[j][i].bImage[0] == null) { + enable = false; + } + } else { + if (images[j][i].imageYup == null) { + enable = false; + } + } + } + } + sendMessage(ENABLE_CHANGED, (enable ? Boolean.TRUE: Boolean.FALSE)); + } + + /** + * Retrieves the state of the texture enable flag. + * @return true if texture mapping is enabled, + * false if texture mapping is disabled + */ + final boolean getEnable() { + return userSpecifiedEnable; + } + + + final void initBaseLevel(int level) { + if ((level < 0) || (level > maximumLevel)) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture36")); + } + baseLevel = level; + } + + + final void setBaseLevel(int level) { + + if (level == baseLevel) + return; + + initBaseLevel(level); + sendMessage(BASE_LEVEL_CHANGED, new Integer(level)); + } + + final int getBaseLevel() { + return baseLevel; + } + + + final void initMaximumLevel(int level) { + if ((level < baseLevel) || (level >= maxMipMapLevels)) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture37")); + } + maximumLevel = level; + } + + final void setMaximumLevel(int level) { + + if (level == maximumLevel) + return; + + initMaximumLevel(level); + sendMessage(MAX_LEVEL_CHANGED, new Integer(level)); + } + + final int getMaximumLevel() { + return maximumLevel; + } + + final void initMinimumLOD(float lod) { + if (lod > maximumLod) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture42")); + } + minimumLod = lod; + } + + final void setMinimumLOD(float lod) { + initMinimumLOD(lod); + sendMessage(MIN_LOD_CHANGED, new Float(lod)); + } + + final float getMinimumLOD() { + return minimumLod; + } + + + final void initMaximumLOD(float lod) { + if (lod < minimumLod) { + throw new IllegalArgumentException( + J3dI18N.getString("Texture42")); + } + maximumLod = lod; + } + + final void setMaximumLOD(float lod) { + initMaximumLOD(lod); + sendMessage(MAX_LOD_CHANGED, new Float(lod)); + } + + final float getMaximumLOD() { + return maximumLod; + } + + + final void initLodOffset(float s, float t, float r) { + if (lodOffset == null) { + lodOffset = new Point3f(s, t, r); + } else { + lodOffset.set(s, t, r); + } + } + + final void setLodOffset(float s, float t, float r) { + initLodOffset(s, t, r); + sendMessage(LOD_OFFSET_CHANGED, new Point3f(s, t, r)); + } + + final void getLodOffset(Tuple3f offset) { + if (lodOffset == null) { + offset.set(0.0f, 0.0f, 0.0f); + } else { + offset.set(lodOffset); + } + } + + + /** + * Sets the texture boundary color for this texture object. The + * texture boundary color is used when boundaryModeS or boundaryModeT + * is set to CLAMP. + * @param boundaryColor the new texture boundary color. + */ + final void initBoundaryColor(Color4f boundaryColor) { + this.boundaryColor.set(boundaryColor); + } + + /** + * Sets the texture boundary color for this texture object. The + * texture boundary color is used when boundaryModeS or boundaryModeT + * is set to CLAMP. + * @param r the red component of the color. + * @param g the green component of the color. + * @param b the blue component of the color. + * @param a the alpha component of the color. + */ + final void initBoundaryColor(float r, float g, float b, float a) { + this.boundaryColor.set(r, g, b, a); + } + + /** + * Retrieves the texture boundary color for this texture object. + * @param boundaryColor the vector that will receive the + * current texture boundary color. + */ + final void getBoundaryColor(Color4f boundaryColor) { + boundaryColor.set(this.boundaryColor); + } + + + /** + * Set Anisotropic Filter + */ + final void initAnisotropicFilterMode(int mode) { + anisotropicFilterMode = mode; + } + + final int getAnisotropicFilterMode() { + return anisotropicFilterMode; + } + + final void initAnisotropicFilterDegree(float degree) { + anisotropicFilterDegree = degree; + } + + final float getAnisotropicFilterDegree() { + return anisotropicFilterDegree; + } + + /** + * Set Sharpen Texture function + */ + final void initSharpenTextureFunc(float[] lod, float[] pts) { + if (lod == null) { // pts will be null too. + sharpenTextureFuncPts = null; + numSharpenTextureFuncPts = 0; + } else { + numSharpenTextureFuncPts = lod.length; + if ((sharpenTextureFuncPts == null) || + (sharpenTextureFuncPts.length != lod.length * 2)) { + sharpenTextureFuncPts = new float[lod.length * 2]; + } + for (int i = 0, j = 0; i < lod.length; i++) { + sharpenTextureFuncPts[j++] = lod[i]; + sharpenTextureFuncPts[j++] = pts[i]; + } + } + } + + final void initSharpenTextureFunc(Point2f[] pts) { + if (pts == null) { + sharpenTextureFuncPts = null; + numSharpenTextureFuncPts = 0; + } else { + numSharpenTextureFuncPts = pts.length; + if ((sharpenTextureFuncPts == null) || + (sharpenTextureFuncPts.length != pts.length * 2)) { + sharpenTextureFuncPts = new float[pts.length * 2]; + } + for (int i = 0, j = 0; i < pts.length; i++) { + sharpenTextureFuncPts[j++] = pts[i].x; + sharpenTextureFuncPts[j++] = pts[i].y; + } + } + } + + final void initSharpenTextureFunc(float[] pts) { + if (pts == null) { + sharpenTextureFuncPts = null; + numSharpenTextureFuncPts = 0; + } else { + numSharpenTextureFuncPts = pts.length / 2; + if ((sharpenTextureFuncPts == null) || + (sharpenTextureFuncPts.length != pts.length)) { + sharpenTextureFuncPts = new float[pts.length]; + } + for (int i = 0; i < pts.length; i++) { + sharpenTextureFuncPts[i] = pts[i]; + } + } + } + + /** + * Get number of points in the sharpen texture LOD function + */ + final int getSharpenTextureFuncPointsCount() { + return numSharpenTextureFuncPts; + } + + + /** + * Copies the array of sharpen texture LOD function points into the + * specified arrays + */ + final void getSharpenTextureFunc(float[] lod, float[] pts) { + if (sharpenTextureFuncPts != null) { + for (int i = 0, j = 0; i < numSharpenTextureFuncPts; i++) { + lod[i] = sharpenTextureFuncPts[j++]; + pts[i] = sharpenTextureFuncPts[j++]; + } + } + } + + final void getSharpenTextureFunc(Point2f[] pts) { + if (sharpenTextureFuncPts != null) { + for (int i = 0, j = 0; i < numSharpenTextureFuncPts; i++) { + pts[i].x = sharpenTextureFuncPts[j++]; + pts[i].y = sharpenTextureFuncPts[j++]; } } + } + + + final void initFilter4Func(float[] weights) { + if (weights == null) { + filter4FuncPts = null; + } else { + if ((filter4FuncPts == null) || + (filter4FuncPts.length != weights.length)) { + filter4FuncPts = new float[weights.length]; + } + for (int i = 0; i < weights.length; i++) { + filter4FuncPts[i] = weights[i]; + } + } + } + + + final int getFilter4FuncPointsCount() { + if (filter4FuncPts == null) { + return 0; + } else { + return filter4FuncPts.length; + } + } + + final void getFilter4Func(float[] weights) { + if (filter4FuncPts != null) { + for (int i = 0; i < filter4FuncPts.length; i++) { + weights[i] = filter4FuncPts[i]; + } + } + } + + + /** + * internal method only -- returns internal function points + */ + final float[] getSharpenTextureFunc() { + return sharpenTextureFuncPts; + } + + final float[] getFilter4Func(){ + return filter4FuncPts; + } + + + + + void setLive(boolean backgroundGroup, int refCount) { + + // check the sizes of the images + if (images != null) { + for (int j = 0; j < numFaces; j++) { + checkSizes(images[j]); + } + } + + // This line should be assigned before calling doSetLive, so that + // the mirror object's enable is assigned correctly! + enable = userSpecifiedEnable; + + super.doSetLive(backgroundGroup, refCount); + + // TODO: for now, do setLive for all the defined images. + // But in theory, we only need to setLive those within the + // baseLevel and maximumLevel range. But then we'll need + // setLive and clearLive image when the range changes. + + if (images != null) { + + for (int j = 0; j < numFaces; j++) { + for (int i = 0; i < maxLevels; i++){ + if (images[j][i] == null) { + throw new IllegalArgumentException( + J3dI18N.getString("TextureRetained3") + i); + } + images[j][i].setLive(backgroundGroup, refCount); + } + } + } + + // Send a message to Rendering Attr stucture to update the resourceMask + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.TEXTURE_CHANGED; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(UPDATE_IMAGE); + createMessage.args[2] = null; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + // If the user has set enable to true, then if the image is null + // turn off texture enable + if (userSpecifiedEnable) { + if (images != null) { + for (int j = 0; j < numFaces && enable; j++) { + for (int i = baseLevel; i <= maximumLevel && enable; i++){ + if (images[j][i].isByReference()) { + if (images[j][i].bImage[0] == null) { + enable = false; + } + } else { + if (images[j][i].imageYup == null) { + enable = false; + } + } + } + } + } else { + enable = false; + } + if (!enable) + sendMessage(ENABLE_CHANGED, Boolean.FALSE); + } + + super.markAsLive(); + } + + void clearLive(int refCount) { + super.clearLive(refCount); + + if (images != null) { + for (int j = 0; j < numFaces; j++) { + for (int i = 0; i < maxLevels; i++) { + images[j][i].clearLive(refCount); + images[j][i].removeUser(mirror); + } + } + } + } + + // Simply pass along to the NodeComponents + /** + * This method updates the native context. The implementation for 2D + * texture mapping happens here. Texture3D implements its own version + * of this. + */ + native void bindTexture(long ctx, int objectId, boolean enable); + + native void updateTextureFilterModes(long ctx, + int minFilter, int magFilter); + + native void updateTextureLodRange(long ctx, + int baseLevel, int maximumLevel, + float minimumLod, float maximumLod); + + native void updateTextureLodOffset(long ctx, + float lodOffsetX, float lodOffsetY, + float lodOffsetZ); + + + native void updateTextureBoundary(long ctx, + int boundaryModeS, int boundaryModeT, + float boundaryRed, float boundaryGreen, + float boundaryBlue, float boundaryAlpha); + + native void updateTextureSharpenFunc(long ctx, + int numSharpenTextureFuncPts, + float[] sharpenTextureFuncPts); + + native void updateTextureFilter4Func(long ctx, + int numFilter4FuncPts, + float[] filter4FuncPts); + + native void updateTextureAnisotropicFilter(long ctx, float degree); + + native void updateTextureImage(long ctx, + int numLevels, int level, + int format, int storedFormat, + int width, int height, + int boundaryWidth, byte[] data); + + native void updateTextureSubImage(long ctx, int level, + int xoffset, int yoffset, int format, + int storedFormat, int imgXOffset, + int imgYOffset, int tileWidth, + int width, int height, byte[] data); + + + // get an ID for Texture 2D + int getTextureId() { + return (VirtualUniverse.mc.getTexture2DId()); + } + + + // free a Texture2D id + void freeTextureId(int id) { + synchronized (resourceLock) { + if (objectId == id) { + objectId = -1; + VirtualUniverse.mc.freeTexture2DId(id); + } + } + } + + + // bind a named texture to a texturing target + + void bindTexture(Canvas3D cv) { + synchronized(resourceLock) { + if (objectId == -1) { + objectId = getTextureId(); + } + cv.addTextureResource(objectId, this); + } + bindTexture(cv.ctx, objectId, enable); + } + + + /** + * load level 0 explicitly with null pointer to enable + * mipmapping when level 0 is not the base level + */ + void updateTextureDimensions(Canvas3D cv) { + updateTextureImage(cv.ctx, maxLevels, 0, + format, ImageComponentRetained.BYTE_RGBA, + width, height, boundaryWidth, null); + } + + + void updateTextureLOD(Canvas3D cv) { + + if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_LOD_RANGE) != 0 ) { + updateTextureLodRange(cv.ctx, baseLevel, maximumLevel, + minimumLod, maximumLod); + } + + if ((lodOffset != null) && + ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_LOD_OFFSET) != 0)) { + updateTextureLodOffset(cv.ctx, + lodOffset.x, lodOffset.y, lodOffset.z); + } + } + + + void updateTextureBoundary(Canvas3D cv) { + updateTextureBoundary(cv.ctx, boundaryModeS, boundaryModeT, + boundaryColor.x, boundaryColor.y, + boundaryColor.z, boundaryColor.w); + } + + + void updateTextureFields(Canvas3D cv) { + + int magnificationFilter = magFilter; + int minificationFilter = minFilter; + + // update sharpen texture function if applicable + + if ((magFilter >= Texture.LINEAR_SHARPEN) && + (magFilter <= Texture.LINEAR_SHARPEN_ALPHA)) { + + if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_SHARPEN) != 0 ) { + + // send down sharpen texture LOD function + // + updateTextureSharpenFunc(cv.ctx, + numSharpenTextureFuncPts, sharpenTextureFuncPts); + + } else { + + // sharpen texture is not supported by the underlying + // library, fallback to BASE_LEVEL_LINEAR + + magnificationFilter = Texture.BASE_LEVEL_LINEAR; + } + } else if ((magFilter >= Texture2D.LINEAR_DETAIL) && + (magFilter <= Texture2D.LINEAR_DETAIL_ALPHA)) { + if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_DETAIL) == 0) { + + // detail texture is not supported by the underlying + // library, fallback to BASE_LEVEL_LINEAR + + magnificationFilter = Texture.BASE_LEVEL_LINEAR; + } + } + + if (minFilter == Texture.FILTER4 || magFilter == Texture.FILTER4) { + + boolean noFilter4 = false; + + if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_FILTER4) != 0) { + + if (filter4FuncPts == null) { + + // filter4 function is not defined, + // fallback to BASE_LEVEL_LINEAR + + noFilter4 = true; + } else { + updateTextureFilter4Func(cv.ctx, filter4FuncPts.length, + filter4FuncPts); + } + } else { + + // filter4 is not supported by the underlying + // library, fallback to BASE_LEVEL_LINEAR + + noFilter4 = true; + } + + if (noFilter4) { + if (minFilter == Texture.FILTER4) { + minificationFilter = Texture.BASE_LEVEL_LINEAR; + } + if (magFilter == Texture.FILTER4) { + magnificationFilter = Texture.BASE_LEVEL_LINEAR; + } + } + } + + // update texture filtering modes + + updateTextureFilterModes(cv.ctx, minificationFilter, + magnificationFilter); + + if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_ANISOTROPIC_FILTER) + != 0) { + if (anisotropicFilterMode == Texture.ANISOTROPIC_NONE) { + updateTextureAnisotropicFilter(cv.ctx, 1.0f); + } else { + updateTextureAnisotropicFilter(cv.ctx, anisotropicFilterDegree); + } + } + + // update texture boundary modes, boundary color + + updateTextureBoundary(cv); + + } + + + + // wrapper of the native call + + void updateTextureImage(Canvas3D cv, int face, + int numLevels, int level, + int format, int storedFormat, + int width, int height, + int boundaryWidth, byte[] data) { + + updateTextureImage(cv.ctx, maxLevels, level, + format, storedFormat, + width, height, boundaryWidth, data); + } + + + + // wrapper of the native call + + void updateTextureSubImage(Canvas3D cv, int face, int level, + int xoffset, int yoffset, int format, + int storedFormat, int imgXOffset, + int imgYOffset, int tileWidth, + int width, int height, byte[] data) { + + updateTextureSubImage(cv.ctx, level, + xoffset, yoffset, format, + storedFormat, imgXOffset, + imgYOffset, tileWidth, + width, height, data); + } + + + + /** + * reloadTextureImage is used to load a particular level of image + * This method needs to take care of RenderedImage as well as + * BufferedImage + */ + void reloadTextureImage(Canvas3D cv, int face, int level, + ImageComponentRetained image, int numLevels) { + + //System.out.println("Texture.reloadTextureImage: face= " + face + " level= " + level); + + //System.out.println("...image = "+image+" image.storedFormat = "+image.storedYupFormat+" image.imageYup = "+image.imageYup+" texture - "+this); + + //System.out.println("....imageYupAllocated= " + image.imageYupAllocated); + + updateTextureImage(cv, face, numLevels, level, format, + image.storedYupFormat, + image.width, image.height, + boundaryWidth, image.imageYup); + + // Now take care of the RenderedImage case. Note, if image + // is a RenderedImage, then imageYup will be null + + if (image.imageYupClass == ImageComponentRetained.RENDERED_IMAGE) { + + // System.out.println("==========. subImage"); + // Download all the tiles for this texture + int xoffset = 0, yoffset = 0; + int tmpw = image.width; + int tmph = image.height; + int endXTile = image.minTileX * image.tilew + image.tileGridXOffset+image.tilew; + int endYTile = image.minTileY * image.tileh + image.tileGridYOffset+image.tileh; + int curw = (endXTile - image.minX); + int curh = (endYTile - image.minY); + + if (tmpw < curw) { + curw = tmpw; + } + + if (tmph < curh) { + curh = tmph; + } + + int startw = curw; + int imageXOffset = image.tilew - curw; + int imageYOffset = image.tileh - curh; + for (int m = image.minTileY; m < image.minTileY+image.numYTiles; m++) { + xoffset = 0; + tmpw = width; + curw = startw; + imageXOffset = image.tilew - curw; + for (int n = image.minTileX; + n < image.minTileX+image.numXTiles; n++) { + java.awt.image.Raster ras; + ras = image.bImage[0].getTile(n,m); + byte[] tmpImage = ((DataBufferByte)ras.getDataBuffer()).getData(); + updateTextureSubImage(cv, face, + level, xoffset, yoffset, format, + image.storedYupFormat, + imageXOffset, imageYOffset, + image.tilew, + curw, curh, + tmpImage); + xoffset += curw; + imageXOffset = 0; + tmpw -= curw; + if (tmpw < image.tilew) + curw = tmpw; + else + curw = image.tilew; + } + yoffset += curh; + imageYOffset = 0; + tmph -= curh; + if (tmph < image.tileh) + curh = tmph; + else + curh = image.tileh; + } + } + } + + + /** + * update a subregion of the texture image + * This method needs to take care of RenderedImage as well as + * BufferedImage + */ + void reloadTextureSubImage(Canvas3D cv, int face, int level, + ImageComponentUpdateInfo info, + ImageComponentRetained image) { + + int x = info.x, + y = info.y, + width = info.width, + height = info.height; + + //The x and y here specifies the subregion of the imageData of + //the associated RenderedImage. + + //System.out.println("\nupdateTextureSubImage: x= " + x + " y= " + y + + // " width= " + width + " height= " + height + + // " format= " + format); + + + if (image.imageYupClass == ImageComponentRetained.BUFFERED_IMAGE) { + + int xoffset = x - image.minX; + int yoffset = y - image.minY; + + byte[] imageData; + if (image.imageYupAllocated) { + imageData = image.imageYup; + yoffset = image.height - yoffset - height; + + } else { + imageData = ((DataBufferByte) + image.bImage[0].getData().getDataBuffer()).getData(); + + // based on the yUp flag in the associated ImageComponent, + // adjust the yoffset + + if (!image.yUp) { + yoffset = image.height - yoffset - height; + } + } + + updateTextureSubImage(cv, face, level, + xoffset, yoffset, + format, image.storedYupFormat, + xoffset, yoffset, + image.width, width, height, imageData); + } else { + + // System.out.println("RenderedImage subImage update"); + + // determine the first tile of the image + + float mt; + int xoff = image.tileGridXOffset; + int yoff = image.tileGridYOffset; + int minTileX, minTileY; + + int rx = x + image.minX; // x offset in RenderedImage + int ry = y + image.minY; // y offset in RenderedImage + + mt = (float)(rx - xoff) / (float)image.tilew; + if (mt < 0) { + minTileX = (int)(mt - 1); + } else { + minTileX = (int)mt; + } + + mt = (float)(ry - yoff) / (float)image.tileh; + if (mt < 0) { + minTileY = (int)(mt - 1); + } else { + minTileY = (int)mt; + } + + // determine the pixel offset of the upper-left corner of the + // first tile + int startXTile = minTileX * image.tilew + xoff; + int startYTile = minTileY * image.tilew + yoff; + + + // image dimension in the first tile + + int curw = (startXTile + image.tilew - rx); + int curh = (startYTile + image.tileh - ry); + + // check if the to-be-copied region is less than the tile image + // if so, update the to-be-copied dimension of this tile + + if (curw > width) { + curw = width; + } + + if (curh > height) { + curh = height; + } + + // save the to-be-copied width of the left most tile + + int startw = curw; + + + // temporary variable for dimension of the to-be-copied region + + int tmpw = width; + int tmph = height; + + + // offset of the first pixel of the tile to be copied; offset is + // relative to the upper left corner of the title + + int imgX = rx - startXTile; + int imgY = ry - startYTile; + + + // determine the number of tiles in each direction that the + // image spans + + int numXTiles = (width + imgX) / image.tilew; + int numYTiles = (height + imgY) / image.tileh; + + if (((float)(width + imgX ) % (float)image.tilew) > 0) { + numXTiles += 1; + } + + if (((float)(height + imgY ) % (float)image.tileh) > 0) { + numYTiles += 1; + } + + java.awt.image.Raster ras; + byte[] imageData; + + int textureX = x; // x offset in the texture + int textureY = y; // y offset in the texture + + for (int yTile = minTileY; yTile < minTileY + numYTiles; + yTile++) { + + tmpw = width; + curw = startw; + imgX = rx - startXTile; + + for (int xTile = minTileX; xTile < minTileX + numXTiles; + xTile++) { + ras = image.bImage[0].getTile(xTile, yTile); + imageData = ((DataBufferByte)ras.getDataBuffer()).getData(); + + updateTextureSubImage(cv, face, level, + textureX, textureY, + format, image.storedYupFormat, + imgX, imgY, image.tilew, curw, curh, imageData); + + + // move to the next tile in x direction + + textureX += curw; + imgX = 0; + + // determine the width of copy region of the next tile + + tmpw -= curw; + if (tmpw < image.tilew) { + curw = tmpw; + } else { + curw = image.tilew; + } + } + + // move to the next set of tiles in y direction + textureY += curh; + imgY = 0; + + // determine the height of copy region for the next set + // of tiles + tmph -= curh; + if (tmph < image.tileh) { + curh = tmph; + } else { + curh = image.tileh; + } + } + } + } + + + // reload texture mipmap + + void reloadTexture(Canvas3D cv) { + + + int blevel, mlevel; + + //System.out.println("reloadTexture: baseLevel= " + baseLevel + + // " maximumLevel= " + maximumLevel); + + if ((cv.textureExtendedFeatures & Canvas3D.TEXTURE_LOD_RANGE) == 0 ) { + blevel = 0; + mlevel = maxLevels - 1; + } else { + blevel = baseLevel; + mlevel = maximumLevel; + } + + if (blevel != 0) { + // level 0 is not the base level, hence, need + // to load level 0 explicitly with a null pointer in order + // for mipmapping to be active. + + updateTextureDimensions(cv); + } + + for (int j = 0; j < numFaces; j++) { + for (int i = blevel; i <= mlevel; i++) { + + // it is possible to have null pointer if only a subset + // of mipmap levels is defined but the canvas does not + // support lod_range extension + + if (images[j][i] != null) { + reloadTextureImage(cv, j, i, images[j][i], maxLevels); + } + } + } + } + + + // update texture mipmap based on the imageUpdateInfo + + void updateTexture(Canvas3D cv, int resourceBit) { + + //System.out.println("updateTexture\n"); + + ImageComponentUpdateInfo info; + + for (int k = 0; k < numFaces; k++) { + for (int i = baseLevel; i <= maximumLevel; i++) { + if (imageUpdateInfo[k][i] != null) { + for (int j = 0; j < imageUpdateInfo[k][i].size(); j++) { + + info = (ImageComponentUpdateInfo) + imageUpdateInfo[k][i].get(j); + + + synchronized(resourceLock) { + + // if this info is updated, move on to the next one + + if ((info.updateMask & resourceBit) == 0) + continue; + + + // check if all canvases have processed this update + info.updateMask &= ~resourceBit; + + // all the current resources have updated this + // info, so this info can be removed from the + // update list + if ((info.updateMask & resourceCreationMask) + == 0) { + info.updateMask = 0; // mark this update as + // done + + // mark the prune flag so as to prune the + // update list next time when the update + // list is to be modified. + // Don't want to clean up the list at + // rendering time because (1) MT issue, + // other renderer could be processing + // the update list now; + // (2) takes up rendering time. + if (imageUpdatePruneMask == null) { + imageUpdatePruneMask = new int[numFaces]; + } + imageUpdatePruneMask[k] = 1 << i; + } + } + + if (info.entireImage == true) { + reloadTextureImage(cv, k, i, + images[k][i], maxLevels); + } else { + reloadTextureSubImage(cv, k, i, info, images[k][i]); + } + + } + } + } + } + } + + + /** + * reloadTextureSharedContext is called to reload texture + * on a shared context. It is invoked from the Renderer + * before traversing the RenderBin. The idea is to reload + * all necessary textures up front for all shared contexts + * in order to minimize the context switching overhead. + */ + void reloadTextureSharedContext(Canvas3D cv) { + + // if texture is not enabled, don't bother downloading the + // the texture state + + if (enable == false) { + return; + } + + bindTexture(cv); + + // reload all levels of texture image + + // update texture parameters such as boundary modes, filtering + + updateTextureFields(cv); + + + // update texture Lod parameters + + updateTextureLOD(cv); + + + // update all texture images + + reloadTexture(cv); + + synchronized(resourceLock) { + resourceCreationMask |= cv.screen.renderer.rendererBit; + resourceUpdatedMask |= cv.screen.renderer.rendererBit; + resourceLodUpdatedMask |= cv.screen.renderer.rendererBit; + resourceInReloadList &= ~cv.screen.renderer.rendererBit; + } + } + + + /** + * updateNative is called while traversing the RenderBin to + * update the texture state + */ + void updateNative(Canvas3D cv) { + boolean reloadTexture = false; // true - reload all levels of texture + boolean updateTexture = false; // true - update a portion of texture + boolean updateTextureLod = false; // true - update texture Lod info + + //System.out.println("Texture/updateNative: " + this + "object= " + objectId + " enable= " + enable); + + bindTexture(cv); + + // if texture is not enabled, don't bother downloading the + // the texture state + + if (enable == false) { + return; + } + + if (cv.useSharedCtx && cv.screen.renderer.sharedCtx != 0) { + + if ((resourceCreationMask & cv.screen.renderer.rendererBit) == 0) { + reloadTexture = true; + } else { + if (((resourceUpdatedMask & + cv.screen.renderer.rendererBit) == 0) && + (imageUpdateInfo != null)) { + updateTexture = true; + } + + if ((resourceLodUpdatedMask & + cv.screen.renderer.rendererBit) == 0) { + updateTextureLod = true; + } + } + if (reloadTexture || updateTexture || updateTextureLod) { + cv.makeCtxCurrent(cv.screen.renderer.sharedCtx); + bindTexture(cv); + } + } else { + if ((resourceCreationMask & cv.canvasBit) == 0) { + reloadTexture = true; + } else { + if (((resourceUpdatedMask & cv.canvasBit) == 0) && + (imageUpdateInfo != null)) { + updateTexture = true; + } + + if ((resourceLodUpdatedMask & cv.canvasBit) == 0) { + updateTextureLod = true; + } + } + } + + + if (VirtualUniverse.mc.isD3D()) { + if (texTimestamp != VirtualUniverse.mc.resendTexTimestamp) { + texTimestamp = VirtualUniverse.mc.resendTexTimestamp; + reloadTexture = true; + } + + if (!reloadTexture) { + // D3D didn't store texture properties during Texture binding + updateTextureFields(cv); + } + } + + +//System.out.println("......... reloadTexture= " + reloadTexture + +// " updateTexture= " + updateTexture + +// " updateTextureLod= " + updateTextureLod); + +//System.out.println("......... resourceCreationMask= " + resourceCreationMask + +// " resourceUpdatedMask= " + resourceUpdatedMask); + + if (reloadTexture) { + + // reload all levels of texture image + + // update texture parameters such as boundary modes, filtering + + updateTextureFields(cv); + + + // update texture Lod parameters + + updateTextureLOD(cv); + + + // update all texture images + + reloadTexture(cv); + + + if (cv.useSharedCtx) { + cv.makeCtxCurrent(cv.ctx); + synchronized(resourceLock) { + resourceCreationMask |= cv.screen.renderer.rendererBit; + resourceUpdatedMask |= cv.screen.renderer.rendererBit; + resourceLodUpdatedMask |= cv.screen.renderer.rendererBit; + } + } + else { + synchronized(resourceLock) { + resourceCreationMask |= cv.canvasBit; + resourceUpdatedMask |= cv.canvasBit; + resourceLodUpdatedMask |= cv.canvasBit; + } + } + } else if (updateTextureLod || updateTexture) { + + if (updateTextureLod) { + updateTextureLOD(cv); + } + + if (updateTexture) { + + // update texture image + + int resourceBit = 0; + + if (cv.useSharedCtx) { + resourceBit = cv.screen.renderer.rendererBit; + } else { + resourceBit = cv.canvasBit; + } + + // update texture based on the imageComponent update info + + updateTexture(cv, resourceBit); + } + + // set the appropriate bit in the resource update masks showing + // that the resource is up-to-date + + if (cv.useSharedCtx) { + cv.makeCtxCurrent(cv.ctx); + synchronized(resourceLock) { + resourceUpdatedMask |= cv.screen.renderer.rendererBit; + resourceLodUpdatedMask |= cv.screen.renderer.rendererBit; + } + } else { + synchronized(resourceLock) { + resourceUpdatedMask |= cv.canvasBit; + resourceLodUpdatedMask |= cv.canvasBit; + } + } + } + } + + synchronized void createMirrorObject() { + if (mirror == null) { + if (this instanceof Texture3DRetained) { + Texture3DRetained t3d = (Texture3DRetained)this; + Texture3D tex = new Texture3D(t3d.mipmapMode, + t3d.format, + t3d.width, + t3d.height, + t3d.depth, + t3d.boundaryWidth); + mirror = (Texture3DRetained)tex.retained;; + + } else if (this instanceof TextureCubeMapRetained) { + TextureCubeMap tex = new TextureCubeMap(mipmapMode, + format, width, + boundaryWidth); + mirror = (TextureCubeMapRetained)tex.retained; + + } else { + Texture2D tex = new Texture2D(mipmapMode, + format, + width, + height, + boundaryWidth); + mirror = (Texture2DRetained)tex.retained;; + } + + ((TextureRetained)mirror).objectId = -1; + } + initMirrorObject(); + } + + + /** + * Initializes a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + mirror.source = source; + if (this instanceof Texture3DRetained) { + Texture3DRetained t3d = (Texture3DRetained)this; + + ((Texture3DRetained)mirror).boundaryModeR = t3d.boundaryModeR; + ((Texture3DRetained)mirror).depth = t3d.depth; + } + TextureRetained mirrorTexture = (TextureRetained)mirror; + + mirrorTexture.boundaryModeS = boundaryModeS; + mirrorTexture.boundaryModeT = boundaryModeT; + mirrorTexture.minFilter = minFilter; + mirrorTexture.magFilter = magFilter; + mirrorTexture.boundaryColor.set(boundaryColor); + mirrorTexture.enable = enable; + mirrorTexture.userSpecifiedEnable = enable; + mirrorTexture.imagesLoaded = imagesLoaded; + mirrorTexture.enable = enable; + mirrorTexture.numFaces = numFaces; + mirrorTexture.resourceCreationMask = 0x0; + mirrorTexture.resourceUpdatedMask = 0x0; + mirrorTexture.resourceLodUpdatedMask = 0x0; + mirrorTexture.resourceInReloadList = 0x0; + + // LOD information + mirrorTexture.baseLevel = baseLevel; + mirrorTexture.maximumLevel = maximumLevel; + mirrorTexture.minimumLod = minimumLod; + mirrorTexture.maximumLod = maximumLod; + mirrorTexture.lodOffset = lodOffset; + + // sharpen texture LOD function + + mirrorTexture.numSharpenTextureFuncPts = numSharpenTextureFuncPts; + if (sharpenTextureFuncPts == null) { + mirrorTexture.sharpenTextureFuncPts = null; + } else { + if ((mirrorTexture.sharpenTextureFuncPts == null) || + (mirrorTexture.sharpenTextureFuncPts.length != + sharpenTextureFuncPts.length)) { + mirrorTexture.sharpenTextureFuncPts = + new float[sharpenTextureFuncPts.length]; + } + for (int i = 0; i < sharpenTextureFuncPts.length; i++) { + mirrorTexture.sharpenTextureFuncPts[i] = + sharpenTextureFuncPts[i]; + } + } + + // filter4 function + if (filter4FuncPts == null) { + mirrorTexture.filter4FuncPts = null; + } else { + if ((mirrorTexture.filter4FuncPts == null) || + (mirrorTexture.filter4FuncPts.length != + filter4FuncPts.length)) { + mirrorTexture.filter4FuncPts = + new float[filter4FuncPts.length]; + } + for (int i = 0; i < filter4FuncPts.length; i++) { + mirrorTexture.filter4FuncPts[i] = + filter4FuncPts[i]; + } + } + + // Anisotropic Filter + mirrorTexture.anisotropicFilterMode = anisotropicFilterMode; + mirrorTexture.anisotropicFilterDegree = anisotropicFilterDegree; + + // implicit mipmap generation + if (mipmapMode == Texture.BASE_LEVEL && + (minFilter == Texture.NICEST || + minFilter == Texture.MULTI_LEVEL_POINT || + minFilter == Texture.MULTI_LEVEL_LINEAR)) { + mirrorTexture.maxLevels = maxMipMapLevels; + + if ((mirrorTexture.images == null) || + (mirrorTexture.images.length < numFaces) || + (mirrorTexture.images[0].length < mirrorTexture.maxLevels)) { + mirrorTexture.images = + new ImageComponentRetained[numFaces][mirrorTexture.maxLevels]; + } + + for (int j = 0; j < numFaces; j++) { + mirrorTexture.images[j][0] = images[j][0]; + + // add texture to the userList of the images + if (images[j][0] != null) { + images[j][0].addUser(mirrorTexture); + } + + for (int i = 1; i < mirrorTexture.maxLevels; i++) { + mirrorTexture.images[j][i] = createNextLevelImage( + (mirrorTexture.images[j][i-1])); + } + } + } + else { + mirrorTexture.maxLevels = maxLevels; + if (images != null) { + + for (int j = 0; j < numFaces; j++) { + for (int i = 0; i < maxLevels; i++) { + mirrorTexture.images[j][i] = images[j][i]; + + // add texture to the userList of the images + if (images[j][i] != null) { + images[j][i].addUser(mirrorTexture); + } + } + } + } + } + } + + + /** + * Go through the image update info list + * and remove those that are already done + * by all the resources + */ + void pruneImageUpdateInfo() { + ImageComponentUpdateInfo info; + + //System.out.println("Texture.pruneImageUpdateInfo"); + + for (int k = 0; k < numFaces; k++) { + for (int i = baseLevel; i <= maximumLevel; i++) { + if ((imageUpdatePruneMask[k] & (1<<i)) != 0) { + if (imageUpdateInfo[k][i] != null) { + for (int j = 0; j < imageUpdateInfo[k][i].size(); j++) { + info = (ImageComponentUpdateInfo) + imageUpdateInfo[k][i].get(j); + if (info.updateMask == 0) { + // this update info is done, remove it + // from the update list + VirtualUniverse.mc.addFreeImageUpdateInfo(info); + imageUpdateInfo[k][i].remove(j); + } + } + } + imageUpdatePruneMask[k] &= ~(1<<i); + } + } + } + } + + /** + * addImageUpdateInfo(int level) is to update a particular level. + * In this case, it supercedes all the subImage update for this level, + * and all those update info can be removed from the update list. + * + * Note: this method is called from mirror only + */ + void addImageUpdateInfo(int level, int face, ImageComponentUpdateInfo arg) { + + ImageComponentUpdateInfo info; + + if (imageUpdateInfo == null) { + imageUpdateInfo = new ArrayList[numFaces][maxLevels]; + } + + if (imageUpdateInfo[face][level] == null) { + imageUpdateInfo[face][level] = new ArrayList(); + } + + //info = mirrorTa.getFreeImageUpdateInfo(); + info = VirtualUniverse.mc.getFreeImageUpdateInfo(); + + + if (arg == null) { + // no subimage info, so the entire image is to be updated + info.entireImage = true; + + } else if ((arg.width >= width/2) && (arg.height >= height/2)) { + + // if the subimage dimension is close to the complete dimension, + // use the full update (it's more efficient) + info.entireImage = true; + } else { + info.entireImage = false; + } + + if (info.entireImage) { + // the entire image update supercedes all the subimage update; + // hence, remove all the existing updates from the list + VirtualUniverse.mc.addFreeImageUpdateInfo( + imageUpdateInfo[face][level]); + imageUpdateInfo[face][level].clear(); + + // reset the update prune mask for this level + if (imageUpdatePruneMask != null) { + imageUpdatePruneMask[face] &= ~(1 << level); + } + + } else { + // subimage update, needs to save the subimage info + info.x = arg.x; + info.y = arg.y; + info.z = arg.z; + info.width = arg.width; + info.height = arg.height; + } + + // save the mask which shows the canvases that have created resources + // for this image, aka, these are the resources that need to be + // updated. + info.updateMask = resourceCreationMask; + + // add the image update to the list + imageUpdateInfo[face][level].add(info); + + // check if the update list stills need to be pruned + if (imageUpdatePruneMask != null) { + pruneImageUpdateInfo(); + } + } + + void validate() { + enable = true; + for (int j = 0; j < numFaces && enable; j++) { + for (int i = baseLevel; i <= maximumLevel && enable; i++) { + if (images[j][i] == null) { + enable = false; + } + } + } + } + + /** + * Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + + TextureRetained mirrorTexture = (TextureRetained)mirror; + + if ((component & ENABLE_CHANGED) != 0) { + mirrorTexture.enable = ((Boolean)value).booleanValue(); + + } else if ((component & IMAGE_CHANGED) != 0) { + + Object [] arg = (Object []) value; + int level = ((Integer)arg[0]).intValue(); + ImageComponent image = (ImageComponent)arg[1]; + int face = ((Integer)arg[2]).intValue(); + + // first remove texture from the userList of the current + // referencing image and + + if (mirrorTexture.images[face][level] != null) { + mirrorTexture.images[face][level].removeUser(mirror); + } + + // assign the new image and add texture to the userList + if (image == null) { + mirrorTexture.images[face][level] = null; + + } else { + mirrorTexture.images[face][level] = + (ImageComponentRetained)image.retained; + mirrorTexture.images[face][level].addUser(mirror); + + } + + // NOTE: the old image has to be removed from the + // renderBins' NodeComponentList and new image has to be + // added to the lists. This will be taken care of + // in the RenderBin itself in response to the + // IMAGE_CHANGED message + + + // mark that texture images need to be updated + mirrorTexture.resourceUpdatedMask = 0; + + // add update info to the update list + mirrorTexture.addImageUpdateInfo(level, face, null); + + } else if ((component & IMAGES_CHANGED) != 0) { + + Object [] arg = (Object []) value; + ImageComponent [] images = (ImageComponent[])arg[0]; + int face = ((Integer)arg[1]).intValue(); + + for (int i = 0; i < images.length; i++) { + + // first remove texture from the userList of the current + // referencing image + if (mirrorTexture.images[face][i] != null) { + mirrorTexture.images[face][i].removeUser(mirror); + } + + // assign the new image and add texture to the userList + if (images[i] == null) { + mirrorTexture.images[face][i] = null; + } else { + mirrorTexture.images[face][i] = + (ImageComponentRetained)images[i].retained; + mirrorTexture.images[face][i].addUser(mirror); + } + } + mirrorTexture.updateResourceCreationMask(); + + // NOTE: the old images have to be removed from the + // renderBins' NodeComponentList and new image have to be + // added to the lists. This will be taken care of + // in the RenderBin itself in response to the + // IMAGES_CHANGED message + + } else if ((component & BASE_LEVEL_CHANGED) != 0) { + int level = ((Integer)value).intValue(); + + if (level < mirrorTexture.baseLevel) { + + // add texture to the userList of those new levels of + // enabling images + + for (int j = 0; j < numFaces; j++) { + for (int i = level; i < mirrorTexture.baseLevel; i++) { + + if (mirrorTexture.images[j][i] == null) { + mirrorTexture.enable = false; + } else { + mirrorTexture.addImageUpdateInfo(i, j, null); + } + } + } + + mirrorTexture.baseLevel = level; + + // mark that texture images need to be updated + mirrorTexture.resourceUpdatedMask = 0; + + + } else { + + mirrorTexture.baseLevel = level; + + if (userSpecifiedEnable && (mirrorTexture.enable == false)) { + + // if texture is to be enabled but is currently + // disabled, it's probably disabled because + // some of the images are missing. Now that + // the baseLevel is modified, validate the + // texture images again + + mirrorTexture.validate(); + } + } + + // mark that texture lod info needs to be updated + mirrorTexture.resourceLodUpdatedMask = 0; + + } else if ((component & MAX_LEVEL_CHANGED) != 0) { + int level = ((Integer)value).intValue(); + + if (level > mirrorTexture.maximumLevel) { + + // add texture to the userList of those new levels of + // enabling images + + for (int j = 0; j < numFaces; j++) { + for (int i = mirrorTexture.maximumLevel; i < level; i++) { + + if (mirrorTexture.images[j][i] == null) { + mirrorTexture.enable = false; + } else { + mirrorTexture.addImageUpdateInfo(i, j, null); + } + } + } + + mirrorTexture.maximumLevel = level; + + // mark that texture images need to be updated + mirrorTexture.resourceUpdatedMask = 0; + + } else { + + mirrorTexture.maximumLevel = level; + + if (userSpecifiedEnable && (mirrorTexture.enable == false)) { + + // if texture is to be enabled but is currently + // disabled, it's probably disabled because + // some of the images are missing. Now that + // the baseLevel is modified, validate the + // texture images again + + mirrorTexture.validate(); + } + } + + // mark that texture lod info needs to be updated + mirrorTexture.resourceLodUpdatedMask = 0; + + } else if ((component & MIN_LOD_CHANGED) != 0) { + mirrorTexture.minimumLod = ((Float)value).floatValue(); + + // mark that texture lod info needs to be updated + mirrorTexture.resourceLodUpdatedMask = 0; + + } else if ((component & MAX_LOD_CHANGED) != 0) { + mirrorTexture.maximumLod = ((Float)value).floatValue(); + + // mark that texture lod info needs to be updated + mirrorTexture.resourceLodUpdatedMask = 0; + + } else if ((component & LOD_OFFSET_CHANGED) != 0) { + if ((mirrorTexture.lodOffset) == null) { + mirrorTexture.lodOffset = + new Point3f((Point3f)value); + } else { + mirrorTexture.lodOffset.set((Point3f)value); + } + + // mark that texture lod info needs to be updated + mirrorTexture.resourceLodUpdatedMask = 0; + + } else if ((component & UPDATE_IMAGE) != 0) { + mirrorTexture.updateResourceCreationMask(); + } + + } + + + // notifies the Texture mirror object that the image data in a referenced + // ImageComponent object is changed. Need to update the texture image + // accordingly. + // Note: this is called from mirror object only + + void notifyImageComponentImageChanged(ImageComponentRetained image, + ImageComponentUpdateInfo value) { + + //System.out.println("Texture.notifyImageComponentImageChanged"); + + + // if this texture is to be reloaded, don't bother to keep + // the update info + + if (resourceCreationMask == 0) { + + if (imageUpdateInfo != null) { + + //remove all the existing updates from the list + + for (int face = 0; face < numFaces; face++) { + for (int level = 0; level < maxLevels; level++) { + if (imageUpdateInfo[face][level] != null) { + VirtualUniverse.mc.addFreeImageUpdateInfo( + imageUpdateInfo[face][level]); + imageUpdateInfo[face][level].clear(); + } + } + + // reset the update prune mask for this level + if (imageUpdatePruneMask != null) { + imageUpdatePruneMask[face] = 0; + } + } + } + + return; + } + + + // first find which texture image is being affected + + boolean done; + + for (int j = 0; j < numFaces; j++) { + + done = false; + for (int i = baseLevel; i <= maximumLevel && !done; i++) { + if (images[j][i] == image) { + + // reset the resourceUpdatedMask to tell the + // rendering method to update the resource + resourceUpdatedMask = 0; + + // add update info to the update list + addImageUpdateInfo(i, j, value); + + // set done to true for this face because no two levels + // can reference the same ImageComponent object + done = true; + } + } + } + } + + + // reset the resourceCreationMask + // Note: called from the mirror object only + + void updateResourceCreationMask() { + resourceCreationMask = 0x0; + } + + final ImageComponentRetained createNextLevelImage( + ImageComponentRetained oImage) { + + int xScale, yScale, nWidth, nHeight; + ImageComponentRetained nImage = null; + + if (oImage.width > 1) { + nWidth = oImage.width >> 1; + xScale = 2; + } else { + nWidth = 1; + xScale = 1; + } + if (oImage.height > 1) { + nHeight = oImage.height >> 1; + yScale = 2; + } else { + nHeight = 1; + yScale = 1; + } + + int bytesPerPixel = oImage.bytesPerYupPixelStored; + + if (oImage instanceof ImageComponent2DRetained) { + + nImage = new ImageComponent2DRetained(); + nImage.processParams(oImage.format, nWidth, nHeight, 1); + nImage.imageYup = new byte[nWidth * nHeight * bytesPerPixel]; + nImage.storedYupFormat = nImage.internalFormat; + nImage.bytesPerYupPixelStored = bytesPerPixel; + scaleImage(nWidth, nHeight, xScale, yScale, oImage.width, 0, 0, + bytesPerPixel, nImage.imageYup, oImage.imageYup); + + } else { //oImage instanceof ImageComponent3DRetained + + int depth = ((ImageComponent3DRetained)oImage).depth; + nImage = new ImageComponent3DRetained(); + nImage.processParams(oImage.format, nWidth, nHeight, depth); + nImage.imageYup = new byte[nWidth * nHeight * bytesPerPixel]; + nImage.storedYupFormat = nImage.internalFormat; + nImage.bytesPerYupPixelStored = bytesPerPixel; + + for (int i = 0; i < depth; i++) { + scaleImage(nWidth, nHeight, xScale, yScale, oImage.width, + i * nWidth * nHeight * bytesPerPixel, + i * oImage.width * oImage.height * bytesPerPixel, + bytesPerPixel, nImage.imageYup, oImage.imageYup); + } + } + return nImage; + } + + final void scaleImage(int nWidth, int nHeight, int xScale, int yScale, + int oWidth, int nStart, int oStart, int bytesPerPixel, + byte[] nData, byte[] oData) { + + int nOffset = 0; + int oOffset = 0; + int oLineIncr = bytesPerPixel * oWidth; + int oPixelIncr = bytesPerPixel << 1; + + if (yScale == 1) { + for (int x = 0; x < nWidth; x++) { + for (int k = 0; k < bytesPerPixel; k++) { + nData[nStart + nOffset + k] = (byte) + (((int)(oData[oStart + oOffset + k] & 0xff) + + (int)(oData[oStart + oOffset + k + + bytesPerPixel] & 0xff) + 1) >> 1); + } + nOffset += bytesPerPixel; + oOffset += oPixelIncr; + } + } else if (xScale == 1) { + for (int y = 0; y < nHeight; y++) { + for (int k = 0; k < bytesPerPixel; k++) { + nData[nStart + nOffset + k] = (byte) + (((int)(oData[oStart + oOffset + k] & 0xff) + + (int)(oData[oStart + oOffset + k + + oLineIncr] & 0xff) + 1) >> 1); + } + nOffset += bytesPerPixel; + oOffset += oLineIncr; + } + } else { + for (int y = 0; y < nHeight; y++) { + for (int x = 0; x < nWidth; x++) { + for (int k = 0; k < bytesPerPixel; k++) { + nData[nStart + nOffset + k] = (byte) + (((int)(oData[oStart + oOffset + k] & 0xff) + + (int)(oData[oStart + oOffset + k + + bytesPerPixel] & 0xff) + + (int)(oData[oStart + oOffset + k + + oLineIncr] & 0xff) + + (int)(oData[oStart + oOffset + k + oLineIncr + + + bytesPerPixel] & 0xff) + 2) >> 2); + } + nOffset += bytesPerPixel; + oOffset += oPixelIncr; + } + oOffset += oLineIncr; + } + } + } + + void incTextureBinRefCount(TextureBin tb) { + + ImageComponentRetained image; + + textureBinRefCount++; + + // check to see if there is any modifiable images, + // if yes, add those images to nodeComponentList in RenderBin + // so that RenderBin can acquire a lock before rendering + // to prevent updating of image data while rendering + + for (int j = 0; j < numFaces; j++) { + for (int i = 0; i < maxLevels; i++) { + image = images[j][i]; + + // it is possible that image.source == null because + // the mipmap could have been created by the library, and + // hence don't have source and therefore they are + // guaranteed not modifiable + + if (image != null && + (image.isByReference() || + (image.source != null && + image.source.getCapability( + ImageComponent.ALLOW_IMAGE_WRITE)))) { + tb.renderBin.addNodeComponent(image); + } + } + } + } + + void decTextureBinRefCount(TextureBin tb) { + + ImageComponentRetained image; + + textureBinRefCount--; + + // remove any modifiable images from RenderBin nodeComponentList + + for (int j = 0; j < numFaces; j++) { + for (int i = 0; i < maxLevels; i++) { + image = images[j][i]; + if (image != null && + (image.isByReference() || + (image.source != null && + image.source.getCapability( + ImageComponent.ALLOW_IMAGE_WRITE)))) { + tb.renderBin.removeNodeComponent(image); + } + } + } + } + + + final void sendMessage(int attrMask, Object attr) { + + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.TEXTURE_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1] = new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.TEXTURE_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + + } + + protected void finalize() { + + if (objectId > 0) { + // memory not yet free + // send a message to the request renderer + synchronized (VirtualUniverse.mc.contextCreationLock) { + boolean found = false; + + for (Enumeration e = Screen3D.deviceRendererMap.elements(); + e.hasMoreElements(); ) { + Renderer rdr = (Renderer) e.nextElement(); + J3dMessage renderMessage = VirtualUniverse.mc.getMessage(); + renderMessage.threads = J3dThread.RENDER_THREAD; + renderMessage.type = J3dMessage.RENDER_IMMEDIATE; + renderMessage.universe = null; + renderMessage.view = null; + renderMessage.args[0] = null; + renderMessage.args[1] = new Integer(objectId); + renderMessage.args[2] = "2D"; + rdr.rendererStructure.addMessage(renderMessage); + } + objectId = -1; + } + + VirtualUniverse.mc.setWorkForRequestRenderer(); + } + + } + + void handleFrequencyChange(int bit) { + switch (bit) { + case Texture.ALLOW_ENABLE_WRITE: + case Texture.ALLOW_IMAGE_WRITE: + case Texture.ALLOW_LOD_RANGE_WRITE: { + setFrequencyChangeMask(bit, bit); + } + default: + break; + } + } +} + diff --git a/src/classes/share/javax/media/j3d/TextureUnitState.java b/src/classes/share/javax/media/j3d/TextureUnitState.java new file mode 100644 index 0000000..d544f43 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TextureUnitState.java @@ -0,0 +1,320 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Hashtable; + +/** + * The TextureUnitState object defines all texture mapping state for a + * single texture unit. An appearance object contains an array of + * texture unit state objects to define the state for multiple texture + * mapping units. The texture unit state consists of the + * following: + * + * <p> + * <ul> + * <li>Texture - defines the texture image and filtering + * parameters used when texture mapping is enabled. These attributes + * are defined in a Texture object.</li><p> + * + * <li>Texture attributes - defines the attributes that apply to + * texture mapping, such as the texture mode, texture transform, + * blend color, and perspective correction mode. These attributes + * are defined in a TextureAttributes object.</li><p> + * + * <li>Texture coordinate generation - defines the attributes + * that apply to texture coordinate generation, such as whether + * texture coordinate generation is enabled, coordinate format + * (2D or 3D coordinates), coordinate generation mode (object + * linear, eye linear, or spherical reflection mapping), and the + * R, S, and T coordinate plane equations. These attributes + * are defined in a TexCoordGeneration object.</li><p> + * </ul> + * + * @see Appearance + * @see Texture + * @see TextureAttributes + * @see TexCoordGeneration + * + * @since Java 3D 1.2 + */ +public class TextureUnitState extends NodeComponent { + + /** + * Specifies that this TextureUnitState object allows reading its + * texture, texture attribute, or texture coordinate generation + * component information. + */ + public static final int ALLOW_STATE_READ = + CapabilityBits.TEXTURE_UNIT_STATE_ALLOW_STATE_READ; + + /** + * Specifies that this TextureUnitState object allows writing its + * texture, texture attribute, or texture coordinate generation + * component information. + */ + public static final int ALLOW_STATE_WRITE = + CapabilityBits.TEXTURE_UNIT_STATE_ALLOW_STATE_WRITE; + + + /** + * Constructs a TextureUnitState component object using defaults for all + * state variables. All component object references are initialized + * to null. + */ + public TextureUnitState() { + // Just use default values + } + + /** + * Constructs a TextureUnitState component object using the specified + * component objects. + * + * @param texture object that specifies the desired texture + * map and texture parameters + * @param textureAttributes object that specifies the desired + * texture attributes + * @param texCoordGeneration object that specifies the texture coordinate + * generation parameters + */ + public TextureUnitState(Texture texture, + TextureAttributes textureAttributes, + TexCoordGeneration texCoordGeneration) { + + ((TextureUnitStateRetained)this.retained).initTexture(texture); + ((TextureUnitStateRetained)this.retained).initTextureAttributes( + textureAttributes); + ((TextureUnitStateRetained)this.retained).initTexCoordGeneration( + texCoordGeneration); + } + + /** + * Creates the retained mode TextureUnitStateRetained object that this + * TextureUnitState component object will point to. + */ + void createRetained() { + this.retained = new TextureUnitStateRetained(); + this.retained.setSource(this); + } + + /** + * Sets the texture, texture attributes, and texture coordinate + * generation components in this TextureUnitState object to the + * specified component objects. + * + * @param texture object that specifies the desired texture + * map and texture parameters + * @param textureAttributes object that specifies the desired + * texture attributes + * @param texCoordGeneration object that specifies the texture coordinate + * generation parameters + */ + public void set(Texture texture, + TextureAttributes textureAttributes, + TexCoordGeneration texCoordGeneration) { + + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_STATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureUnitState0")); + + ((TextureUnitStateRetained)this.retained).setTextureUnitState( + texture, textureAttributes, texCoordGeneration); + } + + /** + * Sets the texture object to the specified object. + * Setting it to null disables texture mapping for the + * texture unit corresponding to this TextureUnitState object. + * @param texture object that specifies the desired texture + * map and texture parameters + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setTexture(Texture texture) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_STATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureUnitState0")); + + ((TextureUnitStateRetained)this.retained).setTexture(texture); + } + + /** + * Retrieves the current texture object. + * @return the texture object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Texture getTexture() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_STATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureUnitState1")); + + return ((TextureUnitStateRetained)this.retained).getTexture(); + } + + /** + * Sets the textureAttributes object to the specified object. + * Setting it to null will result in default attribute usage for the. + * texture unit corresponding to this TextureUnitState object. + * @param textureAttributes object that specifies the desired + * texture attributes + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setTextureAttributes(TextureAttributes textureAttributes) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_STATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureUnitState2")); + + ((TextureUnitStateRetained)this.retained).setTextureAttributes(textureAttributes); + } + + /** + * Retrieves the current textureAttributes object. + * @return the textureAttributes object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public TextureAttributes getTextureAttributes() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_STATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureUnitState3")); + + return ((TextureUnitStateRetained)this.retained).getTextureAttributes(); + } + + /** + * Sets the texCoordGeneration object to the specified object. + * Setting it to null disables texture coordinate generation for the + * texture unit corresponding to this TextureUnitState object. + * @param texCoordGeneration object that specifies the texture coordinate + * generation parameters + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setTexCoordGeneration(TexCoordGeneration texCoordGeneration) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_STATE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureUnitState4")); + + ((TextureUnitStateRetained)this.retained).setTexCoordGeneration(texCoordGeneration); + } + + /** + * Retrieves the current texCoordGeneration object. + * @return the texCoordGeneration object + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public TexCoordGeneration getTexCoordGeneration() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_STATE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TextureUnitState5")); + + return ((TextureUnitStateRetained)this.retained).getTexCoordGeneration(); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + TextureUnitState ts = new TextureUnitState(); + ts.duplicateNodeComponent(this); + return ts; + } + + /** + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneNode method. + * + * @deprecated replaced with duplicateNodeComponent( + * NodeComponent originalNodeComponent, boolean forceDuplicate) + */ + public void duplicateNodeComponent(NodeComponent originalNodeComponent) { + checkDuplicateNodeComponent(originalNodeComponent); + } + + /** + * Copies all TextureUnitState information from + * <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + Hashtable hashtable = originalNodeComponent.nodeHashtable; + + TextureUnitStateRetained app = (TextureUnitStateRetained) originalNodeComponent.retained; + + TextureUnitStateRetained rt = (TextureUnitStateRetained) retained; + + rt.setTexture((Texture) getNodeComponent(app.getTexture(), + forceDuplicate, + hashtable)); + + rt.setTextureAttributes((TextureAttributes) getNodeComponent( + app.getTextureAttributes(), + forceDuplicate, + hashtable)); + + rt.setTexCoordGeneration((TexCoordGeneration) getNodeComponent( + app.getTexCoordGeneration(), + forceDuplicate, + hashtable)); + } + + /** + * This function is called from getNodeComponent() to see if any of + * the sub-NodeComponents duplicateOnCloneTree flag is true. + * If it is the case, current NodeComponent needs to + * duplicate also even though current duplicateOnCloneTree flag is false. + * This should be overwrite by NodeComponent which contains sub-NodeComponent. + */ + boolean duplicateChild() { + if (getDuplicateOnCloneTree()) + return true; + + TextureUnitStateRetained rt = (TextureUnitStateRetained) retained; + + NodeComponent nc = rt.getTexture(); + if ((nc != null) && nc.duplicateChild()) + return true; + + nc = rt.getTextureAttributes(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + nc = rt.getTexCoordGeneration(); + if ((nc != null) && nc.getDuplicateOnCloneTree()) + return true; + + return false; + } +} diff --git a/src/classes/share/javax/media/j3d/TextureUnitStateRetained.java b/src/classes/share/javax/media/j3d/TextureUnitStateRetained.java new file mode 100644 index 0000000..cecb193 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TextureUnitStateRetained.java @@ -0,0 +1,615 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.Color4f; +import java.util.ArrayList; + +class TextureUnitStateRetained extends NodeComponentRetained { + + static final int TEXTURE_CHANGED = 0x0001; + static final int TEXTURE_ATTRS_CHANGED = 0x0002; + static final int TEXCOORD_GEN_CHANGED = 0x0004; + static final int ALL_STATE_CHANGED = 0x0008; + + TextureRetained texture = null; + TextureAttributesRetained texAttrs = null; + TexCoordGenerationRetained texGen = null; + + /** + * An abstract method to validate the texture unit state component + */ + final void setTextureUnitStateComponent(NodeComponent comp, + NodeComponentRetained thisComp, + int messageOp) { + if (source.isLive()) { + + if ((comp == null && thisComp == null) || + (comp != null && comp.retained == thisComp)) + return; + + if (thisComp != null) { + thisComp.clearLive(refCount); + thisComp.removeMirrorUsers(this); + } + + if (comp != null) { + ((NodeComponentRetained)comp.retained).setLive(inBackgroundGroup, refCount); + // If texture unit is live, then copy all the users of this + // texture unit state as users of this texture component + ((NodeComponentRetained)comp.retained).copyMirrorUsers(this); + } + + if (messageOp != -1) { + sendMessage(messageOp, + (comp == null ? null : + ((NodeComponentRetained)comp.retained).mirror)); + } + + } + } + + final void initTextureUnitState(Texture texture, + TextureAttributes texAttrs, + TexCoordGeneration texGen) { + + initTexture(texture); + initTextureAttributes(texAttrs); + initTexCoordGeneration(texGen); + } + + final void setTextureUnitState(Texture texture, + TextureAttributes texAttrs, + TexCoordGeneration texGen) { + + setTextureUnitStateComponent(texture, this.texture, -1); + setTextureUnitStateComponent(texAttrs, this.texAttrs, -1); + setTextureUnitStateComponent(texGen, this.texGen, -1); + + + // send all changes to the target threads in one + // message to avoid modifying the renderBin repeatedly + + Object args[] = new Object[3]; + args[0] = (texture == null ? null : + ((TextureRetained)texture.retained).mirror); + args[1] = (texAttrs == null ? null : + ((TextureAttributesRetained)texAttrs.retained).mirror); + args[2] = (texGen == null ? null : + ((TexCoordGenerationRetained)texGen.retained).mirror); + + sendMessage(ALL_STATE_CHANGED, args); + + initTextureUnitState(texture, texAttrs, texGen); + } + + final void initTexture(Texture texture) { + if (texture == null) + this.texture = null; + else + this.texture = (TextureRetained)texture.retained; + } + + final void setTexture(Texture texture) { + setTextureUnitStateComponent(texture, this.texture, TEXTURE_CHANGED); + initTexture(texture); + } + + final void initTextureAttributes(TextureAttributes texAttrs) { + if (texAttrs == null) + this.texAttrs = null; + else + this.texAttrs = (TextureAttributesRetained)texAttrs.retained; + } + + final void setTextureAttributes(TextureAttributes texAttrs) { + setTextureUnitStateComponent(texAttrs, this.texAttrs, + TEXTURE_ATTRS_CHANGED); + initTextureAttributes(texAttrs); + } + + final void initTexCoordGeneration(TexCoordGeneration texGen) { + if (texGen == null) + this.texGen = null; + else + this.texGen = (TexCoordGenerationRetained)texGen.retained; + } + + final void setTexCoordGeneration(TexCoordGeneration texGen) { + setTextureUnitStateComponent(texGen, this.texGen, TEXCOORD_GEN_CHANGED); + initTexCoordGeneration(texGen); + } + + Texture getTexture() { + return (texture == null ? null : (Texture)texture.source); + } + + TextureAttributes getTextureAttributes() { + return (texAttrs == null ? null : (TextureAttributes)texAttrs.source); + } + + TexCoordGeneration getTexCoordGeneration() { + return (texGen == null ? null : (TexCoordGeneration)texGen.source); + } + + native void updateTextureUnitState(long ctx, int unitIndex, boolean enableFlag); + + void updateNative(int unitIndex, Canvas3D cv, + boolean reload, boolean simulate) { + + //System.out.println("TextureUnitState/updateNative: unitIndex= " + unitIndex + " reload= " + reload + " simulate= " + simulate); + + // unitIndex can be -1 for the single texture case, so + // can't use unitIndex to index into the cv.texUnitState; + // in this case, use index 0 + + int index = unitIndex; + + if (index < 0) + index = 0; + + + boolean dirty = ((cv.canvasDirty & (Canvas3D.TEXTUREATTRIBUTES_DIRTY|Canvas3D.TEXTUREBIN_DIRTY)) != 0); + + if (this.texture == null) { + // if texture is null, then texture mapped is + // disabled for this texture unit; and no more + // state update is needed + + //System.out.println("texture is null"); + + if (cv.texUnitState[index].texture != null) { + cv.resetTexture(cv.ctx, unitIndex); + cv.texUnitState[index].texture = null; + } + cv.canvasDirty &= ~Canvas3D.TEXTUREATTRIBUTES_DIRTY; + return; + } else { + + updateTextureUnitState(cv.ctx, unitIndex, true); + } + + // reload is needed in a multi-texture case to bind the + // texture parameters to the texture unit state + + if (reload || dirty || cv.texUnitState[index].texture != this.texture) { + + // texture cannot be null at this point because it is + // already checked above + this.texture.updateNative(cv); + + cv.texUnitState[index].texture = this.texture; + + } + + if (this.texAttrs == null) { + if (reload || dirty || cv.texUnitState[index].texAttrs != null) { + cv.resetTextureAttributes(cv.ctx); + if (VirtualUniverse.mc.isD3D() && + (texGen != null) && + ((texGen.genMode == TexCoordGeneration.EYE_LINEAR) || + ((texGen.genMode == TexCoordGeneration.SPHERE_MAP)))) { + // We need to reload tex since eye linear + // and sphere map in D3D will change the + // texture transform matrix also. + dirty = true; + } + cv.setBlendFunc(cv.ctx, TransparencyAttributes.BLEND_ONE, + TransparencyAttributes.BLEND_ZERO); + cv.texUnitState[index].texAttrs = null; + } + } else { + + TextureAttributesRetained mTexAttrs; + if (this.texAttrs.mirror == null) { + mTexAttrs = this.texAttrs; + } else { + mTexAttrs = (TextureAttributesRetained) this.texAttrs.mirror; + } + + + if (mTexAttrs.mirrorCompDirty) { + // This happen when canvas reference is same as + // texUnitState.texAttrs and we update the later without + // notify cache. + cv.texUnitState[index].texAttrs = null; + mTexAttrs.mirrorCompDirty = false; + } + + if (reload || dirty || cv.texUnitState[index].texAttrs != mTexAttrs) { + this.texAttrs.updateNative(cv, simulate, texture.format); + if (VirtualUniverse.mc.isD3D() && + (texGen != null) && + ((texGen.genMode == TexCoordGeneration.EYE_LINEAR) || + ((texGen.genMode == TexCoordGeneration.SPHERE_MAP)))) { + dirty = true; + } + cv.texUnitState[index].texAttrs = mTexAttrs; + } + } + + if (this.texGen == null) { + if (reload || dirty || cv.texUnitState[index].texGen != null) { + cv.resetTexCoordGeneration(cv.ctx); + cv.texUnitState[index].texGen = null; + } + } else { + TexCoordGenerationRetained mTexGen; + + if (this.texGen.mirror == null) { + mTexGen = this.texGen; + } else { + mTexGen = (TexCoordGenerationRetained)this.texGen.mirror; + } + + if (mTexGen.mirrorCompDirty) { + // This happen when canvas reference is same as + // texUnitState.texGen and we update the later without + // notify cache. + cv.texUnitState[index].texGen = null; + mTexGen.mirrorCompDirty = false; + } + + if (reload || dirty || cv.texUnitState[index].texGen != mTexGen) { + this.texGen.updateNative(cv); + cv.texUnitState[index].texGen = mTexGen; + } + } + cv.canvasDirty &= ~Canvas3D.TEXTUREATTRIBUTES_DIRTY; + } + + + /** + * Creates and initializes a mirror object, point the mirror object + * to the retained object if the object is not editable + */ + synchronized void createMirrorObject() { + + if (mirror == null) { + TextureUnitStateRetained mirrorTus = + new TextureUnitStateRetained(); + mirror = mirrorTus; + } + mirror.source = source; + initMirrorObject(); + + } + + synchronized void initMirrorObject() { + + TextureUnitStateRetained mirrorTus = + (TextureUnitStateRetained)mirror; + + if (this.texture != null) + mirrorTus.texture = (TextureRetained)this.texture.mirror; + else + mirrorTus.texture = null; + + if (this.texAttrs != null) + mirrorTus.texAttrs = + (TextureAttributesRetained)this.texAttrs.mirror; + else + mirrorTus.texAttrs = null; + + if (this.texGen != null) + mirrorTus.texGen = (TexCoordGenerationRetained)this.texGen.mirror; + else + mirrorTus.texGen = null; + } + + + /** Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + + TextureUnitStateRetained mirrorTus = (TextureUnitStateRetained)mirror; + + if ((component & TEXTURE_CHANGED) != 0) { + mirrorTus.texture = (TextureRetained)value; + } + else if ((component & TEXTURE_ATTRS_CHANGED) != 0) { + mirrorTus.texAttrs = (TextureAttributesRetained)value; + } + else if ((component & TEXCOORD_GEN_CHANGED) != 0) { + mirrorTus.texGen = (TexCoordGenerationRetained)value; + } + else if ((component & ALL_STATE_CHANGED) != 0) { + Object [] args = (Object []) value; + mirrorTus.texture = (TextureRetained)args[0]; + mirrorTus.texAttrs = (TextureAttributesRetained)args[1]; + mirrorTus.texGen = (TexCoordGenerationRetained)args[2]; + } + } + + + boolean equivalent(TextureUnitStateRetained tr) { + + if (tr == null) { + return (false); + + } else if ((this.changedFrequent != 0) || (tr.changedFrequent != 0)) { + return (this.mirror == tr); + + } else { + + if (this.texture != tr.texture) { + return false; + } + + if (this.texAttrs != null && + !this.texAttrs.equivalent(tr.texAttrs)) { + return false; + } + + if (this.texGen != null && + !this.texGen.equivalent(tr.texGen)) { + return false; + } + } + + return true; + } + + protected Object clone() { + TextureUnitStateRetained tr = (TextureUnitStateRetained)super.clone(); + + // the cloned object is used for RenderBin only. + // In most cases, it will duplicate all attributes in the RenderBin + // so that updating a mirror object in one level will not affect the + // entire structure of the RenderBin, but will affect only those bins + // that got affected by the modified mirror object + if (this.texAttrs != null) + tr.texAttrs = (TextureAttributesRetained)this.texAttrs.clone(); + + if (this.texGen != null) + tr.texGen = (TexCoordGenerationRetained)this.texGen.clone(); + + return tr; + } + + + /** + * set the texture unit state according to the specified texture + * unit state + */ + protected void set(TextureUnitStateRetained tr) { + super.set(tr); + this.texture = tr.texture; + + if (tr.texAttrs == null) { + this.texAttrs = null; + } else { + if (this.texAttrs == null) { + this.texAttrs = (TextureAttributesRetained)tr.texAttrs.clone(); + } else { + this.texAttrs.set(tr.texAttrs); + } + } + + if (tr.texGen == null) { + this.texGen = null; + } else { + if (this.texGen == null) { + this.texGen = (TexCoordGenerationRetained)tr.texGen.clone(); + } else { + this.texGen.set(tr.texGen); + } + } + } + + protected void set(TextureRetained texture, + TextureAttributesRetained texAttrs, + TexCoordGenerationRetained texGen) { + this.texture = texture; + this.texAttrs = texAttrs; + this.texGen = texGen; + } + + synchronized void addAMirrorUser(Shape3DRetained shape) { + + super.addAMirrorUser(shape); + + if (texture != null) + texture.addAMirrorUser(shape); + if (texAttrs != null) + texAttrs.addAMirrorUser(shape); + if (texGen != null) + texGen.addAMirrorUser(shape); + } + + synchronized void removeAMirrorUser(Shape3DRetained shape) { + super.removeAMirrorUser(shape); + + if (texture != null) + texture.removeAMirrorUser(shape); + if (texAttrs != null) + texAttrs.removeAMirrorUser(shape); + if (texGen != null) + texGen.removeAMirrorUser(shape); + } + + synchronized void removeMirrorUsers(NodeComponentRetained node) { + super.removeMirrorUsers(node); + + if (texture != null) + texture.removeMirrorUsers(node); + if (texAttrs != null) + texAttrs.removeMirrorUsers(node); + if (texGen != null) + texGen.removeMirrorUsers(node); + } + + synchronized void copyMirrorUsers(NodeComponentRetained node) { + super.copyMirrorUsers(node); + + if (texture != null) + texture.copyMirrorUsers(node); + if (texAttrs != null) + texAttrs.copyMirrorUsers(node); + if (texGen != null) + texGen.copyMirrorUsers(node); + } + + + void setLive(boolean backgroundGroup, int refCount) { + if (texture != null) + texture.setLive(backgroundGroup, refCount); + + if (texAttrs != null) + texAttrs.setLive(backgroundGroup, refCount); + + if (texGen != null) + texGen.setLive(backgroundGroup, refCount); + + // Increment the reference count and initialize the textureUnitState + // mirror object + super.doSetLive(backgroundGroup, refCount); + super.markAsLive(); + + } + + + void clearLive(int refCount) { + super.clearLive(refCount); + + if (texture != null) + texture.clearLive(refCount); + if (texAttrs != null) + texAttrs.clearLive(refCount); + if (texGen != null) + texGen.clearLive(refCount); + } + + boolean isStatic() { + + return (source.capabilityBitsEmpty() && + ((texture == null) || (texture.isStatic())) && + ((texAttrs == null) || (texAttrs.isStatic())) && + ((texGen == null) || (texGen.isStatic()))); + } + + /* + // Simply pass along to the NodeComponent + + void compile (CompileState compState) { + setCompiled(); + + if (texture != null) + texture.compile(compState); + if (texAttrs != null) + texAttrs.compile(compState); + if (texGen != null) + texGen.compile(compState); + } + */ + + boolean equals(TextureUnitStateRetained ts) { + return ((ts == this) || + (ts != null) && + ((texture == ts.texture) || + ((texture != null) && (texture.equals(ts.texture)))) && + ((texAttrs == ts.texAttrs) || + ((texAttrs != null) && (texAttrs.equals(ts.texAttrs)))) && + ((texGen == ts.texGen) || + ((texGen != null) && (texGen.equals(ts.texGen))))); + } + + + void setInImmCtx(boolean flag) { + if (texture != null) + texture.setInImmCtx(flag); + if (texAttrs != null) + texAttrs.setInImmCtx(flag); + if (texGen != null) + texGen.setInImmCtx(flag); + } + + boolean getInImmCtx() { + return (inImmCtx || + ((texture != null) && (texture.getInImmCtx())) || + ((texAttrs != null) && (texAttrs.getInImmCtx())) || + ((texGen != null) && (texGen.getInImmCtx()))); + } + + + boolean isLive() { + return (source.isLive() || + ((texture != null) && (texture.source.isLive())) || + ((texAttrs != null) && (texAttrs.source.isLive())) || + ((texGen != null) && (texGen.source.isLive()))); + } + + final void sendMessage(int attrMask, Object attr) { + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.TEXTURE_UNIT_STATE_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.TEXTURE_UNIT_STATE_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + + } + + boolean isTextureEnabled() { + // Check the internal enable , instead of userSpecifiedEnable + return (texture != null && texture.enable); + } + + // use by D3D to simulate OGL blend mode using multi-pass + final boolean needBlend2Pass(Canvas3D cv) { + return ((texAttrs != null) && + VirtualUniverse.mc.isD3D() && + ((cv.textureExtendedFeatures & + Canvas3D.TEXTURE_LERP) == 0) && + (texAttrs.textureMode == TextureAttributes.BLEND) && + (texture.format != Texture.ALPHA) && + (texture.format != Texture.INTENSITY)); + } + + void handleFrequencyChange(int bit) { + switch (bit) { + case TextureUnitState.ALLOW_STATE_WRITE: { + setFrequencyChangeMask(bit, bit); + } + default: + break; + } + } +} diff --git a/src/classes/share/javax/media/j3d/TimerThread.java b/src/classes/share/javax/media/j3d/TimerThread.java new file mode 100644 index 0000000..b89922b --- /dev/null +++ b/src/classes/share/javax/media/j3d/TimerThread.java @@ -0,0 +1,144 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The TimerThread is thread that handle WakeupOnElapsedTime call. + * There is only one thread for the whole system. + */ + +class TimerThread extends Thread { + + // action flag for runMonitor + private static final int WAIT = 0; + private static final int NOTIFY = 1; + private static final int STOP = 2; + + private WakeupOnElapsedTimeHeap heap = new WakeupOnElapsedTimeHeap(); + + // Wakeup InputDeviceScheduler for every sample time reach + private WakeupOnElapsedTime inputDeviceSchedCond = + new WakeupOnElapsedTime(InputDeviceScheduler.samplingTime); + + // Wakeup {all?} Sound Scheduler{s} for every sample time reach + // QUESTION: this sampling time is set to a very large value so Sound + // Schedulers are not pinged often unless explicitly requested + // TODO: need a way to remove/null this condition when all + // soundschedulers are halted + private WakeupOnElapsedTime soundSchedCond = + new WakeupOnElapsedTime(120000); // every 2 minutes + + private boolean running = true; + private volatile boolean waiting = false; + + TimerThread(ThreadGroup t) { + super(t, "J3D-TimerThread"); + } + + // call from UserThread + void add(WakeupOnElapsedTime wakeup) { + synchronized (heap) { + heap.insert(wakeup); + } + runMonitor(NOTIFY, 0); + } + + void addInputDeviceSchedCond() { + inputDeviceSchedCond.triggeredTime = + InputDeviceScheduler.samplingTime + + System.currentTimeMillis(); + add(inputDeviceSchedCond); + } + + void addSoundSchedCond(long wakeupTime) { + // TODO: there are potentially multiple sound schedulers. + // this code will force a wait up on ALL sound schedulers + // even though only one needs to process the sound that + // this wakeup condition is triggered by. + soundSchedCond.triggeredTime = wakeupTime; + add(soundSchedCond); + } + + // call from MasterThread + void finish() { + runMonitor(STOP, 0); + } + + void remove(WakeupOnElapsedTime w) { + synchronized (heap) { + heap.extract(w); + } + } + + public void run() { + long waitTime = -1; + long time; + WakeupOnElapsedTime cond; + + while (running) { + runMonitor(WAIT, waitTime); + time = System.currentTimeMillis(); + + while (true) { + cond = null; + waitTime = -1; + synchronized (heap) { + if (!heap.isEmpty()) { + waitTime = heap.getMin().triggeredTime - time; + if (waitTime <= 0) { + cond = heap.extractMin(); + } + } + } + if (cond == null) { + break; + } else if (cond == inputDeviceSchedCond) { + VirtualUniverse.mc.sendRunMessage( + J3dThread.INPUT_DEVICE_SCHEDULER); + } else if (cond == soundSchedCond) { + VirtualUniverse.mc.sendRunMessage( + J3dThread.SOUND_SCHEDULER); + } else { + cond.setTriggered(); + } + } + } + } + + + synchronized void runMonitor(int action, long waitTime) { + switch (action) { + case WAIT: + try { + if (running) { + waiting = true; + if (waitTime < 0) { + wait(); + } else { + wait(waitTime); + } + } + } catch (InterruptedException e) {} + waiting = false; + break; + case NOTIFY: + notify(); + break; + case STOP: + running = false; + notify(); + break; + } + } + +} diff --git a/src/classes/share/javax/media/j3d/Transform3D.java b/src/classes/share/javax/media/j3d/Transform3D.java new file mode 100644 index 0000000..4b1d69f --- /dev/null +++ b/src/classes/share/javax/media/j3d/Transform3D.java @@ -0,0 +1,5641 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.*; + +/** + * A generalized transform object represented internally as a 4x4 + * double-precision floating point matrix. The mathematical + * representation is + * row major, as in traditional matrix mathematics. + * A Transform3D is used to perform translations, rotations, and + * scaling and shear effects.<P> + * + * A transform has an associated type, and + * all type classification is left to the Transform3D object. + * A transform will typically have multiple types, unless it is a + * general, unclassifiable matrix, in which case it won't be assigned + * a type. <P> + * + * The Transform3D type is internally computed when the transform + * object is constructed and updated any time it is modified. A + * matrix will typically have multiple types. For example, the type + * associated with an identity matrix is the result of ORing all of + * the types, except for ZERO and NEGATIVE_DETERMINANT, together. + * There are public methods available to get the ORed type of the + * transformation, the sign of the determinant, and the least + * general matrix type. The matrix type flags are defined as + * follows:<P> + * <UL> + * <LI>ZERO - zero matrix. All of the elements in the matrix + * have the value 0.</LI><P> + * <LI>IDENTITY - identity matrix. A matrix with ones on its + * main diagonal and zeros every where else.</LI><P> + * <LI>SCALE - the matrix is a uniform scale matrix - there are + * no rotational or translation components.</LI><P> + * <LI>ORTHOGONAL - the four row vectors that make up an orthogonal + * matrix form a basis, meaning that they are mutually orthogonal. + * The scale is unity and there are no translation components.</LI><P> + * <LI>RIGID - the upper 3 X 3 of the matrix is orthogonal, and + * there is a translation component-the scale is unity.</LI><P> + * <LI>CONGRUENT - this is an angle- and length-preserving matrix, + * meaning that it can translate, rotate, and reflect about an axis, + * and scale by an amount that is uniform in all directions. These + * operations preserve the distance between any two points, and the + * angle between any two intersecting lines.</LI><P> + * <LI>AFFINE - an affine matrix can translate, rotate, reflect, + * scale anisotropically, and shear. Lines remain straight, and parallel + * lines remain parallel, but the angle between intersecting lines can + * change.</LI><P> + * </UL> + * A matrix is also classified by the sign of its determinant:<P> + * <UL> + * NEGATIVE_DETERMINANT - this matrix has a negative determinant. + * An orthogonal matrix with a positive determinant is a rotation + * matrix. An orthogonal matrix with a negative determinant is a + * reflection and rotation matrix.<P></UL> + * The Java 3D model for 4 X 4 transformations is:<P> + * <UL><pre> + * [ m00 m01 m02 m03 ] [ x ] [ x' ] + * [ m10 m11 m12 m13 ] . [ y ] = [ y' ] + * [ m20 m21 m22 m23 ] [ z ] [ z' ] + * [ m30 m31 m32 m33 ] [ w ] [ w' ] + * + * x' = m00 . x+m01 . y+m02 . z+m03 . w + * y' = m10 . x+m11 . y+m12 . z+m13 . w + * z' = m20 . x+m21 . y+m22 . z+m23 . w + * w' = m30 . x+m31 . y+m32 . z+m33 . w + * </pre></ul><P> + * Note: When transforming a Point3f or a Point3d, the input w is set to + * 1. When transforming a Vector3f or Vector3d, the input w is set to 0. + */ + +public class Transform3D { + + double[] mat = new double[16]; + //double[] rot = new double[9]; + //double[] scales = new double[3]; + // allocate the memory only when it is needed. Following three places will allocate the memory, + // void setScaleTranslation(), void computeScales() and void computeScaleRotation() + double[] rot = null; + double[] scales = null; + + // Unknown until lazy classification is done + private int type = 0; + + // Dirty bit for classification, this is used + // for classify() + private static final int AFFINE_BIT = 0x01; + private static final int ORTHO_BIT = 0x02; + private static final int CONGRUENT_BIT = 0x04; + private static final int RIGID_BIT = 0x08; + private static final int CLASSIFY_BIT = 0x10; + + // this is used for scales[], rot[] + private static final int SCALE_BIT = 0x20; + private static final int ROTATION_BIT = 0x40; + // set when SVD renormalization is necessary + private static final int SVD_BIT = 0x80; + + private static final int CLASSIFY_ALL_DIRTY = AFFINE_BIT | + ORTHO_BIT | + CONGRUENT_BIT | + RIGID_BIT | + CLASSIFY_BIT; + private static final int ROTSCALESVD_DIRTY = SCALE_BIT | + ROTATION_BIT | + SVD_BIT; + private int dirtyBits; + + boolean autoNormalize = false; // Don't auto normalize by default + /* + // reused temporaries for compute_svd + private boolean svdAllocd =false; + private double[] u1 = null; + private double[] v1 = null; + private double[] t1 = null; // used by both compute_svd and compute_qr + private double[] t2 = null; // used by both compute_svd and compute_qr + private double[] ts = null; + private double[] svdTmp = null; + private double[] svdRot = null; + private double[] single_values = null; + private double[] e = null; + private double[] svdScales = null; + // from svrReorder + private int[] svdOut = null; + private double[] svdMag = null; + + // from compute_qr + private double[] cosl = null; + private double[] cosr = null; + private double[] sinl = null; + private double[] sinr = null; + private double[] qr_m = null; + */ + + private static final double EPS = 1.110223024E-16; + + static final double EPSILON = 1.0e-10; + static final double EPSILON_ABSOLUTE = 1.0e-5; + static final double EPSILON_RELATIVE = 1.0e-4; + /** + * A zero matrix. + */ + public static final int ZERO = 0x01; + + /** + * An identity matrix. + */ + public static final int IDENTITY = 0x02; + + + /** + * A Uniform scale matrix with no translation or other + * off-diagonal components. + */ + public static final int SCALE = 0x04; + + /** + * A translation-only matrix with ones on the diagonal. + * + */ + public static final int TRANSLATION = 0x08; + + /** + * The four row vectors that make up an orthogonal matrix form a basis, + * meaning that they are mutually orthogonal; an orthogonal matrix with + * positive determinant is a pure rotation matrix; a negative + * determinant indicates a rotation and a reflection. + */ + public static final int ORTHOGONAL = 0x10; + + /** + * This matrix is a rotation and a translation with unity scale; + * The upper 3x3 of the matrix is orthogonal, and there is a + * translation component. + */ + public static final int RIGID = 0x20; + + /** + * This is an angle and length preserving matrix, meaning that it + * can translate, rotate, and reflect + * about an axis, and scale by an amount that is uniform in all directions. + * These operations preserve the distance between any two points and the + * angle between any two intersecting lines. + */ + public static final int CONGRUENT = 0x40; + + /** + * An affine matrix can translate, rotate, reflect, scale anisotropically, + * and shear. Lines remain straight, and parallel lines remain parallel, + * but the angle between intersecting lines can change. In order for a + * transform to be classified as affine, the 4th row must be: [0, 0, 0, 1]. + */ + public static final int AFFINE = 0x80; + + /** + * This matrix has a negative determinant; an orthogonal matrix with + * a positive determinant is a rotation matrix; an orthogonal matrix + * with a negative determinant is a reflection and rotation matrix. + */ + public static final int NEGATIVE_DETERMINANT = 0x100; + + /** + * The upper 3x3 column vectors that make up an orthogonal + * matrix form a basis meaning that they are mutually orthogonal. + * It can have non-uniform or zero x/y/z scale as long as + * the dot product of any two column is zero. + * This one is used by Java3D internal only and should not + * expose to the user. + */ + private static final int ORTHO = 0x40000000; + + /** + * Constructs and initializes a transform from the 4 x 4 matrix. The + * type of the constructed transform will be classified automatically. + * @param m1 the 4 x 4 transformation matrix + */ + public Transform3D(Matrix4f m1) { + set(m1); + } + + /** + * Constructs and initializes a transform from the 4 x 4 matrix. The + * type of the constructed transform will be classified automatically. + * @param m1 the 4 x 4 transformation matrix + */ + public Transform3D(Matrix4d m1) { + set(m1); + } + + /** + * Constructs and initializes a transform from the Transform3D object. + * @param t1 the transformation object to be copied + */ + public Transform3D(Transform3D t1) { + set(t1); + } + + /** + * Constructs and initializes a transform to the identity matrix. + */ + public Transform3D() { + setIdentity(); // this will also classify the matrix + } + + /** + * Constructs and initializes a transform from the float array of + * length 16; the top row of the matrix is initialized to the first + * four elements of the array, and so on. The type of the transform + * object is classified internally. + * @param matrix a float array of 16 + */ + public Transform3D(float[] matrix) { + set(matrix); + } + + /** + * Constructs and initializes a transform from the double precision array + * of length 16; the top row of the matrix is initialized to the first + * four elements of the array, and so on. The type of the transform is + * classified internally. + * @param matrix a float array of 16 + */ + public Transform3D(double[] matrix) { + set(matrix); + } + + /** + * Constructs and initializes a transform from the quaternion, + * translation, and scale values. The scale is applied only to the + * rotational components of the matrix (upper 3 x 3) and not to the + * translational components of the matrix. + * @param q1 the quaternion value representing the rotational component + * @param t1 the translational component of the matrix + * @param s the scale value applied to the rotational components + */ + public Transform3D(Quat4d q1, Vector3d t1, double s) { + set(q1, t1, s); + } + + /** + * Constructs and initializes a transform from the quaternion, + * translation, and scale values. The scale is applied only to the + * rotational components of the matrix (upper 3 x 3) and not to the + * translational components of the matrix. + * @param q1 the quaternion value representing the rotational component + * @param t1 the translational component of the matrix + * @param s the scale value applied to the rotational components + */ + public Transform3D(Quat4f q1, Vector3d t1, double s) { + set(q1, t1, s); + } + + /** + * Constructs and initializes a transform from the quaternion, + * translation, and scale values. The scale is applied only to the + * rotational components of the matrix (upper 3 x 3) and not to the + * translational components of the matrix. + * @param q1 the quaternion value representing the rotational component + * @param t1 the translational component of the matrix + * @param s the scale value applied to the rotational components + */ + public Transform3D(Quat4f q1, Vector3f t1, float s) { + set(q1, t1, s); + } + + /** + * Constructs a transform and initializes it to the upper 4 x 4 + * of the GMatrix argument. If the parameter matrix is + * smaller than 4 x 4, the remaining elements in the transform matrix are + * assigned to zero. + * @param m1 the GMatrix + */ + public Transform3D(GMatrix m1) { + set(m1); + } + + /** + * Constructs and initializes a transform from the rotation matrix, + * translation, and scale values. The scale is applied only to the + * rotational component of the matrix (upper 3x3) and not to the + * translational component of the matrix. + * @param m1 the rotation matrix representing the rotational component + * @param t1 the translational component of the matrix + * @param s the scale value applied to the rotational components + */ + public Transform3D(Matrix3f m1, Vector3d t1, double s) { + set(m1, t1, s); + } + + /** + * Constructs and initializes a transform from the rotation matrix, + * translation, and scale values. The scale is applied only to the + * rotational components of the matrix (upper 3x3) and not to the + * translational components of the matrix. + * @param m1 the rotation matrix representing the rotational component + * @param t1 the translational component of the matrix + * @param s the scale value applied to the rotational components + */ + public Transform3D(Matrix3d m1, Vector3d t1, double s) { + set(m1, t1, s); + } + + + /** + * Constructs and initializes a transform from the rotation matrix, + * translation, and scale values. The scale is applied only to the + * rotational components of the matrix (upper 3x3) and not to the + * translational components of the matrix. + * @param m1 the rotation matrix representing the rotational component + * @param t1 the translational component of the matrix + * @param s the scale value applied to the rotational components + */ + public Transform3D(Matrix3f m1, Vector3f t1, float s) { + set(m1, t1, s); + } + + /** + * Returns the type of this matrix as an or'ed bitmask of + * of all of the type classifications to which it belongs. + * @return or'ed bitmask of all of the type classifications + * of this transform + */ + public final int getType() { + if ((dirtyBits & CLASSIFY_BIT) != 0) { + classify(); + } + // clear ORTHO bit which only use internally + return (type & ~ORTHO); + } + + // True if type is ORTHO + // Since ORTHO didn't take into account the last row. + final boolean isOrtho() { + if ((dirtyBits & ORTHO_BIT) != 0) { + if ((almostZero(mat[0]*mat[2] + mat[4]*mat[6] + + mat[8]*mat[10]) && + almostZero(mat[0]*mat[1] + mat[4]*mat[5] + + mat[8]*mat[9]) && + almostZero(mat[1]*mat[2] + mat[5]*mat[6] + + mat[9]*mat[10]))) { + type |= ORTHO; + dirtyBits &= ~ORTHO_BIT; + return true; + } else { + type &= ~ORTHO; + dirtyBits &= ~ORTHO_BIT; + return false; + } + } + return ((type & ORTHO) != 0); + } + + final boolean isCongruent() { + if ((dirtyBits & CONGRUENT_BIT) != 0) { + // This will also classify AFFINE + classifyRigid(); + } + return ((type & CONGRUENT) != 0); + } + + final boolean isAffine() { + if ((dirtyBits & AFFINE_BIT) != 0) { + classifyAffine(); + } + return ((type & AFFINE) != 0); + } + + final boolean isRigid() { + if ((dirtyBits & RIGID_BIT) != 0) { + + + // This will also classify AFFINE & CONGRUENT + if ((dirtyBits & CONGRUENT_BIT) != 0) { + classifyRigid(); + } else { + + if ((type & CONGRUENT) != 0) { + // Matrix is Congruent, need only + // to check scale + double s; + if ((dirtyBits & SCALE_BIT) != 0){ + s = mat[0]*mat[0] + mat[4]*mat[4] + + mat[8]*mat[8]; + // Note that + // scales[0] = sqrt(s); + // but since sqrt(1) = 1, + // we don't need to do s = sqrt(s) here. + } else { + if(scales == null) + scales = new double[3]; + s = scales[0]; + } + if (almostOne(s)) { + type |= RIGID; + } else { + type &= ~RIGID; + } + } else { + // Not even congruent, so isRigid must be false + type &= ~RIGID; + } + dirtyBits &= ~RIGID_BIT; + } + } + return ((type & RIGID) != 0); + } + + + /** + * Returns the least general type of this matrix; the order of + * generality from least to most is: ZERO, IDENTITY, + * SCALE/TRANSLATION, ORTHOGONAL, RIGID, CONGRUENT, AFFINE. + * If the matrix is ORTHOGONAL, calling the method + * getDeterminantSign() will yield more information. + * @return the least general matrix type + */ + public final int getBestType() { + getType(); // force classify if necessary + + if ((type & ZERO) != 0 ) return ZERO; + if ((type & IDENTITY) != 0 ) return IDENTITY; + if ((type & SCALE) != 0 ) return SCALE; + if ((type & TRANSLATION) != 0 ) return TRANSLATION; + if ((type & ORTHOGONAL) != 0 ) return ORTHOGONAL; + if ((type & RIGID) != 0 ) return RIGID; + if ((type & CONGRUENT) != 0 ) return CONGRUENT; + if ((type & AFFINE) != 0 ) return AFFINE; + if ((type & NEGATIVE_DETERMINANT) != 0 ) return NEGATIVE_DETERMINANT; + return 0; + } + + /* + private void print_type() { + if ((type & ZERO) > 0 ) System.out.print(" ZERO"); + if ((type & IDENTITY) > 0 ) System.out.print(" IDENTITY"); + if ((type & SCALE) > 0 ) System.out.print(" SCALE"); + if ((type & TRANSLATION) > 0 ) System.out.print(" TRANSLATION"); + if ((type & ORTHOGONAL) > 0 ) System.out.print(" ORTHOGONAL"); + if ((type & RIGID) > 0 ) System.out.print(" RIGID"); + if ((type & CONGRUENT) > 0 ) System.out.print(" CONGRUENT"); + if ((type & AFFINE) > 0 ) System.out.print(" AFFINE"); + if ((type & NEGATIVE_DETERMINANT) > 0 ) System.out.print(" NEGATIVE_DETERMINANT"); + } + */ + + /** + * Returns the sign of the determinant of this matrix; a return value + * of true indicates a positive determinant; a return value of false + * indicates a negative determinant. In general, an orthogonal matrix + * with a positive determinant is a pure rotation matrix; an orthogonal + * matrix with a negative determinant is a both a rotation and a + * reflection matrix. + * @return determinant sign : true means positive, false means negative + */ + public final boolean getDeterminantSign() { + return (determinant() >= 0); + } + + /** + * Sets a flag that enables or disables automatic SVD + * normalization. If this flag is enabled, an automatic SVD + * normalization of the rotational components (upper 3x3) of this + * matrix is done after every subsequent matrix operation that + * modifies this matrix. This is functionally equivalent to + * calling normalize() after every subsequent call, but may be + * less computationally expensive. + * The default value for this parameter is false. + * @param autoNormalize the boolean state of auto normalization + */ + public final void setAutoNormalize(boolean autoNormalize) { + this.autoNormalize = autoNormalize; + + if (autoNormalize) { + normalize(); + } + } + + /** + * Returns the state of auto-normalization. + * @return boolean state of auto-normalization + */ + public final boolean getAutoNormalize() { + return this.autoNormalize; + } + + private static final boolean almostZero(double a) { + return ((a < EPSILON_ABSOLUTE) && (a > -EPSILON_ABSOLUTE)); + } + + private static final boolean almostOne(double a) { + return ((a < 1+EPSILON_ABSOLUTE) && (a > 1-EPSILON_ABSOLUTE)); + } + + private static final boolean almostEqual(double a, double b) { + double diff = a-b; + + if (diff >= 0) { + if (diff < EPSILON) { + return true; + } + // a > b + if ((b > 0) || (a > -b)) { + return (diff < EPSILON_RELATIVE*a); + } else { + return (diff < -EPSILON_RELATIVE*b); + } + + } else { + if (diff > -EPSILON) { + return true; + } + // a < b + if ((b < 0) || (-a > b)) { + return (diff > EPSILON_RELATIVE*a); + } else { + return (diff > -EPSILON_RELATIVE*b); + } + } + } + + private final void classifyAffine() { + if (almostZero(mat[12]) && + almostZero(mat[13]) && + almostZero(mat[14]) && + almostOne(mat[15])) { + type |= AFFINE; + } else { + type &= ~AFFINE; + } + dirtyBits &= ~AFFINE_BIT; + } + + // same amount of work to classify rigid and congruent + private final void classifyRigid() { + + if ((dirtyBits & AFFINE_BIT) != 0) { + // should not touch ORTHO bit + type &= ORTHO; + classifyAffine(); + } else { + // keep the affine bit if there is one + // and clear the others (CONGRUENT/RIGID) bit + type &= (ORTHO | AFFINE); + } + + if ((type & AFFINE) != 0) { + // checking orthogonal condition + if (isOrtho()) { + if ((dirtyBits & SCALE_BIT) != 0) { + double s0 = mat[0]*mat[0] + mat[4]*mat[4] + + mat[8]*mat[8]; + double s1 = mat[1]*mat[1] + mat[5]*mat[5] + + mat[9]*mat[9]; + if (almostEqual(s0, s1)) { + double s2 = mat[2]*mat[2] + mat[6]*mat[6] + + mat[10]*mat[10]; + if (almostEqual(s2, s0)) { + type |= CONGRUENT; + // Note that scales[0] = sqrt(s0); + if (almostOne(s0)) { + type |= RIGID; + } + } + } + } else { + if(scales == null) + scales = new double[3]; + + double s = scales[0]; + if (almostEqual(s, scales[1]) && + almostEqual(s, scales[2])) { + type |= CONGRUENT; + if (almostOne(s)) { + type |= RIGID; + } + } + } + } + } + dirtyBits &= (~RIGID_BIT | ~CONGRUENT_BIT); + } + + /** + * Classifies a matrix. + */ + private final void classify() { + + if ((dirtyBits & (RIGID_BIT|AFFINE_BIT|CONGRUENT_BIT)) != 0) { + // Test for RIGID, CONGRUENT, AFFINE. + classifyRigid(); + } + + // Test for ZERO, IDENTITY, SCALE, TRANSLATION, + // ORTHOGONAL, NEGATIVE_DETERMINANT + if ((type & AFFINE) != 0) { + if ((type & CONGRUENT) != 0) { + if ((type & RIGID) != 0) { + if (zeroTranslation()) { + type |= ORTHOGONAL; + if (rotateZero()) { + // mat[0], mat[5], mat[10] can be only be + // 1 or -1 when reach here + if ((mat[0] > 0) && + (mat[5] > 0) && + (mat[10] > 0)) { + type |= IDENTITY|SCALE|TRANSLATION; + } + } + } else { + if (rotateZero()) { + type |= TRANSLATION; + } + } + } else { + // uniform scale + if (zeroTranslation() && rotateZero()) { + type |= SCALE; + } + } + + } + } else { + // last row is not (0, 0, 0, 1) + if (almostZero(mat[12]) && + almostZero(mat[13]) && + almostZero(mat[14]) && + almostZero(mat[15]) && + zeroTranslation() && + rotateZero() && + almostZero(mat[0]) && + almostZero(mat[5]) && + almostZero(mat[10])) { + type |= ZERO; + } + } + + if (!getDeterminantSign()) { + type |= NEGATIVE_DETERMINANT; + } + dirtyBits &= ~CLASSIFY_BIT; + } + + final boolean zeroTranslation() { + return (almostZero(mat[3]) && + almostZero(mat[7]) && + almostZero(mat[11])); + } + + final boolean rotateZero() { + return (almostZero(mat[1]) && almostZero(mat[2]) && + almostZero(mat[4]) && almostZero(mat[6]) && + almostZero(mat[8]) && almostZero(mat[9])); + } + + /** + * Returns the matrix elements of this transform as a string. + * @return the matrix elements of this transform + */ + public String toString() { + // also, print classification? + return + mat[0] + ", " + mat[1] + ", " + mat[2] + ", " + mat[3] + "\n" + + mat[4] + ", " + mat[5] + ", " + mat[6] + ", " + mat[7] + "\n" + + mat[8] + ", " + mat[9] + ", " + mat[10] + ", " + mat[11] + "\n" + + mat[12] + ", " + mat[13] + ", " + mat[14] + ", " + mat[15] + + "\n"; + } + + /** + * Sets this transform to the identity matrix. + */ + public final void setIdentity() { + mat[0] = 1.0; mat[1] = 0.0; mat[2] = 0.0; mat[3] = 0.0; + mat[4] = 0.0; mat[5] = 1.0; mat[6] = 0.0; mat[7] = 0.0; + mat[8] = 0.0; mat[9] = 0.0; mat[10] = 1.0; mat[11] = 0.0; + mat[12] = 0.0; mat[13] = 0.0; mat[14] = 0.0; mat[15] = 1.0; + type = IDENTITY | SCALE | ORTHOGONAL | RIGID | CONGRUENT | + AFFINE | TRANSLATION | ORTHO; + dirtyBits = SCALE_BIT | ROTATION_BIT; + // No need to set SVD_BIT + } + + /** + * Sets this transform to all zeros. + */ + public final void setZero() { + mat[0] = 0.0; mat[1] = 0.0; mat[2] = 0.0; mat[3] = 0.0; + mat[4] = 0.0; mat[5] = 0.0; mat[6] = 0.0; mat[7] = 0.0; + mat[8] = 0.0; mat[9] = 0.0; mat[10] = 0.0; mat[11] = 0.0; + mat[12] = 0.0; mat[13] = 0.0; mat[14] = 0.0; mat[15] = 0.0; + + type = ZERO | ORTHO; + dirtyBits = SCALE_BIT | ROTATION_BIT; + } + + + /** + * Adds this transform to transform t1 and places the result into + * this: this = this + t1. + * @param t1 the transform to be added to this transform + */ + public final void add(Transform3D t1) { + for (int i=0 ; i<16 ; i++) { + mat[i] += t1.mat[i]; + } + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + + } + + /** + * Adds transforms t1 and t2 and places the result into this transform. + * @param t1 the transform to be added + * @param t2 the transform to be added + */ + public final void add(Transform3D t1, Transform3D t2) { + for (int i=0 ; i<16 ; i++) { + mat[i] = t1.mat[i] + t2.mat[i]; + } + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + + } + + /** + * Subtracts transform t1 from this transform and places the result + * into this: this = this - t1. + * @param t1 the transform to be subtracted from this transform + */ + public final void sub(Transform3D t1) { + for (int i=0 ; i<16 ; i++) { + mat[i] -= t1.mat[i]; + } + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + + /** + * Subtracts transform t2 from transform t1 and places the result into + * this: this = t1 - t2. + * @param t1 the left transform + * @param t2 the right transform + */ + public final void sub(Transform3D t1, Transform3D t2) { + for (int i=0 ; i<16 ; i++) { + mat[i] = t1.mat[i] - t2.mat[i]; + } + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + + } + + + /** + * Transposes this matrix in place. + */ + public final void transpose() { + double temp; + + temp = mat[4]; + mat[4] = mat[1]; + mat[1] = temp; + + temp = mat[8]; + mat[8] = mat[2]; + mat[2] = temp; + + temp = mat[12]; + mat[12] = mat[3]; + mat[3] = temp; + + temp = mat[9]; + mat[9] = mat[6]; + mat[6] = temp; + + temp = mat[13]; + mat[13] = mat[7]; + mat[7] = temp; + + temp = mat[14]; + mat[14] = mat[11]; + mat[11] = temp; + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + /** + * Transposes transform t1 and places the value into this transform. + * The transform t1 is not modified. + * @param t1 the transform whose transpose is placed into this transform + */ + public final void transpose(Transform3D t1) { + + if (this != t1) { + mat[0] = t1.mat[0]; + mat[1] = t1.mat[4]; + mat[2] = t1.mat[8]; + mat[3] = t1.mat[12]; + mat[4] = t1.mat[1]; + mat[5] = t1.mat[5]; + mat[6] = t1.mat[9]; + mat[7] = t1.mat[13]; + mat[8] = t1.mat[2]; + mat[9] = t1.mat[6]; + mat[10] = t1.mat[10]; + mat[11] = t1.mat[14]; + mat[12] = t1.mat[3]; + mat[13] = t1.mat[7]; + mat[14] = t1.mat[11]; + mat[15] = t1.mat[15]; + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } else { + this.transpose(); + } + + } + + /** + * Sets the value of this transform to the matrix conversion of the + * single precision quaternion argument; the non-rotational + * components are set as if this were an identity matrix. + * @param q1 the quaternion to be converted + */ + public final void set(Quat4f q1) { + + mat[0] = (1.0f - 2.0f*q1.y*q1.y - 2.0f*q1.z*q1.z); + mat[4] = (2.0f*(q1.x*q1.y + q1.w*q1.z)); + mat[8] = (2.0f*(q1.x*q1.z - q1.w*q1.y)); + + mat[1] = (2.0f*(q1.x*q1.y - q1.w*q1.z)); + mat[5] = (1.0f - 2.0f*q1.x*q1.x - 2.0f*q1.z*q1.z); + mat[9] = (2.0f*(q1.y*q1.z + q1.w*q1.x)); + + mat[2] = (2.0f*(q1.x*q1.z + q1.w*q1.y)); + mat[6] = (2.0f*(q1.y*q1.z - q1.w*q1.x)); + mat[10] = (1.0f - 2.0f*q1.x*q1.x - 2.0f*q1.y*q1.y); + + mat[3] = 0.0; + mat[7] = 0.0; + mat[11] = 0.0; + + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + + dirtyBits = CLASSIFY_BIT | SCALE_BIT | ROTATION_BIT; + type = RIGID | CONGRUENT | AFFINE | ORTHO; + } + + /** + * Sets the value of this transform to the matrix conversion of the + * double precision quaternion argument; the non-rotational + * components are set as if this were an identity matrix. + * @param q1 the quaternion to be converted + */ + public final void set(Quat4d q1) { + + mat[0] = (1.0 - 2.0*q1.y*q1.y - 2.0*q1.z*q1.z); + mat[4] = (2.0*(q1.x*q1.y + q1.w*q1.z)); + mat[8] = (2.0*(q1.x*q1.z - q1.w*q1.y)); + + mat[1] = (2.0*(q1.x*q1.y - q1.w*q1.z)); + mat[5] = (1.0 - 2.0*q1.x*q1.x - 2.0*q1.z*q1.z); + mat[9] = (2.0*(q1.y*q1.z + q1.w*q1.x)); + + mat[2] = (2.0*(q1.x*q1.z + q1.w*q1.y)); + mat[6] = (2.0*(q1.y*q1.z - q1.w*q1.x)); + mat[10] = (1.0 - 2.0*q1.x*q1.x - 2.0*q1.y*q1.y); + + mat[3] = 0.0; + mat[7] = 0.0; + mat[11] = 0.0; + + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + dirtyBits = CLASSIFY_BIT | SCALE_BIT | ROTATION_BIT; + type = RIGID | CONGRUENT | AFFINE | ORTHO; + } + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * matrix values in the double precision Matrix3d argument; the other + * elements of this transform are unchanged; any pre-existing scale + * will be preserved; the argument matrix m1 will be checked for proper + * normalization when this transform is internally classified. + * @param m1 the double precision 3x3 matrix + */ + public final void setRotation(Matrix3d m1) { + + if ((dirtyBits & SCALE_BIT)!= 0) { + computeScales(false); + } + + mat[0] = m1.m00*scales[0]; + mat[1] = m1.m01*scales[1]; + mat[2] = m1.m02*scales[2]; + mat[4] = m1.m10*scales[0]; + mat[5] = m1.m11*scales[1]; + mat[6] = m1.m12*scales[2]; + mat[8] = m1.m20*scales[0]; + mat[9] = m1.m21*scales[1]; + mat[10]= m1.m22*scales[2]; + + // only affine bit is preserved + // SCALE_BIT is clear in the above computeScales() so + // there is no need to set it dirty again. + dirtyBits |= (RIGID_BIT | CONGRUENT_BIT| ORTHO_BIT| + CLASSIFY_BIT | ROTSCALESVD_DIRTY); + + if (autoNormalize) { + // the matrix pass in may not normalize + normalize(); + } + } + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * matrix values in the single precision Matrix3f argument; the other + * elements of this transform are unchanged; any pre-existing scale + * will be preserved; the argument matrix m1 will be checked for proper + * normalization when this transform is internally classified. + * @param m1 the single precision 3x3 matrix + */ + public final void setRotation(Matrix3f m1) { + + if ((dirtyBits & SCALE_BIT)!= 0) { + computeScales(false); + } + + mat[0] = m1.m00*scales[0]; + mat[1] = m1.m01*scales[1]; + mat[2] = m1.m02*scales[2]; + mat[4] = m1.m10*scales[0]; + mat[5] = m1.m11*scales[1]; + mat[6] = m1.m12*scales[2]; + mat[8] = m1.m20*scales[0]; + mat[9] = m1.m21*scales[1]; + mat[10]= m1.m22*scales[2]; + + dirtyBits |= (RIGID_BIT | CONGRUENT_BIT| ORTHO_BIT| + CLASSIFY_BIT | ROTSCALESVD_DIRTY); + + if (autoNormalize) { + normalize(); + } + } + + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * matrix equivalent values of the quaternion argument; the other + * elements of this transform are unchanged; any pre-existing scale + * in the transform is preserved. + * @param q1 the quaternion that specifies the rotation + */ + public final void setRotation(Quat4f q1) { + + if ((dirtyBits & SCALE_BIT)!= 0) { + computeScales(false); + } + + mat[0] = (1.0 - 2.0*q1.y*q1.y - 2.0*q1.z*q1.z)*scales[0]; + mat[4] = (2.0*(q1.x*q1.y + q1.w*q1.z))*scales[0]; + mat[8] = (2.0*(q1.x*q1.z - q1.w*q1.y))*scales[0]; + + mat[1] = (2.0*(q1.x*q1.y - q1.w*q1.z))*scales[1]; + mat[5] = (1.0 - 2.0*q1.x*q1.x - 2.0*q1.z*q1.z)*scales[1]; + mat[9] = (2.0*(q1.y * q1.z + q1.w * q1.x))*scales[1]; + + mat[2] = (2.0*(q1.x*q1.z + q1.w*q1.y))*scales[2]; + mat[6] = (2.0*(q1.y*q1.z - q1.w*q1.x))*scales[2]; + mat[10] = (1.0 - 2.0*q1.x*q1.x - 2.0*q1.y*q1.y)*scales[2]; + + dirtyBits |= CLASSIFY_BIT | ROTATION_BIT; + dirtyBits &= ~ORTHO_BIT; + type |= ORTHO; + type &= ~(ORTHOGONAL|IDENTITY|SCALE|TRANSLATION|SCALE|ZERO); + } + + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * matrix equivalent values of the quaternion argument; the other + * elements of this transform are unchanged; any pre-existing scale + * in the transform is preserved. + * @param q1 the quaternion that specifies the rotation + */ + public final void setRotation(Quat4d q1) { + + if ((dirtyBits & SCALE_BIT)!= 0) { + computeScales(false); + } + + mat[0] = (1.0 - 2.0*q1.y*q1.y - 2.0*q1.z*q1.z)*scales[0]; + mat[4] = (2.0*(q1.x*q1.y + q1.w*q1.z))*scales[0]; + mat[8] = (2.0*(q1.x*q1.z - q1.w*q1.y))*scales[0]; + + mat[1] = (2.0*(q1.x*q1.y - q1.w*q1.z))*scales[1]; + mat[5] = (1.0 - 2.0*q1.x*q1.x - 2.0*q1.z*q1.z)*scales[1]; + mat[9] = (2.0*(q1.y * q1.z + q1.w * q1.x))*scales[1]; + + mat[2] = (2.0*(q1.x*q1.z + q1.w*q1.y))*scales[2]; + mat[6] = (2.0*(q1.y*q1.z - q1.w*q1.x))*scales[2]; + mat[10] = (1.0 - 2.0*q1.x*q1.x - 2.0*q1.y*q1.y)*scales[2]; + + dirtyBits |= CLASSIFY_BIT | ROTATION_BIT; + dirtyBits &= ~ORTHO_BIT; + type |= ORTHO; + type &= ~(ORTHOGONAL|IDENTITY|SCALE|TRANSLATION|SCALE|ZERO); + } + + + /** + * Sets the value of this transform to the matrix conversion + * of the single precision axis-angle argument; all of the matrix + * values are modified. + * @param a1 the axis-angle to be converted (x, y, z, angle) + */ + public final void set(AxisAngle4f a1) { + + double mag = Math.sqrt( a1.x*a1.x + a1.y*a1.y + a1.z*a1.z); + + if (almostZero(mag)) { + setIdentity(); + } else { + mag = 1.0/mag; + double ax = a1.x*mag; + double ay = a1.y*mag; + double az = a1.z*mag; + + double sinTheta = Math.sin((double)a1.angle); + double cosTheta = Math.cos((double)a1.angle); + double t = 1.0 - cosTheta; + + double xz = ax * az; + double xy = ax * ay; + double yz = ay * az; + + mat[0] = t * ax * ax + cosTheta; + mat[1] = t * xy - sinTheta * az; + mat[2] = t * xz + sinTheta * ay; + mat[3] = 0.0; + + mat[4] = t * xy + sinTheta * az; + mat[5] = t * ay * ay + cosTheta; + mat[6] = t * yz - sinTheta * ax; + mat[7] = 0.0; + + mat[8] = t * xz - sinTheta * ay; + mat[9] = t * yz + sinTheta * ax; + mat[10] = t * az * az + cosTheta; + mat[11] = 0.0; + + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = CONGRUENT | AFFINE | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | SCALE_BIT; + } + } + + + /** + * Sets the value of this transform to the matrix conversion + * of the double precision axis-angle argument; all of the matrix + * values are modified. + * @param a1 the axis-angle to be converted (x, y, z, angle) + */ + public final void set(AxisAngle4d a1) { + + double mag = Math.sqrt( a1.x*a1.x + a1.y*a1.y + a1.z*a1.z); + + if (almostZero(mag)) { + setIdentity(); + } else { + mag = 1.0/mag; + double ax = a1.x*mag; + double ay = a1.y*mag; + double az = a1.z*mag; + + double sinTheta = Math.sin(a1.angle); + double cosTheta = Math.cos(a1.angle); + double t = 1.0 - cosTheta; + + double xz = ax * az; + double xy = ax * ay; + double yz = ay * az; + + mat[0] = t * ax * ax + cosTheta; + mat[1] = t * xy - sinTheta * az; + mat[2] = t * xz + sinTheta * ay; + mat[3] = 0.0; + + mat[4] = t * xy + sinTheta * az; + mat[5] = t * ay * ay + cosTheta; + mat[6] = t * yz - sinTheta * ax; + mat[7] = 0.0; + + mat[8] = t * xz - sinTheta * ay; + mat[9] = t * yz + sinTheta * ax; + mat[10] = t * az * az + cosTheta; + mat[11] = 0.0; + + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = CONGRUENT | AFFINE | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | SCALE_BIT; + } + } + + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * matrix equivalent values of the axis-angle argument; the other + * elements of this transform are unchanged; any pre-existing scale + * in the transform is preserved. + * @param a1 the axis-angle to be converted (x, y, z, angle) + */ + public final void setRotation(AxisAngle4d a1) { + + if ((dirtyBits & SCALE_BIT)!= 0) { + computeScales(false); + } + + double mag = Math.sqrt( a1.x*a1.x + a1.y*a1.y + a1.z*a1.z); + + if (almostZero(mag)) { + mat[0] = scales[0]; + mat[1] = 0.0; + mat[2] = 0.0; + mat[4] = 0.0; + mat[5] = scales[1]; + mat[6] = 0.0; + mat[8] = 0.0; + mat[9] = 0.0; + mat[10] = scales[2]; + } else { + mag = 1.0/mag; + double ax = a1.x*mag; + double ay = a1.y*mag; + double az = a1.z*mag; + + double sinTheta = Math.sin(a1.angle); + double cosTheta = Math.cos(a1.angle); + double t = 1.0 - cosTheta; + + double xz = ax * az; + double xy = ax * ay; + double yz = ay * az; + + mat[0] = (t * ax * ax + cosTheta)*scales[0]; + mat[1] = (t * xy - sinTheta * az)*scales[1]; + mat[2] = (t * xz + sinTheta * ay)*scales[2]; + + mat[4] = (t * xy + sinTheta * az)*scales[0]; + mat[5] = (t * ay * ay + cosTheta)*scales[1]; + mat[6] = (t * yz - sinTheta * ax)*scales[2]; + + mat[8] = (t * xz - sinTheta * ay)*scales[0]; + mat[9] = (t * yz + sinTheta * ax)*scales[1]; + mat[10] = (t * az * az + cosTheta)*scales[2]; + } + + + // Rigid remain rigid, congruent remain congruent after + // set rotation + dirtyBits |= CLASSIFY_BIT | ROTATION_BIT; + dirtyBits &= ~ORTHO_BIT; + type |= ORTHO; + type &= ~(ORTHOGONAL|IDENTITY|SCALE|TRANSLATION|SCALE|ZERO); + } + + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * matrix equivalent values of the axis-angle argument; the other + * elements of this transform are unchanged; any pre-existing scale + * in the transform is preserved. + * @param a1 the axis-angle to be converted (x, y, z, angle) + */ + public final void setRotation(AxisAngle4f a1) { + + if ((dirtyBits & SCALE_BIT)!= 0) { + computeScales(false); + } + + double mag = Math.sqrt( a1.x*a1.x + a1.y*a1.y + a1.z*a1.z); + + if (almostZero(mag)) { + mat[0] = scales[0]; + mat[1] = 0.0; + mat[2] = 0.0; + mat[4] = 0.0; + mat[5] = scales[1]; + mat[6] = 0.0; + mat[8] = 0.0; + mat[9] = 0.0; + mat[10] = scales[2]; + } else { + mag = 1.0/mag; + double ax = a1.x*mag; + double ay = a1.y*mag; + double az = a1.z*mag; + + double sinTheta = Math.sin(a1.angle); + double cosTheta = Math.cos(a1.angle); + double t = 1.0 - cosTheta; + + double xz = ax * az; + double xy = ax * ay; + double yz = ay * az; + + mat[0] = (t * ax * ax + cosTheta)*scales[0]; + mat[1] = (t * xy - sinTheta * az)*scales[1]; + mat[2] = (t * xz + sinTheta * ay)*scales[2]; + + mat[4] = (t * xy + sinTheta * az)*scales[0]; + mat[5] = (t * ay * ay + cosTheta)*scales[1]; + mat[6] = (t * yz - sinTheta * ax)*scales[2]; + + mat[8] = (t * xz - sinTheta * ay)*scales[0]; + mat[9] = (t * yz + sinTheta * ax)*scales[1]; + mat[10] = (t * az * az + cosTheta)*scales[2]; + } + + + // Rigid remain rigid, congruent remain congruent after + // set rotation + dirtyBits |= CLASSIFY_BIT | ROTATION_BIT; + dirtyBits &= (~ORTHO_BIT | ~SVD_BIT); + type |= ORTHO; + type &= ~(ORTHOGONAL|IDENTITY|SCALE|TRANSLATION|SCALE|ZERO); + } + + + /** + * Sets the value of this transform to a counter clockwise rotation + * about the x axis. All of the non-rotational components are set as + * if this were an identity matrix. + * @param angle the angle to rotate about the X axis in radians + */ + public void rotX(double angle) { + double sinAngle = Math.sin(angle); + double cosAngle = Math.cos(angle); + + mat[0] = 1.0; + mat[1] = 0.0; + mat[2] = 0.0; + mat[3] = 0.0; + + mat[4] = 0.0; + mat[5] = cosAngle; + mat[6] = -sinAngle; + mat[7] = 0.0; + + mat[8] = 0.0; + mat[9] = sinAngle; + mat[10] = cosAngle; + mat[11] = 0.0; + + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = CONGRUENT | AFFINE | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | SCALE_BIT; + } + + /** + * Sets the value of this transform to a counter clockwise rotation about + * the y axis. All of the non-rotational components are set as if this + * were an identity matrix. + * @param angle the angle to rotate about the Y axis in radians + */ + public void rotY(double angle) { + double sinAngle = Math.sin(angle); + double cosAngle = Math.cos(angle); + + mat[0] = cosAngle; + mat[1] = 0.0; + mat[2] = sinAngle; + mat[3] = 0.0; + + mat[4] = 0.0; + mat[5] = 1.0; + mat[6] = 0.0; + mat[7] = 0.0; + + mat[8] = -sinAngle; + mat[9] = 0.0; + mat[10] = cosAngle; + mat[11] = 0.0; + + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = CONGRUENT | AFFINE | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | SCALE_BIT; + } + + + /** + * Sets the value of this transform to a counter clockwise rotation + * about the z axis. All of the non-rotational components are set + * as if this were an identity matrix. + * @param angle the angle to rotate about the Z axis in radians + */ + public void rotZ(double angle) { + double sinAngle = Math.sin(angle); + double cosAngle = Math.cos(angle); + + mat[0] = cosAngle; + mat[1] = -sinAngle; + mat[2] = 0.0; + mat[3] = 0.0; + + mat[4] = sinAngle; + mat[5] = cosAngle; + mat[6] = 0.0; + mat[7] = 0.0; + + mat[8] = 0.0; + mat[9] = 0.0; + mat[10] = 1.0; + mat[11] = 0.0; + + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = CONGRUENT | AFFINE | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | SCALE_BIT; + } + + + /** + * Sets the translational value of this matrix to the Vector3f parameter + * values, and sets the other components of the matrix as if this + * transform were an identity matrix. + * @param trans the translational component + */ + public final void set(Vector3f trans) { + mat[0] = 1.0; mat[1] = 0.0; mat[2] = 0.0; mat[3] = trans.x; + mat[4] = 0.0; mat[5] = 1.0; mat[6] = 0.0; mat[7] = trans.y; + mat[8] = 0.0; mat[9] = 0.0; mat[10] = 1.0; mat[11] = trans.z; + mat[12] = 0.0; mat[13] = 0.0; mat[14] = 0.0; mat[15] = 1.0; + + type = CONGRUENT | AFFINE | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | SCALE_BIT; + } + + /** + * Sets the translational value of this matrix to the Vector3d paramter + * values, and sets the other components of the matrix as if this + * transform were an identity matrix. + * @param trans the translational component + */ + public final void set(Vector3d trans) { + mat[0] = 1.0; mat[1] = 0.0; mat[2] = 0.0; mat[3] = trans.x; + mat[4] = 0.0; mat[5] = 1.0; mat[6] = 0.0; mat[7] = trans.y; + mat[8] = 0.0; mat[9] = 0.0; mat[10] = 1.0; mat[11] = trans.z; + mat[12] = 0.0; mat[13] = 0.0; mat[14] = 0.0; mat[15] = 1.0; + + + type = CONGRUENT | AFFINE | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | SCALE_BIT; + } + + /** + * Sets the scale component of the current transform; any existing + * scale is first factored out of the existing transform before + * the new scale is applied. + * @param scale the new scale amount + */ + public final void setScale(double scale) { + if ((dirtyBits & ROTATION_BIT)!= 0) { + computeScaleRotation(false); + } + + scales[0] = scales[1] = scales[2] = scale; + mat[0] = rot[0]*scale; + mat[1] = rot[1]*scale; + mat[2] = rot[2]*scale; + mat[4] = rot[3]*scale; + mat[5] = rot[4]*scale; + mat[6] = rot[5]*scale; + mat[8] = rot[6]*scale; + mat[9] = rot[7]*scale; + mat[10] = rot[8]*scale; + + dirtyBits |= (CLASSIFY_BIT | RIGID_BIT | CONGRUENT_BIT | SVD_BIT); + dirtyBits &= ~SCALE_BIT; + } + + + /** + * Sets the possibly non-uniform scale component of the current + * transform; any existing scale is first factored out of the + * existing transform before the new scale is applied. + * @param scale the new x,y,z scale values + */ + public final void setScale(Vector3d scale) { + + if ((dirtyBits & ROTATION_BIT)!= 0) { + computeScaleRotation(false); + } + + scales[0] = scale.x; + scales[1] = scale.y; + scales[2] = scale.z; + + mat[0] = rot[0]*scale.x; + mat[1] = rot[1]*scale.y; + mat[2] = rot[2]*scale.z; + mat[4] = rot[3]*scale.x; + mat[5] = rot[4]*scale.y; + mat[6] = rot[5]*scale.z; + mat[8] = rot[6]*scale.x; + mat[9] = rot[7]*scale.y; + mat[10] = rot[8]*scale.z; + dirtyBits |= (CLASSIFY_BIT | RIGID_BIT | CONGRUENT_BIT | SVD_BIT); + dirtyBits &= ~SCALE_BIT; + } + + + /** + * Replaces the current transform with a non-uniform scale transform. + * All values of the existing transform are replaced. + * @param xScale the new X scale amount + * @param yScale the new Y scale amount + * @param zScale the new Z scale amount + * @deprecated Use setScale(Vector3d) instead of setNonUniformScale; + * note that the setScale only modifies the scale component + */ + public final void setNonUniformScale(double xScale, + double yScale, + double zScale) { + if(scales == null) + scales = new double[3]; + + scales[0] = xScale; + scales[1] = yScale; + scales[2] = zScale; + mat[0] = xScale; + mat[1] = 0.0; + mat[2] = 0.0; + mat[3] = 0.0; + mat[4] = 0.0; + mat[5] = yScale; + mat[6] = 0.0; + mat[7] = 0.0; + mat[8] = 0.0; + mat[9] = 0.0; + mat[10] = zScale; + mat[11] = 0.0; + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = AFFINE | ORTHO; + dirtyBits = CLASSIFY_BIT | CONGRUENT_BIT | RIGID_BIT | + ROTATION_BIT; + } + + /** + * Replaces the translational components of this transform to the values + * in the Vector3f argument; the other values of this transform are not + * modified. + * @param trans the translational component + */ + public final void setTranslation(Vector3f trans) { + mat[3] = trans.x; + mat[7] = trans.y; + mat[11] = trans.z; + // Only preserve CONGRUENT, RIGID, ORTHO + type &= ~(ORTHOGONAL|IDENTITY|SCALE|TRANSLATION|SCALE|ZERO); + dirtyBits |= CLASSIFY_BIT; + } + + + /** + * Replaces the translational components of this transform to the values + * in the Vector3d argument; the other values of this transform are not + * modified. + * @param trans the translational component + */ + public final void setTranslation(Vector3d trans) { + mat[3] = trans.x; + mat[7] = trans.y; + mat[11] = trans.z; + type &= ~(ORTHOGONAL|IDENTITY|SCALE|TRANSLATION|SCALE|ZERO); + dirtyBits |= CLASSIFY_BIT; + } + + + /** + * Sets the value of this matrix from the rotation expressed + * by the quaternion q1, the translation t1, and the scale s. + * @param q1 the rotation expressed as a quaternion + * @param t1 the translation + * @param s the scale value + */ + public final void set(Quat4d q1, Vector3d t1, double s) { + if(scales == null) + scales = new double[3]; + + scales[0] = scales[1] = scales[2] = s; + + mat[0] = (1.0 - 2.0*q1.y*q1.y - 2.0*q1.z*q1.z)*s; + mat[4] = (2.0*(q1.x*q1.y + q1.w*q1.z))*s; + mat[8] = (2.0*(q1.x*q1.z - q1.w*q1.y))*s; + + mat[1] = (2.0*(q1.x*q1.y - q1.w*q1.z))*s; + mat[5] = (1.0 - 2.0*q1.x*q1.x - 2.0*q1.z*q1.z)*s; + mat[9] = (2.0*(q1.y*q1.z + q1.w*q1.x))*s; + + mat[2] = (2.0*(q1.x*q1.z + q1.w*q1.y))*s; + mat[6] = (2.0*(q1.y*q1.z - q1.w*q1.x))*s; + mat[10] = (1.0 - 2.0*q1.x*q1.x - 2.0*q1.y*q1.y)*s; + + mat[3] = t1.x; + mat[7] = t1.y; + mat[11] = t1.z; + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + type = CONGRUENT | AFFINE | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | RIGID_BIT; + } + + /** + * Sets the value of this matrix from the rotation expressed + * by the quaternion q1, the translation t1, and the scale s. + * @param q1 the rotation expressed as a quaternion + * @param t1 the translation + * @param s the scale value + */ + public final void set(Quat4f q1, Vector3d t1, double s) { + if(scales == null) + scales = new double[3]; + + scales[0] = scales[1] = scales[2] = s; + + mat[0] = (1.0f - 2.0f*q1.y*q1.y - 2.0f*q1.z*q1.z)*s; + mat[4] = (2.0f*(q1.x*q1.y + q1.w*q1.z))*s; + mat[8] = (2.0f*(q1.x*q1.z - q1.w*q1.y))*s; + + mat[1] = (2.0f*(q1.x*q1.y - q1.w*q1.z))*s; + mat[5] = (1.0f - 2.0f*q1.x*q1.x - 2.0f*q1.z*q1.z)*s; + mat[9] = (2.0f*(q1.y*q1.z + q1.w*q1.x))*s; + + mat[2] = (2.0f*(q1.x*q1.z + q1.w*q1.y))*s; + mat[6] = (2.0f*(q1.y*q1.z - q1.w*q1.x))*s; + mat[10] = (1.0f - 2.0f*q1.x*q1.x - 2.0f*q1.y*q1.y)*s; + + mat[3] = t1.x; + mat[7] = t1.y; + mat[11] = t1.z; + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = CONGRUENT | AFFINE | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT| RIGID_BIT; + } + + /** + * Sets the value of this matrix from the rotation expressed + * by the quaternion q1, the translation t1, and the scale s. + * @param q1 the rotation expressed as a quaternion + * @param t1 the translation + * @param s the scale value + */ + public final void set(Quat4f q1, Vector3f t1, float s) { + if(scales == null) + scales = new double[3]; + + scales[0] = scales[1] = scales[2] = s; + + mat[0] = (1.0f - 2.0f*q1.y*q1.y - 2.0f*q1.z*q1.z)*s; + mat[4] = (2.0f*(q1.x*q1.y + q1.w*q1.z))*s; + mat[8] = (2.0f*(q1.x*q1.z - q1.w*q1.y))*s; + + mat[1] = (2.0f*(q1.x*q1.y - q1.w*q1.z))*s; + mat[5] = (1.0f - 2.0f*q1.x*q1.x - 2.0f*q1.z*q1.z)*s; + mat[9] = (2.0f*(q1.y*q1.z + q1.w*q1.x))*s; + + mat[2] = (2.0f*(q1.x*q1.z + q1.w*q1.y))*s; + mat[6] = (2.0f*(q1.y*q1.z - q1.w*q1.x))*s; + mat[10] = (1.0f - 2.0f*q1.x*q1.x - 2.0f*q1.y*q1.y)*s; + + mat[3] = t1.x; + mat[7] = t1.y; + mat[11] = t1.z; + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = CONGRUENT | AFFINE | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | RIGID_BIT; + } + + /** + * Sets the value of this matrix from the rotation expressed + * by the rotation matrix m1, the translation t1, and the scale s. + * The scale is only applied to the + * rotational component of the matrix (upper 3x3) and not to the + * translational component of the matrix. + * @param m1 the rotation matrix + * @param t1 the translation + * @param s the scale value + */ + public final void set(Matrix3f m1, Vector3f t1, float s) { + mat[0]=m1.m00*s; + mat[1]=m1.m01*s; + mat[2]=m1.m02*s; + mat[3]=t1.x; + mat[4]=m1.m10*s; + mat[5]=m1.m11*s; + mat[6]=m1.m12*s; + mat[7]=t1.y; + mat[8]=m1.m20*s; + mat[9]=m1.m21*s; + mat[10]=m1.m22*s; + mat[11]=t1.z; + mat[12]=0.0; + mat[13]=0.0; + mat[14]=0.0; + mat[15]=1.0; + + type = AFFINE; + dirtyBits = ORTHO_BIT | CONGRUENT_BIT | RIGID_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + // input matrix may not normalize + normalize(); + } + } + + /** + * Sets the value of this matrix from the rotation expressed + * by the rotation matrix m1, the translation t1, and the scale s. + * The scale is only applied to the + * rotational component of the matrix (upper 3x3) and not to the + * translational component of the matrix. + * @param m1 the rotation matrix + * @param t1 the translation + * @param s the scale value + */ + public final void set(Matrix3f m1, Vector3d t1, double s) { + mat[0]=m1.m00*s; + mat[1]=m1.m01*s; + mat[2]=m1.m02*s; + mat[3]=t1.x; + mat[4]=m1.m10*s; + mat[5]=m1.m11*s; + mat[6]=m1.m12*s; + mat[7]=t1.y; + mat[8]=m1.m20*s; + mat[9]=m1.m21*s; + mat[10]=m1.m22*s; + mat[11]=t1.z; + mat[12]=0.0; + mat[13]=0.0; + mat[14]=0.0; + mat[15]=1.0; + + type = AFFINE; + dirtyBits = ORTHO_BIT | CONGRUENT_BIT | RIGID_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + /** + * Sets the value of this matrix from the rotation expressed + * by the rotation matrix m1, the translation t1, and the scale s. + * The scale is only applied to the + * rotational component of the matrix (upper 3x3) and not to the + * translational component of the matrix. + * @param m1 the rotation matrix + * @param t1 the translation + * @param s the scale value + */ + public final void set(Matrix3d m1, Vector3d t1, double s) { + mat[0]=m1.m00*s; + mat[1]=m1.m01*s; + mat[2]=m1.m02*s; + mat[3]=t1.x; + mat[4]=m1.m10*s; + mat[5]=m1.m11*s; + mat[6]=m1.m12*s; + mat[7]=t1.y; + mat[8]=m1.m20*s; + mat[9]=m1.m21*s; + mat[10]=m1.m22*s; + mat[11]=t1.z; + mat[12]=0.0; + mat[13]=0.0; + mat[14]=0.0; + mat[15]=1.0; + + type = AFFINE; + dirtyBits = ORTHO_BIT | CONGRUENT_BIT | RIGID_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + /** + * Sets the matrix values of this transform to the matrix values in the + * upper 4x4 corner of the GMatrix parameter. If the parameter matrix is + * smaller than 4x4, the remaining elements in the transform matrix are + * assigned to zero. The transform matrix type is classified + * internally by the Transform3D class. + * @param matrix the general matrix from which the Transform3D matrix is derived + */ + public final void set(GMatrix matrix) { + int i,j, k; + int numRows = matrix.getNumRow(); + int numCol = matrix.getNumCol(); + + for(i=0 ; i<4 ; i++) { + k = i*4; + for(j=0 ; j<4 ; j++) { + if(i>=numRows || j>=numCol) + mat[k+j] = 0.0; + else + mat[k+j] = matrix.getElement(i,j); + } + } + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + + } + + /** + * Sets the matrix, type, and state of this transform to the matrix, + * type, and state of transform t1. + * @param t1 the transform to be copied + */ + public final void set(Transform3D t1){ + mat[0] = t1.mat[0]; + mat[1] = t1.mat[1]; + mat[2] = t1.mat[2]; + mat[3] = t1.mat[3]; + mat[4] = t1.mat[4]; + mat[5] = t1.mat[5]; + mat[6] = t1.mat[6]; + mat[7] = t1.mat[7]; + mat[8] = t1.mat[8]; + mat[9] = t1.mat[9]; + mat[10] = t1.mat[10]; + mat[11] = t1.mat[11]; + mat[12] = t1.mat[12]; + mat[13] = t1.mat[13]; + mat[14] = t1.mat[14]; + mat[15] = t1.mat[15]; + type = t1.type; + + // don't copy rot[] and scales[] + dirtyBits = t1.dirtyBits | ROTATION_BIT | SCALE_BIT; + autoNormalize = t1.autoNormalize; + } + + // This version gets a lock before doing the set. It is used internally + synchronized void setWithLock(Transform3D t1) { + this.set(t1); + } + + // This version gets a lock before doing the get. It is used internally + synchronized void getWithLock(Transform3D t1) { + t1.set(this); + } + + /** + * Sets the matrix values of this transform to the matrix values in the + * double precision array parameter. The matrix type is classified + * internally by the Transform3D class. + * @param matrix the double precision array of length 16 in row major format + */ + public final void set(double[] matrix) { + mat[0] = matrix[0]; + mat[1] = matrix[1]; + mat[2] = matrix[2]; + mat[3] = matrix[3]; + mat[4] = matrix[4]; + mat[5] = matrix[5]; + mat[6] = matrix[6]; + mat[7] = matrix[7]; + mat[8] = matrix[8]; + mat[9] = matrix[9]; + mat[10] = matrix[10]; + mat[11] = matrix[11]; + mat[12] = matrix[12]; + mat[13] = matrix[13]; + mat[14] = matrix[14]; + mat[15] = matrix[15]; + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + + } + + /** + * Sets the matrix values of this transform to the matrix values in the + * single precision array parameter. The matrix type is classified + * internally by the Transform3D class. + * @param matrix the single precision array of length 16 in row major format + */ + public final void set(float[] matrix) { + mat[0] = matrix[0]; + mat[1] = matrix[1]; + mat[2] = matrix[2]; + mat[3] = matrix[3]; + mat[4] = matrix[4]; + mat[5] = matrix[5]; + mat[6] = matrix[6]; + mat[7] = matrix[7]; + mat[8] = matrix[8]; + mat[9] = matrix[9]; + mat[10] = matrix[10]; + mat[11] = matrix[11]; + mat[12] = matrix[12]; + mat[13] = matrix[13]; + mat[14] = matrix[14]; + mat[15] = matrix[15]; + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + + } + + /** + * Sets the matrix values of this transform to the matrix values in the + * double precision Matrix4d argument. The transform type is classified + * internally by the Transform3D class. + * @param m1 the double precision 4x4 matrix + */ + public final void set(Matrix4d m1) { + mat[0] = m1.m00; + mat[1] = m1.m01; + mat[2] = m1.m02; + mat[3] = m1.m03; + mat[4] = m1.m10; + mat[5] = m1.m11; + mat[6] = m1.m12; + mat[7] = m1.m13; + mat[8] = m1.m20; + mat[9] = m1.m21; + mat[10] = m1.m22; + mat[11] = m1.m23; + mat[12] = m1.m30; + mat[13] = m1.m31; + mat[14] = m1.m32; + mat[15] = m1.m33; + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + + /** + * Sets the matrix values of this transform to the matrix values in the + * single precision Matrix4f argument. The transform type is classified + * internally by the Transform3D class. + * @param m1 the single precision 4x4 matrix + */ + public final void set(Matrix4f m1) { + mat[0] = m1.m00; + mat[1] = m1.m01; + mat[2] = m1.m02; + mat[3] = m1.m03; + mat[4] = m1.m10; + mat[5] = m1.m11; + mat[6] = m1.m12; + mat[7] = m1.m13; + mat[8] = m1.m20; + mat[9] = m1.m21; + mat[10] = m1.m22; + mat[11] = m1.m23; + mat[12] = m1.m30; + mat[13] = m1.m31; + mat[14] = m1.m32; + mat[15] = m1.m33; + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * matrix values in the single precision Matrix3f argument; the other + * elements of this transform are initialized as if this were an identity + * matrix (i.e., affine matrix with no translational component). + * @param m1 the single precision 3x3 matrix + */ + public final void set(Matrix3f m1) { + mat[0] = m1.m00; + mat[1] = m1.m01; + mat[2] = m1.m02; + mat[3] = 0.0; + mat[4] = m1.m10; + mat[5] = m1.m11; + mat[6] = m1.m12; + mat[7] = 0.0; + mat[8] = m1.m20; + mat[9] = m1.m21; + mat[10] = m1.m22; + mat[11] = 0.0; + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = AFFINE; + dirtyBits = ORTHO_BIT | CONGRUENT_BIT | RIGID_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * matrix values in the double precision Matrix3d argument; the other + * elements of this transform are initialized as if this were an identity + * matrix (ie, affine matrix with no translational component). + * @param m1 the double precision 3x3 matrix + */ + public final void set(Matrix3d m1) { + mat[0] = m1.m00; + mat[1] = m1.m01; + mat[2] = m1.m02; + mat[3] = 0.0; + mat[4] = m1.m10; + mat[5] = m1.m11; + mat[6] = m1.m12; + mat[7] = 0.0; + mat[8] = m1.m20; + mat[9] = m1.m21; + mat[10] = m1.m22; + mat[11] = 0.0; + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = AFFINE; + dirtyBits = ORTHO_BIT | CONGRUENT_BIT | RIGID_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + + } + + + /** + * Sets the rotational component (upper 3x3) of this transform to the + * rotation matrix converted from the Euler angles provided; the other + * non-rotational elements are set as if this were an identity matrix. + * The euler parameter is a Vector3d consisting of three rotation angles + * applied first about the X, then Y then Z axis. + * These rotations are applied using a static frame of reference. In + * other words, the orientation of the Y rotation axis is not affected + * by the X rotation and the orientation of the Z rotation axis is not + * affected by the X or Y rotation. + * @param euler the Vector3d consisting of three rotation angles about X,Y,Z + * + */ + public final void setEuler(Vector3d euler) { + double sina, sinb, sinc; + double cosa, cosb, cosc; + + sina = Math.sin(euler.x); + sinb = Math.sin(euler.y); + sinc = Math.sin(euler.z); + cosa = Math.cos(euler.x); + cosb = Math.cos(euler.y); + cosc = Math.cos(euler.z); + + mat[0] = cosb * cosc; + mat[1] = -(cosa * sinc) + (sina * sinb * cosc); + mat[2] = (sina * sinc) + (cosa * sinb *cosc); + mat[3] = 0.0; + + mat[4] = cosb * sinc; + mat[5] = (cosa * cosc) + (sina * sinb * sinc); + mat[6] = -(sina * cosc) + (cosa * sinb *sinc); + mat[7] = 0.0; + + mat[8] = -sinb; + mat[9] = sina * cosb; + mat[10] = cosa * cosb; + mat[11] = 0.0; + + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + type = AFFINE | CONGRUENT | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | SCALE_BIT | ROTATION_BIT; + } + + + /** + * Places the values of this transform into the double precision array + * of length 16. The first four elements of the array will contain + * the top row of the transform matrix, etc. + * @param matrix the double precision array of length 16 + */ + public final void get(double[] matrix) { + matrix[0] = mat[0]; + matrix[1] = mat[1]; + matrix[2] = mat[2]; + matrix[3] = mat[3]; + matrix[4] = mat[4]; + matrix[5] = mat[5]; + matrix[6] = mat[6]; + matrix[7] = mat[7]; + matrix[8] = mat[8]; + matrix[9] = mat[9]; + matrix[10] = mat[10]; + matrix[11] = mat[11]; + matrix[12] = mat[12]; + matrix[13] = mat[13]; + matrix[14] = mat[14]; + matrix[15] = mat[15]; + } + + + /** + * Places the values of this transform into the single precision array + * of length 16. The first four elements of the array will contain + * the top row of the transform matrix, etc. + * @param matrix the single precision array of length 16 + */ + public final void get(float[] matrix) { + matrix[0] = (float) mat[0]; + matrix[1] = (float) mat[1]; + matrix[2] = (float) mat[2]; + matrix[3] = (float) mat[3]; + matrix[4] = (float) mat[4]; + matrix[5] = (float) mat[5]; + matrix[6] = (float) mat[6]; + matrix[7] = (float) mat[7]; + matrix[8] = (float) mat[8]; + matrix[9] = (float) mat[9]; + matrix[10] = (float) mat[10]; + matrix[11] = (float) mat[11]; + matrix[12] = (float) mat[12]; + matrix[13] = (float) mat[13]; + matrix[14] = (float) mat[14]; + matrix[15] = (float) mat[15]; + } + + + /** + * Places the normalized rotational component of this transform + * into the 3x3 matrix argument. + * @param m1 the matrix into which the rotational component is placed + */ + public final void get(Matrix3d m1) { + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } + m1.m00 = rot[0]; + m1.m01 = rot[1]; + m1.m02 = rot[2]; + m1.m10 = rot[3]; + m1.m11 = rot[4]; + m1.m12 = rot[5]; + m1.m20 = rot[6]; + m1.m21 = rot[7]; + m1.m22 = rot[8]; + } + + /** + * Places the normalized rotational component of this transform + * into the 3x3 matrix argument. + * @param m1 the matrix into which the rotational component is placed + */ + public final void get(Matrix3f m1) { + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } + m1.m00 = (float)rot[0]; + m1.m01 = (float)rot[1]; + m1.m02 = (float)rot[2]; + m1.m10 = (float)rot[3]; + m1.m11 = (float)rot[4]; + m1.m12 = (float)rot[5]; + m1.m20 = (float)rot[6]; + m1.m21 = (float)rot[7]; + m1.m22 = (float)rot[8]; + } + + /** + * Places the quaternion equivalent of the normalized rotational + * component of this transform into the quaternion parameter. + * @param q1 the quaternion into which the rotation component is placed + */ + public final void get(Quat4f q1) { + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } + + double ww = 0.25*(1.0 + rot[0] + rot[4] + rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-10)) { + q1.w = (float)Math.sqrt(ww); + ww = 0.25/q1.w; + q1.x = (float)((rot[7] - rot[5])*ww); + q1.y = (float)((rot[2] - rot[6])*ww); + q1.z = (float)((rot[3] - rot[1])*ww); + return; + } + + q1.w = 0.0f; + ww = -0.5*(rot[4] + rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-10)) { + q1.x = (float)Math.sqrt(ww); + ww = 0.5/q1.x; + q1.y = (float)(rot[3]*ww); + q1.z = (float)(rot[6]*ww); + return; + } + + q1.x = 0.0f; + ww = 0.5*(1.0 - rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-10)) { + q1.y = (float)Math.sqrt(ww); + q1.z = (float)(rot[7]/(2.0*q1.y)); + return; + } + + q1.y = 0.0f; + q1.z = 1.0f; + } + + /** + * Places the quaternion equivalent of the normalized rotational + * component of this transform into the quaternion parameter. + * @param q1 the quaternion into which the rotation component is placed + */ + public final void get(Quat4d q1) { + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } + + double ww = 0.25*(1.0 + rot[0] + rot[4] + rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-10)) { + q1.w = Math.sqrt(ww); + ww = 0.25/q1.w; + q1.x = (rot[7] - rot[5])*ww; + q1.y = (rot[2] - rot[6])*ww; + q1.z = (rot[3] - rot[1])*ww; + return; + } + + q1.w = 0.0; + ww = -0.5*(rot[4] + rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-10)) { + q1.x = Math.sqrt(ww); + ww = 0.5/q1.x; + q1.y = rot[3]*ww; + q1.z = rot[6]*ww; + return; + } + + q1.x = 0.0; + ww = 0.5*(1.0 - rot[8]); + if (!((ww < 0 ? -ww : ww) < 1.0e-10)) { + q1.y = Math.sqrt(ww); + q1.z = rot[7]/(2.0*q1.y); + return; + } + + q1.y = 0.0; + q1.z = 1.0; + } + + /** + * Places the values of this transform into the double precision + * matrix argument. + * @param matrix the double precision matrix + */ + public final void get(Matrix4d matrix) { + matrix.m00 = mat[0]; + matrix.m01 = mat[1]; + matrix.m02 = mat[2]; + matrix.m03 = mat[3]; + matrix.m10 = mat[4]; + matrix.m11 = mat[5]; + matrix.m12 = mat[6]; + matrix.m13 = mat[7]; + matrix.m20 = mat[8]; + matrix.m21 = mat[9]; + matrix.m22 = mat[10]; + matrix.m23 = mat[11]; + matrix.m30 = mat[12]; + matrix.m31 = mat[13]; + matrix.m32 = mat[14]; + matrix.m33 = mat[15]; + } + + /** + * Places the values of this transform into the single precision matrix + * argument. + * @param matrix the single precision matrix + */ + public final void get(Matrix4f matrix) { + matrix.m00 = (float) mat[0]; + matrix.m01 = (float) mat[1]; + matrix.m02 = (float) mat[2]; + matrix.m03 = (float) mat[3]; + matrix.m10 = (float) mat[4]; + matrix.m11 = (float) mat[5]; + matrix.m12 = (float) mat[6]; + matrix.m13 = (float) mat[7]; + matrix.m20 = (float) mat[8]; + matrix.m21 = (float) mat[9]; + matrix.m22 = (float) mat[10]; + matrix.m23 = (float) mat[11]; + matrix.m30 = (float) mat[12]; + matrix.m31 = (float) mat[13]; + matrix.m32 = (float) mat[14]; + matrix.m33 = (float) mat[15]; + } + + /** + * Places the quaternion equivalent of the normalized rotational + * component of this transform into the quaternion parameter; + * places the translational component into the Vector parameter. + * @param q1 the quaternion representing the rotation + * @param t1 the translation component + * @return the scale component of this transform + */ + public final double get(Quat4d q1, Vector3d t1) { + + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } else if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + + t1.x = mat[3]; + t1.y = mat[7]; + t1.z = mat[11]; + + double maxScale = max3(scales); + + double ww = 0.25*(1.0 + rot[0] + rot[4] + rot[8]); + if (!((ww < 0 ? -ww : ww) < EPSILON)) { + q1.w = Math.sqrt(ww); + ww = 0.25/q1.w; + q1.x = (rot[7] - rot[5])*ww; + q1.y = (rot[2] - rot[6])*ww; + q1.z = (rot[3] - rot[1])*ww; + return maxScale; + } + + q1.w = 0.0; + ww = -0.5*(rot[4] + rot[8]); + if (!((ww < 0 ? -ww : ww) < EPSILON)) { + q1.x = Math.sqrt(ww); + ww = 0.5/q1.x; + q1.y = rot[3]*ww; + q1.z = rot[6]*ww; + return maxScale; + } + + q1.x = 0.0; + ww = 0.5*(1.0 - rot[8]); + if (!((ww < 0 ? -ww : ww) < EPSILON)) { + q1.y = Math.sqrt(ww); + q1.z = rot[7]/(2.0*q1.y); + return maxScale; + } + + q1.y = 0.0; + q1.z = 1.0; + return maxScale; + } + + + /** + * Places the quaternion equivalent of the normalized rotational + * component of this transform into the quaternion parameter; + * places the translational component into the Vector parameter. + * @param q1 the quaternion representing the rotation + * @param t1 the translation component + * @return the scale component of this transform + */ + public final float get(Quat4f q1, Vector3f t1) { + + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } else if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + + double maxScale = max3(scales); + t1.x = (float)mat[3]; + t1.y = (float)mat[7]; + t1.z = (float)mat[11]; + + double ww = 0.25*(1.0 + rot[0] + rot[4] + rot[8]); + if (!((ww < 0 ? -ww : ww) < EPSILON)) { + q1.w = (float)Math.sqrt(ww); + ww = 0.25/q1.w; + q1.x = (float)((rot[7] - rot[5])*ww); + q1.y = (float)((rot[2] - rot[6])*ww); + q1.z = (float)((rot[3] - rot[1])*ww); + return (float) maxScale; + } + + q1.w = 0.0f; + ww = -0.5*(rot[4] + rot[8]); + if (!((ww < 0 ? -ww : ww) < EPSILON)) { + q1.x = (float)Math.sqrt(ww); + ww = 0.5/q1.x; + q1.y = (float)(rot[3]*ww); + q1.z = (float)(rot[6]*ww); + return (float) maxScale; + } + + q1.x = 0.0f; + ww = 0.5*(1.0 - rot[8]); + if (!((ww < 0? -ww : ww) < EPSILON)) { + q1.y = (float)Math.sqrt(ww); + q1.z = (float)(rot[7]/(2.0*q1.y)); + return (float) maxScale; + } + + q1.y = 0.0f; + q1.z = 1.0f; + return (float) maxScale; + } + + /** + * Places the quaternion equivalent of the normalized rotational + * component of this transform into the quaternion parameter; + * places the translational component into the Vector parameter. + * @param q1 the quaternion representing the rotation + * @param t1 the translation component + * @return the scale component of this transform + */ + public final double get(Quat4f q1, Vector3d t1) { + + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } else if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + + double maxScale = max3(scales); + + t1.x = mat[3]; + t1.y = mat[7]; + t1.z = mat[11]; + + double ww = 0.25*(1.0 + rot[0] + rot[4] + rot[8]); + if (!(( ww < 0 ? -ww : ww) < EPSILON)) { + q1.w = (float)Math.sqrt(ww); + ww = 0.25/q1.w; + q1.x = (float)((rot[7] - rot[5])*ww); + q1.y = (float)((rot[2] - rot[6])*ww); + q1.z = (float)((rot[3] - rot[1])*ww); + return maxScale; + } + + q1.w = 0.0f; + ww = -0.5*(rot[4] + rot[8]); + if (!(( ww < 0 ? -ww : ww) < EPSILON)) { + q1.x = (float)Math.sqrt(ww); + ww = 0.5/q1.x; + q1.y = (float)(rot[3]*ww); + q1.z = (float)(rot[6]*ww); + return maxScale; + } + + q1.x = 0.0f; + ww = 0.5*(1.0 - rot[8]); + if (!(( ww < 0 ? -ww : ww) < EPSILON)) { + q1.y = (float)Math.sqrt(ww); + q1.z = (float)(rot[7]/(2.0*q1.y)); + return maxScale; + } + + q1.y = 0.0f; + q1.z = 1.0f; + return maxScale; + } + + /** + * Places the normalized rotational component of this transform + * into the matrix parameter; place the translational component + * into the vector parameter. + * @param m1 the normalized matrix representing the rotation + * @param t1 the translation component + * @return the scale component of this transform + */ + public final double get(Matrix3d m1, Vector3d t1) { + + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } else if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + + t1.x = mat[3]; + t1.y = mat[7]; + t1.z = mat[11]; + + m1.m00 = rot[0]; + m1.m01 = rot[1]; + m1.m02 = rot[2]; + + m1.m10 = rot[3]; + m1.m11 = rot[4]; + m1.m12 = rot[5]; + + m1.m20 = rot[6]; + m1.m21 = rot[7]; + m1.m22 = rot[8]; + + return max3(scales); + } + + + /** + * Places the normalized rotational component of this transform + * into the matrix parameter; place the translational component + * into the vector parameter. + * @param m1 the normalized matrix representing the rotation + * @param t1 the translation component + * @return the scale component of this transform + */ + public final float get(Matrix3f m1, Vector3f t1) { + + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } else if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + + t1.x = (float)mat[3]; + t1.y = (float)mat[7]; + t1.z = (float)mat[11]; + + m1.m00 = (float)rot[0]; + m1.m01 = (float)rot[1]; + m1.m02 = (float)rot[2]; + + m1.m10 = (float)rot[3]; + m1.m11 = (float)rot[4]; + m1.m12 = (float)rot[5]; + + m1.m20 = (float)rot[6]; + m1.m21 = (float)rot[7]; + m1.m22 = (float)rot[8]; + + return (float) max3(scales); + } + + + /** + * Places the normalized rotational component of this transform + * into the matrix parameter; place the translational component + * into the vector parameter. + * @param m1 the normalized matrix representing the rotation + * @param t1 the translation component + * @return the scale component of this transform + */ + public final double get(Matrix3f m1, Vector3d t1) { + if ((dirtyBits & ROTATION_BIT) != 0) { + computeScaleRotation(false); + } else if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + + t1.x = mat[3]; + t1.y = mat[7]; + t1.z = mat[11]; + + m1.m00 = (float)rot[0]; + m1.m01 = (float)rot[1]; + m1.m02 = (float)rot[2]; + + m1.m10 = (float)rot[3]; + m1.m11 = (float)rot[4]; + m1.m12 = (float)rot[5]; + + m1.m20 = (float)rot[6]; + m1.m21 = (float)rot[7]; + m1.m22 = (float)rot[8]; + + return max3(scales); + } + + + /** + * Returns the uniform scale factor of this matrix. + * If the matrix has non-uniform scale factors, the largest of the + * x, y, and z scale factors will be returned. + * @return the scale factor of this matrix + */ + public final double getScale() { + if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + return max3(scales); + } + + + /** + * Gets the possibly non-uniform scale components of the current + * transform and places them into the scale vector. + * @param scale the vector into which the x,y,z scale values will be placed + */ + public final void getScale(Vector3d scale) { + if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + scale.x = scales[0]; + scale.y = scales[1]; + scale.z = scales[2]; + } + + + /** + * Retrieves the translational components of this transform. + * @param trans the vector that will receive the translational component + */ + public final void get(Vector3f trans) { + trans.x = (float)mat[3]; + trans.y = (float)mat[7]; + trans.z = (float)mat[11]; + } + + + /** + * Retrieves the translational components of this transform. + * @param trans the vector that will receive the translational component + */ + public final void get(Vector3d trans) { + trans.x = mat[3]; + trans.y = mat[7]; + trans.z = mat[11]; + } + + /** + * Sets the value of this transform to the inverse of the passed + * Transform3D parameter. This method uses the transform type + * to determine the optimal algorithm for inverting transform t1. + * @param t1 the transform to be inverted + * @exception SingularMatrixException thrown if transform t1 is + * not invertible + */ + public final void invert(Transform3D t1) { + if (t1 == this) { + invert(); + } else if (t1.isAffine()) { + // We can't use invertOrtho() because of numerical + // instability unless we set tolerance of ortho test to 0 + invertAffine(t1); + } else { + invertGeneral(t1); + } + } + + /** + * Inverts this transform in place. This method uses the transform + * type to determine the optimal algorithm for inverting this transform. + * @exception SingularMatrixException thrown if this transform is + * not invertible + */ + public final void invert() { + if (isAffine()) { + invertAffine(); + } else { + invertGeneral(this); + } + } + + /** + * Congruent invert routine. + * + * if uniform scale s + * + * [R | t] => [R^T/s*s | -R^T * t/s*s] + * + */ + + /* + final void invertOrtho() { + double tmp, s1, s2, s3; + + // do not force classifyRigid() + if (((dirtyBits & CONGRUENT_BIT) == 0) && + ((type & CONGRUENT) != 0)) { + s1 = mat[0]*mat[0] + mat[4]*mat[4] + mat[8]*mat[8]; + if (s1 == 0) { + throw new SingularMatrixException(J3dI18N.getString("Transform3D1")); + } + s1 = s2 = s3 = 1/s1; + dirtyBits |= ROTSCALESVD_DIRTY; + } else { + // non-uniform scale matrix + s1 = mat[0]*mat[0] + mat[4]*mat[4] + mat[8]*mat[8]; + s2 = mat[1]*mat[1] + mat[5]*mat[5] + mat[9]*mat[9]; + s3 = mat[2]*mat[2] + mat[6]*mat[6] + mat[10]*mat[10]; + if ((s1 == 0) || (s2 == 0) || (s3 == 0)) { + throw new SingularMatrixException(J3dI18N.getString("Transform3D1")); + } + s1 = 1/s1; + s2 = 1/s2; + s3 = 1/s3; + dirtyBits |= ROTSCALESVD_DIRTY | ORTHO_BIT | CONGRUENT_BIT + | RIGID_BIT | CLASSIFY_BIT; + } + // multiple by 1/s will cause loss in numerical value + tmp = mat[1]; + mat[1] = mat[4]*s1; + mat[4] = tmp*s2; + tmp = mat[2]; + mat[2] = mat[8]*s1; + mat[8] = tmp*s3; + tmp = mat[6]; + mat[6] = mat[9]*s2; + mat[9] = tmp*s3; + mat[0] *= s1; + mat[5] *= s2; + mat[10] *= s3; + + tmp = mat[3]; + s1 = mat[7]; + mat[3] = -(tmp * mat[0] + s1 * mat[1] + mat[11] * mat[2]); + mat[7] = -(tmp * mat[4] + s1 * mat[5] + mat[11] * mat[6]); + mat[11]= -(tmp * mat[8] + s1 * mat[9] + mat[11] * mat[10]); + mat[12] = mat[13] = mat[14] = 0.0; + mat[15] = 1.0; + } + */ + + /** + * Orthogonal matrix invert routine. + * Inverts t1 and places the result in "this". + */ + /* + final void invertOrtho(Transform3D t1) { + double s1, s2, s3; + + // do not force classifyRigid() + if (((t1.dirtyBits & CONGRUENT_BIT) == 0) && + ((t1.type & CONGRUENT) != 0)) { + s1 = t1.mat[0]*t1.mat[0] + t1.mat[4]*t1.mat[4] + + t1.mat[8]*t1.mat[8]; + if (s1 == 0) { + throw new SingularMatrixException(J3dI18N.getString("Transform3D1")); + } + s1 = s2 = s3 = 1/s1; + dirtyBits = t1.dirtyBits | ROTSCALESVD_DIRTY; + } else { + // non-uniform scale matrix + s1 = t1.mat[0]*t1.mat[0] + t1.mat[4]*t1.mat[4] + + t1.mat[8]*t1.mat[8]; + s2 = t1.mat[1]*t1.mat[1] + t1.mat[5]*t1.mat[5] + + t1.mat[9]*t1.mat[9]; + s3 = t1.mat[2]*t1.mat[2] + t1.mat[6]*t1.mat[6] + + t1.mat[10]*t1.mat[10]; + + if ((s1 == 0) || (s2 == 0) || (s3 == 0)) { + throw new SingularMatrixException(J3dI18N.getString("Transform3D1")); + } + s1 = 1/s1; + s2 = 1/s2; + s3 = 1/s3; + dirtyBits = t1.dirtyBits | ROTSCALESVD_DIRTY | ORTHO_BIT | + CONGRUENT_BIT | RIGID_BIT; + } + + mat[0] = t1.mat[0]*s1; + mat[1] = t1.mat[4]*s1; + mat[2] = t1.mat[8]*s1; + mat[4] = t1.mat[1]*s2; + mat[5] = t1.mat[5]*s2; + mat[6] = t1.mat[9]*s2; + mat[8] = t1.mat[2]*s3; + mat[9] = t1.mat[6]*s3; + mat[10] = t1.mat[10]*s3; + + mat[3] = -(t1.mat[3] * mat[0] + t1.mat[7] * mat[1] + + t1.mat[11] * mat[2]); + mat[7] = -(t1.mat[3] * mat[4] + t1.mat[7] * mat[5] + + t1.mat[11] * mat[6]); + mat[11]= -(t1.mat[3] * mat[8] + t1.mat[7] * mat[9] + + t1.mat[11] * mat[10]); + mat[12] = mat[13] = mat[14] = 0.0; + mat[15] = 1.0; + type = t1.type; + } + */ + + /** + * Affine invert routine. Inverts t1 and places the result in "this". + */ + final void invertAffine(Transform3D t1) { + double determinant = t1.affineDeterminant(); + + if (determinant == 0.0) + throw new SingularMatrixException(J3dI18N.getString("Transform3D1")); + + + double s = (t1.mat[0]*t1.mat[0] + t1.mat[1]*t1.mat[1] + + t1.mat[2]*t1.mat[2] + t1.mat[3]*t1.mat[3])* + (t1.mat[4]*t1.mat[4] + t1.mat[5]*t1.mat[5] + + t1.mat[6]*t1.mat[6] + t1.mat[7]*t1.mat[7])* + (t1.mat[8]*t1.mat[8] + t1.mat[9]*t1.mat[9] + + t1.mat[10]*t1.mat[10] + t1.mat[11]*t1.mat[11]); + + if ((determinant*determinant) < (EPS * s)) { + // using invertGeneral is numerically more stable for + //this case see bug 4227733 + invertGeneral(t1); + return; + } + s = 1.0 / determinant; + + mat[0] = (t1.mat[5]*t1.mat[10] - t1.mat[9]*t1.mat[6]) * s; + mat[1] = -(t1.mat[1]*t1.mat[10] - t1.mat[9]*t1.mat[2]) * s; + mat[2] = (t1.mat[1]*t1.mat[6] - t1.mat[5]*t1.mat[2]) * s; + mat[4] = -(t1.mat[4]*t1.mat[10] - t1.mat[8]*t1.mat[6]) * s; + mat[5] = (t1.mat[0]*t1.mat[10] - t1.mat[8]*t1.mat[2]) * s; + mat[6] = -(t1.mat[0]*t1.mat[6] - t1.mat[4]*t1.mat[2]) * s; + mat[8] = (t1.mat[4]*t1.mat[9] - t1.mat[8]*t1.mat[5]) * s; + mat[9] = -(t1.mat[0]*t1.mat[9] - t1.mat[8]*t1.mat[1]) * s; + mat[10]= (t1.mat[0]*t1.mat[5] - t1.mat[4]*t1.mat[1]) * s; + mat[3] = -(t1.mat[3] * mat[0] + t1.mat[7] * mat[1] + + t1.mat[11] * mat[2]); + mat[7] = -(t1.mat[3] * mat[4] + t1.mat[7] * mat[5] + + t1.mat[11] * mat[6]); + mat[11]= -(t1.mat[3] * mat[8] + t1.mat[7] * mat[9] + + t1.mat[11] * mat[10]); + + mat[12] = mat[13] = mat[14] = 0.0; + mat[15] = 1.0; + + dirtyBits = t1.dirtyBits | ROTSCALESVD_DIRTY | CLASSIFY_BIT | ORTHO_BIT; + type = t1.type; + } + + /** + * Affine invert routine. Inverts "this" matrix in place. + */ + final void invertAffine() { + double determinant = affineDeterminant(); + + if (determinant == 0.0) + throw new SingularMatrixException(J3dI18N.getString("Transform3D1")); + + double s = (mat[0]*mat[0] + mat[1]*mat[1] + + mat[2]*mat[2] + mat[3]*mat[3])* + (mat[4]*mat[4] + mat[5]*mat[5] + + mat[6]*mat[6] + mat[7]*mat[7])* + (mat[8]*mat[8] + mat[9]*mat[9] + + mat[10]*mat[10] + mat[11]*mat[11]); + + if ((determinant*determinant) < (EPS * s)) { + invertGeneral(this); + return; + } + s = 1.0 / determinant; + double tmp0 = (mat[5]*mat[10] - mat[9]*mat[6]) * s; + double tmp1 = -(mat[1]*mat[10] - mat[9]*mat[2]) * s; + double tmp2 = (mat[1]*mat[6] - mat[5]*mat[2]) * s; + double tmp4 = -(mat[4]*mat[10] - mat[8]*mat[6]) * s; + double tmp5 = (mat[0]*mat[10] - mat[8]*mat[2]) * s; + double tmp6 = -(mat[0]*mat[6] - mat[4]*mat[2]) * s; + double tmp8 = (mat[4]*mat[9] - mat[8]*mat[5]) * s; + double tmp9 = -(mat[0]*mat[9] - mat[8]*mat[1]) * s; + double tmp10= (mat[0]*mat[5] - mat[4]*mat[1]) * s; + double tmp3 = -(mat[3] * tmp0 + mat[7] * tmp1 + mat[11] * tmp2); + double tmp7 = -(mat[3] * tmp4 + mat[7] * tmp5 + mat[11] * tmp6); + mat[11]= -(mat[3] * tmp8 + mat[7] * tmp9 + mat[11] * tmp10); + + mat[0]=tmp0; mat[1]=tmp1; mat[2]=tmp2; mat[3]=tmp3; + mat[4]=tmp4; mat[5]=tmp5; mat[6]=tmp6; mat[7]=tmp7; + mat[8]=tmp8; mat[9]=tmp9; mat[10]=tmp10; + mat[12] = mat[13] = mat[14] = 0.0; + mat[15] = 1.0; + dirtyBits |= ROTSCALESVD_DIRTY | CLASSIFY_BIT | ORTHO_BIT; + } + + /** + * General invert routine. Inverts t1 and places the result in "this". + * Note that this routine handles both the "this" version and the + * non-"this" version. + * + * Also note that since this routine is slow anyway, we won't worry + * about allocating a little bit of garbage. + */ + final void invertGeneral(Transform3D t1) { + double tmp[] = new double[16]; + int row_perm[] = new int[4]; + int i, r, c; + + // Use LU decomposition and backsubstitution code specifically + // for floating-point 4x4 matrices. + + // Copy source matrix to tmp + System.arraycopy(t1.mat, 0, tmp, 0, tmp.length); + + // Calculate LU decomposition: Is the matrix singular? + if (!luDecomposition(tmp, row_perm)) { + // Matrix has no inverse + throw new SingularMatrixException(J3dI18N.getString("Transform3D1")); + } + + // Perform back substitution on the identity matrix + // luDecomposition will set rot[] & scales[] for use + // in luBacksubstituation + mat[0] = 1.0; mat[1] = 0.0; mat[2] = 0.0; mat[3] = 0.0; + mat[4] = 0.0; mat[5] = 1.0; mat[6] = 0.0; mat[7] = 0.0; + mat[8] = 0.0; mat[9] = 0.0; mat[10] = 1.0; mat[11] = 0.0; + mat[12] = 0.0; mat[13] = 0.0; mat[14] = 0.0; mat[15] = 1.0; + luBacksubstitution(tmp, row_perm, this.mat); + + type = 0; + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + } + + + /** + * Given a 4x4 array "matrix0", this function replaces it with the + * LU decomposition of a row-wise permutation of itself. The input + * parameters are "matrix0" and "dimen". The array "matrix0" is also + * an output parameter. The vector "row_perm[4]" is an output + * parameter that contains the row permutations resulting from partial + * pivoting. The output parameter "even_row_xchg" is 1 when the + * number of row exchanges is even, or -1 otherwise. Assumes data + * type is always double. + * + * This function is similar to luDecomposition, except that it + * is tuned specifically for 4x4 matrices. + * + * @return true if the matrix is nonsingular, or false otherwise. + */ + // + // Reference: Press, Flannery, Teukolsky, Vetterling, + // _Numerical_Recipes_in_C_, Cambridge University Press, + // 1988, pp 40-45. + // + static boolean luDecomposition(double[] matrix0, + int[] row_perm) { + + // Can't re-use this temporary since the method is static. + double row_scale[] = new double[4]; + + // Determine implicit scaling information by looping over rows + { + int i, j; + int ptr, rs; + double big, temp; + + ptr = 0; + rs = 0; + + // For each row ... + i = 4; + while (i-- != 0) { + big = 0.0; + + // For each column, find the largest element in the row + j = 4; + while (j-- != 0) { + temp = matrix0[ptr++]; + temp = Math.abs(temp); + if (temp > big) { + big = temp; + } + } + + // Is the matrix singular? + if (big == 0.0) { + return false; + } + row_scale[rs++] = 1.0 / big; + } + } + + { + int j; + int mtx; + + mtx = 0; + + // For all columns, execute Crout's method + for (j = 0; j < 4; j++) { + int i, imax, k; + int target, p1, p2; + double sum, big, temp; + + // Determine elements of upper diagonal matrix U + for (i = 0; i < j; i++) { + target = mtx + (4*i) + j; + sum = matrix0[target]; + k = i; + p1 = mtx + (4*i); + p2 = mtx + j; + while (k-- != 0) { + sum -= matrix0[p1] * matrix0[p2]; + p1++; + p2 += 4; + } + matrix0[target] = sum; + } + + // Search for largest pivot element and calculate + // intermediate elements of lower diagonal matrix L. + big = 0.0; + imax = -1; + for (i = j; i < 4; i++) { + target = mtx + (4*i) + j; + sum = matrix0[target]; + k = j; + p1 = mtx + (4*i); + p2 = mtx + j; + while (k-- != 0) { + sum -= matrix0[p1] * matrix0[p2]; + p1++; + p2 += 4; + } + matrix0[target] = sum; + + // Is this the best pivot so far? + if ((temp = row_scale[i] * Math.abs(sum)) >= big) { + big = temp; + imax = i; + } + } + + if (imax < 0) { + return false; + } + + // Is a row exchange necessary? + if (j != imax) { + // Yes: exchange rows + k = 4; + p1 = mtx + (4*imax); + p2 = mtx + (4*j); + while (k-- != 0) { + temp = matrix0[p1]; + matrix0[p1++] = matrix0[p2]; + matrix0[p2++] = temp; + } + + // Record change in scale factor + row_scale[imax] = row_scale[j]; + } + + // Record row permutation + row_perm[j] = imax; + + // Is the matrix singular + if (matrix0[(mtx + (4*j) + j)] == 0.0) { + return false; + } + + // Divide elements of lower diagonal matrix L by pivot + if (j != (4-1)) { + temp = 1.0 / (matrix0[(mtx + (4*j) + j)]); + target = mtx + (4*(j+1)) + j; + i = 3 - j; + while (i-- != 0) { + matrix0[target] *= temp; + target += 4; + } + } + } + } + + return true; + } + + + /** + * Solves a set of linear equations. The input parameters "matrix1", + * and "row_perm" come from luDecompostionD4x4 and do not change + * here. The parameter "matrix2" is a set of column vectors assembled + * into a 4x4 matrix of floating-point values. The procedure takes each + * column of "matrix2" in turn and treats it as the right-hand side of the + * matrix equation Ax = LUx = b. The solution vector replaces the + * original column of the matrix. + * + * If "matrix2" is the identity matrix, the procedure replaces its contents + * with the inverse of the matrix from which "matrix1" was originally + * derived. + */ + // + // Reference: Press, Flannery, Teukolsky, Vetterling, + // _Numerical_Recipes_in_C_, Cambridge University Press, + // 1988, pp 44-45. + // + static void luBacksubstitution(double[] matrix1, + int[] row_perm, + double[] matrix2) { + + int i, ii, ip, j, k; + int rp; + int cv, rv; + + // rp = row_perm; + rp = 0; + + // For each column vector of matrix2 ... + for (k = 0; k < 4; k++) { + // cv = &(matrix2[0][k]); + cv = k; + ii = -1; + + // Forward substitution + for (i = 0; i < 4; i++) { + double sum; + + ip = row_perm[rp+i]; + sum = matrix2[cv+4*ip]; + matrix2[cv+4*ip] = matrix2[cv+4*i]; + if (ii >= 0) { + // rv = &(matrix1[i][0]); + rv = i*4; + for (j = ii; j <= i-1; j++) { + sum -= matrix1[rv+j] * matrix2[cv+4*j]; + } + } + else if (sum != 0.0) { + ii = i; + } + matrix2[cv+4*i] = sum; + } + + // Backsubstitution + // rv = &(matrix1[3][0]); + rv = 3*4; + matrix2[cv+4*3] /= matrix1[rv+3]; + + rv -= 4; + matrix2[cv+4*2] = (matrix2[cv+4*2] - + matrix1[rv+3] * matrix2[cv+4*3]) / matrix1[rv+2]; + + rv -= 4; + matrix2[cv+4*1] = (matrix2[cv+4*1] - + matrix1[rv+2] * matrix2[cv+4*2] - + matrix1[rv+3] * matrix2[cv+4*3]) / matrix1[rv+1]; + + rv -= 4; + matrix2[cv+4*0] = (matrix2[cv+4*0] - + matrix1[rv+1] * matrix2[cv+4*1] - + matrix1[rv+2] * matrix2[cv+4*2] - + matrix1[rv+3] * matrix2[cv+4*3]) / matrix1[rv+0]; + } + } + + // given that this matrix is affine + final double affineDeterminant() { + return mat[0]*(mat[5]*mat[10] - mat[6]*mat[9]) - + mat[1]*(mat[4]*mat[10] - mat[6]*mat[8]) + + mat[2]*(mat[4]*mat[ 9] - mat[5]*mat[8]); + } + + /** + * Calculates and returns the determinant of this transform. + * @return the double precision determinant + */ + public final double determinant() { + + if (isAffine()) { + return mat[0]*(mat[5]*mat[10] - mat[6]*mat[9]) - + mat[1]*(mat[4]*mat[10] - mat[6]*mat[8]) + + mat[2]*(mat[4]*mat[ 9] - mat[5]*mat[8]); + } + // cofactor exapainsion along first row + return mat[0]*(mat[5]*(mat[10]*mat[15] - mat[11]*mat[14]) - + mat[6]*(mat[ 9]*mat[15] - mat[11]*mat[13]) + + mat[7]*(mat[ 9]*mat[14] - mat[10]*mat[13])) - + mat[1]*(mat[4]*(mat[10]*mat[15] - mat[11]*mat[14]) - + mat[6]*(mat[ 8]*mat[15] - mat[11]*mat[12]) + + mat[7]*(mat[ 8]*mat[14] - mat[10]*mat[12])) + + mat[2]*(mat[4]*(mat[ 9]*mat[15] - mat[11]*mat[13]) - + mat[5]*(mat[ 8]*mat[15] - mat[11]*mat[12]) + + mat[7]*(mat[ 8]*mat[13] - mat[ 9]*mat[12])) - + mat[3]*(mat[4]*(mat[ 9]*mat[14] - mat[10]*mat[13]) - + mat[5]*(mat[ 8]*mat[14] - mat[10]*mat[12]) + + mat[6]*(mat[ 8]*mat[13] - mat[ 9]*mat[12])); + } + + /** + * Sets the value of this transform to a uniform scale; all of + * the matrix values are modified. + * @param scale the scale factor for the transform + */ + public final void set(double scale) { + setScaleTranslation(0, 0, 0, scale); + } + + + /** + * Sets the value of this transform to a scale and translation + * matrix; the scale is not applied to the translation and all + * of the matrix values are modified. + * @param scale the scale factor for the transform + * @param v1 the translation amount + */ + public final void set(double scale, Vector3d v1) { + setScaleTranslation(v1.x, v1.y, v1.z, scale); + } + + + /** + * Sets the value of this transform to a scale and translation + * matrix; the scale is not applied to the translation and all + * of the matrix values are modified. + * @param scale the scale factor for the transform + * @param v1 the translation amount + */ + public final void set(float scale, Vector3f v1) { + setScaleTranslation(v1.x, v1.y, v1.z, scale); + } + + /** + * Sets the value of this transform to a scale and translation matrix; + * the translation is scaled by the scale factor and all of the + * matrix values are modified. + * @param v1 the translation amount + * @param scale the scale factor for the transform AND the translation + */ + public final void set(Vector3d v1, double scale) { + setScaleTranslation(v1.x*scale, v1.y*scale, v1.z*scale, scale); + } + + /** + * Sets the value of this transform to a scale and translation matrix; + * the translation is scaled by the scale factor and all of the + * matrix values are modified. + * @param v1 the translation amount + * @param scale the scale factor for the transform AND the translation + */ + public final void set(Vector3f v1, float scale) { + setScaleTranslation(v1.x*scale, v1.y*scale, v1.z*scale, scale); + } + + private final void setScaleTranslation(double x, double y, + double z, double scale) { + mat[0] = scale; + mat[1] = 0.0; + mat[2] = 0.0; + mat[3] = x; + mat[4] = 0.0; + mat[5] = scale; + mat[6] = 0.0; + mat[7] = y; + mat[8] = 0.0; + mat[9] = 0.0; + mat[10] = scale; + mat[11] = z; + mat[12] = 0.0; + mat[13] = 0.0; + mat[14] = 0.0; + mat[15] = 1.0; + + if(scales == null) + scales = new double[3]; + + scales[0] = scales[1] = scales[2] = scale; + + type = AFFINE | CONGRUENT | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTATION_BIT | RIGID_BIT; + } + + + + /** + * Multiplies each element of this transform by a scalar. + * @param scalar the scalar multiplier + */ + public final void mul(double scalar) { + for (int i=0 ; i<16 ; i++) { + mat[i] *= scalar; + } + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + } + + /** + * Multiplies each element of transform t1 by a scalar and places + * the result into this. Transform t1 is not modified. + * @param scalar the scalar multiplier + * @param t1 the original transform + */ + public final void mul(double scalar, Transform3D t1) { + for (int i=0 ; i<16 ; i++) { + mat[i] = t1.mat[i] * scalar; + } + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + + } + + + /** + * Sets the value of this transform to the result of multiplying itself + * with transform t1 (this = this * t1). + * @param t1 the other transform + */ + public final void mul(Transform3D t1) { + double tmp0, tmp1, tmp2, tmp3; + double tmp4, tmp5, tmp6, tmp7; + double tmp8, tmp9, tmp10, tmp11; + boolean aff = false; + + if (t1.isAffine()) { + tmp0 = mat[0]*t1.mat[0] + mat[1]*t1.mat[4] + mat[2]*t1.mat[8]; + tmp1 = mat[0]*t1.mat[1] + mat[1]*t1.mat[5] + mat[2]*t1.mat[9]; + tmp2 = mat[0]*t1.mat[2] + mat[1]*t1.mat[6] + mat[2]*t1.mat[10]; + tmp3 = mat[0]*t1.mat[3] + mat[1]*t1.mat[7] + mat[2]*t1.mat[11] + mat[3]; + tmp4 = mat[4]*t1.mat[0] + mat[5]*t1.mat[4] + mat[6]*t1.mat[8]; + tmp5 = mat[4]*t1.mat[1] + mat[5]*t1.mat[5] + mat[6]*t1.mat[9]; + tmp6 = mat[4]*t1.mat[2] + mat[5]*t1.mat[6] + mat[6]*t1.mat[10]; + tmp7 = mat[4]*t1.mat[3] + mat[5]*t1.mat[7] + mat[6]*t1.mat[11] + mat[7]; + tmp8 = mat[8]*t1.mat[0] + mat[9]*t1.mat[4] + mat[10]*t1.mat[8]; + tmp9 = mat[8]*t1.mat[1] + mat[9]*t1.mat[5] + mat[10]*t1.mat[9]; + tmp10 = mat[8]*t1.mat[2] + mat[9]*t1.mat[6] + mat[10]*t1.mat[10]; + tmp11 = mat[8]*t1.mat[3] + mat[9]*t1.mat[7] + mat[10]*t1.mat[11] + mat[11]; + if (isAffine()) { + mat[12] = mat[13] = mat[14] = 0; + mat[15] = 1; + aff = true; + } else { + double tmp12 = mat[12]*t1.mat[0] + mat[13]*t1.mat[4] + + mat[14]*t1.mat[8]; + double tmp13 = mat[12]*t1.mat[1] + mat[13]*t1.mat[5] + + mat[14]*t1.mat[9]; + double tmp14 = mat[12]*t1.mat[2] + mat[13]*t1.mat[6] + + mat[14]*t1.mat[10]; + double tmp15 = mat[12]*t1.mat[3] + mat[13]*t1.mat[7] + + mat[14]*t1.mat[11] + mat[15]; + mat[12] = tmp12; + mat[13] = tmp13; + mat[14] = tmp14; + mat[15] = tmp15; + } + } else { + tmp0 = mat[0]*t1.mat[0] + mat[1]*t1.mat[4] + mat[2]*t1.mat[8] + + mat[3]*t1.mat[12]; + tmp1 = mat[0]*t1.mat[1] + mat[1]*t1.mat[5] + mat[2]*t1.mat[9] + + mat[3]*t1.mat[13]; + tmp2 = mat[0]*t1.mat[2] + mat[1]*t1.mat[6] + mat[2]*t1.mat[10] + + mat[3]*t1.mat[14]; + tmp3 = mat[0]*t1.mat[3] + mat[1]*t1.mat[7] + mat[2]*t1.mat[11] + + mat[3]*t1.mat[15]; + tmp4 = mat[4]*t1.mat[0] + mat[5]*t1.mat[4] + mat[6]*t1.mat[8] + + mat[7]*t1.mat[12]; + tmp5 = mat[4]*t1.mat[1] + mat[5]*t1.mat[5] + mat[6]*t1.mat[9] + + mat[7]*t1.mat[13]; + tmp6 = mat[4]*t1.mat[2] + mat[5]*t1.mat[6] + mat[6]*t1.mat[10] + + mat[7]*t1.mat[14]; + tmp7 = mat[4]*t1.mat[3] + mat[5]*t1.mat[7] + mat[6]*t1.mat[11] + + mat[7]*t1.mat[15]; + tmp8 = mat[8]*t1.mat[0] + mat[9]*t1.mat[4] + mat[10]*t1.mat[8] + + mat[11]*t1.mat[12]; + tmp9 = mat[8]*t1.mat[1] + mat[9]*t1.mat[5] + mat[10]*t1.mat[9] + + mat[11]*t1.mat[13]; + tmp10 = mat[8]*t1.mat[2] + mat[9]*t1.mat[6] + + mat[10]*t1.mat[10]+ mat[11]*t1.mat[14]; + tmp11 = mat[8]*t1.mat[3] + mat[9]*t1.mat[7] + + mat[10]*t1.mat[11] + mat[11]*t1.mat[15]; + + if (isAffine()) { + mat[12] = t1.mat[12]; + mat[13] = t1.mat[13]; + mat[14] = t1.mat[14]; + mat[15] = t1.mat[15]; + } else { + double tmp12 = mat[12]*t1.mat[0] + mat[13]*t1.mat[4] + + mat[14]*t1.mat[8] + mat[15]*t1.mat[12]; + double tmp13 = mat[12]*t1.mat[1] + mat[13]*t1.mat[5] + + mat[14]*t1.mat[9] + mat[15]*t1.mat[13]; + double tmp14 = mat[12]*t1.mat[2] + mat[13]*t1.mat[6] + + mat[14]*t1.mat[10] + mat[15]*t1.mat[14]; + double tmp15 = mat[12]*t1.mat[3] + mat[13]*t1.mat[7] + + mat[14]*t1.mat[11] + mat[15]*t1.mat[15]; + mat[12] = tmp12; + mat[13] = tmp13; + mat[14] = tmp14; + mat[15] = tmp15; + } + } + + mat[0] = tmp0; + mat[1] = tmp1; + mat[2] = tmp2; + mat[3] = tmp3; + mat[4] = tmp4; + mat[5] = tmp5; + mat[6] = tmp6; + mat[7] = tmp7; + mat[8] = tmp8; + mat[9] = tmp9; + mat[10] = tmp10; + mat[11] = tmp11; + + if (((dirtyBits & CONGRUENT_BIT) == 0) && + ((type & CONGRUENT) != 0) && + ((t1.dirtyBits & CONGRUENT_BIT) == 0) && + ((t1.type & CONGRUENT) != 0)) { + type &= t1.type; + dirtyBits |= t1.dirtyBits | CLASSIFY_BIT | + ROTSCALESVD_DIRTY | RIGID_BIT; + } else { + if (aff) { + dirtyBits = ORTHO_BIT | CONGRUENT_BIT | RIGID_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY; + } else { + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + } + } + + if (autoNormalize) { + normalize(); + } + + } + + /** + * Sets the value of this transform to the result of multiplying transform + * t1 by transform t2 (this = t1*t2). + * @param t1 the left transform + * @param t2 the right transform + */ + public final void mul(Transform3D t1, Transform3D t2) { + boolean aff = false; + if ((this != t1) && (this != t2)) { + if (t2.isAffine()) { + + mat[0] = t1.mat[0]*t2.mat[0] + t1.mat[1]*t2.mat[4] + t1.mat[2]*t2.mat[8]; + mat[1] = t1.mat[0]*t2.mat[1] + t1.mat[1]*t2.mat[5] + t1.mat[2]*t2.mat[9]; + mat[2] = t1.mat[0]*t2.mat[2] + t1.mat[1]*t2.mat[6] + t1.mat[2]*t2.mat[10]; + mat[3] = t1.mat[0]*t2.mat[3] + t1.mat[1]*t2.mat[7] + + t1.mat[2]*t2.mat[11] + t1.mat[3]; + mat[4] = t1.mat[4]*t2.mat[0] + t1.mat[5]*t2.mat[4] + t1.mat[6]*t2.mat[8]; + mat[5] = t1.mat[4]*t2.mat[1] + t1.mat[5]*t2.mat[5] + t1.mat[6]*t2.mat[9]; + mat[6] = t1.mat[4]*t2.mat[2] + t1.mat[5]*t2.mat[6] + t1.mat[6]*t2.mat[10]; + mat[7] = t1.mat[4]*t2.mat[3] + t1.mat[5]*t2.mat[7] + + t1.mat[6]*t2.mat[11] + t1.mat[7]; + mat[8] = t1.mat[8]*t2.mat[0] + t1.mat[9]*t2.mat[4] + t1.mat[10]*t2.mat[8]; + mat[9] = t1.mat[8]*t2.mat[1] + t1.mat[9]*t2.mat[5] + t1.mat[10]*t2.mat[9]; + mat[10] = t1.mat[8]*t2.mat[2] + t1.mat[9]*t2.mat[6] + t1.mat[10]*t2.mat[10]; + mat[11] = t1.mat[8]*t2.mat[3] + t1.mat[9]*t2.mat[7] + + t1.mat[10]*t2.mat[11] + t1.mat[11]; + if (t1.isAffine()) { + aff = true; + mat[12] = mat[13] = mat[14] = 0; + mat[15] = 1; + } else { + mat[12] = t1.mat[12]*t2.mat[0] + t1.mat[13]*t2.mat[4] + + t1.mat[14]*t2.mat[8]; + mat[13] = t1.mat[12]*t2.mat[1] + t1.mat[13]*t2.mat[5] + + t1.mat[14]*t2.mat[9]; + mat[14] = t1.mat[12]*t2.mat[2] + t1.mat[13]*t2.mat[6] + + t1.mat[14]*t2.mat[10]; + mat[15] = t1.mat[12]*t2.mat[3] + t1.mat[13]*t2.mat[7] + + t1.mat[14]*t2.mat[11] + t1.mat[15]; + } + } else { + mat[0] = t1.mat[0]*t2.mat[0] + t1.mat[1]*t2.mat[4] + + t1.mat[2]*t2.mat[8] + t1.mat[3]*t2.mat[12]; + mat[1] = t1.mat[0]*t2.mat[1] + t1.mat[1]*t2.mat[5] + + t1.mat[2]*t2.mat[9] + t1.mat[3]*t2.mat[13]; + mat[2] = t1.mat[0]*t2.mat[2] + t1.mat[1]*t2.mat[6] + + t1.mat[2]*t2.mat[10] + t1.mat[3]*t2.mat[14]; + mat[3] = t1.mat[0]*t2.mat[3] + t1.mat[1]*t2.mat[7] + + t1.mat[2]*t2.mat[11] + t1.mat[3]*t2.mat[15]; + mat[4] = t1.mat[4]*t2.mat[0] + t1.mat[5]*t2.mat[4] + + t1.mat[6]*t2.mat[8] + t1.mat[7]*t2.mat[12]; + mat[5] = t1.mat[4]*t2.mat[1] + t1.mat[5]*t2.mat[5] + + t1.mat[6]*t2.mat[9] + t1.mat[7]*t2.mat[13]; + mat[6] = t1.mat[4]*t2.mat[2] + t1.mat[5]*t2.mat[6] + + t1.mat[6]*t2.mat[10] + t1.mat[7]*t2.mat[14]; + mat[7] = t1.mat[4]*t2.mat[3] + t1.mat[5]*t2.mat[7] + + t1.mat[6]*t2.mat[11] + t1.mat[7]*t2.mat[15]; + mat[8] = t1.mat[8]*t2.mat[0] + t1.mat[9]*t2.mat[4] + + t1.mat[10]*t2.mat[8] + t1.mat[11]*t2.mat[12]; + mat[9] = t1.mat[8]*t2.mat[1] + t1.mat[9]*t2.mat[5] + + t1.mat[10]*t2.mat[9] + t1.mat[11]*t2.mat[13]; + mat[10] = t1.mat[8]*t2.mat[2] + t1.mat[9]*t2.mat[6] + + t1.mat[10]*t2.mat[10] + t1.mat[11]*t2.mat[14]; + mat[11] = t1.mat[8]*t2.mat[3] + t1.mat[9]*t2.mat[7] + + t1.mat[10]*t2.mat[11] + t1.mat[11]*t2.mat[15]; + if (t1.isAffine()) { + mat[12] = t2.mat[12]; + mat[13] = t2.mat[13]; + mat[14] = t2.mat[14]; + mat[15] = t2.mat[15]; + } else { + mat[12] = t1.mat[12]*t2.mat[0] + t1.mat[13]*t2.mat[4] + + t1.mat[14]*t2.mat[8] + t1.mat[15]*t2.mat[12]; + mat[13] = t1.mat[12]*t2.mat[1] + t1.mat[13]*t2.mat[5] + + t1.mat[14]*t2.mat[9] + t1.mat[15]*t2.mat[13]; + mat[14] = t1.mat[12]*t2.mat[2] + t1.mat[13]*t2.mat[6] + + t1.mat[14]*t2.mat[10] + t1.mat[15]*t2.mat[14]; + mat[15] = t1.mat[12]*t2.mat[3] + t1.mat[13]*t2.mat[7] + + t1.mat[14]*t2.mat[11] + t1.mat[15]*t2.mat[15]; + } + } + } else { + double tmp0, tmp1, tmp2, tmp3; + double tmp4, tmp5, tmp6, tmp7; + double tmp8, tmp9, tmp10, tmp11; + + if (t2.isAffine()) { + tmp0 = t1.mat[0]*t2.mat[0] + t1.mat[1]*t2.mat[4] + t1.mat[2]*t2.mat[8]; + tmp1 = t1.mat[0]*t2.mat[1] + t1.mat[1]*t2.mat[5] + t1.mat[2]*t2.mat[9]; + tmp2 = t1.mat[0]*t2.mat[2] + t1.mat[1]*t2.mat[6] + t1.mat[2]*t2.mat[10]; + tmp3 = t1.mat[0]*t2.mat[3] + t1.mat[1]*t2.mat[7] + + t1.mat[2]*t2.mat[11] + t1.mat[3]; + tmp4 = t1.mat[4]*t2.mat[0] + t1.mat[5]*t2.mat[4] + t1.mat[6]*t2.mat[8]; + tmp5 = t1.mat[4]*t2.mat[1] + t1.mat[5]*t2.mat[5] + t1.mat[6]*t2.mat[9]; + tmp6 = t1.mat[4]*t2.mat[2] + t1.mat[5]*t2.mat[6] + t1.mat[6]*t2.mat[10]; + tmp7 = t1.mat[4]*t2.mat[3] + t1.mat[5]*t2.mat[7] + + t1.mat[6]*t2.mat[11] + t1.mat[7]; + tmp8 = t1.mat[8]*t2.mat[0] + t1.mat[9]*t2.mat[4] + t1.mat[10]*t2.mat[8]; + tmp9 = t1.mat[8]*t2.mat[1] + t1.mat[9]*t2.mat[5] + t1.mat[10]*t2.mat[9]; + tmp10 = t1.mat[8]*t2.mat[2] + t1.mat[9]*t2.mat[6] + t1.mat[10]*t2.mat[10]; + tmp11 = t1.mat[8]*t2.mat[3] + t1.mat[9]*t2.mat[7] + + t1.mat[10]*t2.mat[11] + t1.mat[11]; + if (t1.isAffine()) { + aff = true; + mat[12] = mat[13] = mat[14] = 0; + mat[15] = 1; + } else { + double tmp12 = t1.mat[12]*t2.mat[0] + t1.mat[13]*t2.mat[4] + + t1.mat[14]*t2.mat[8]; + double tmp13 = t1.mat[12]*t2.mat[1] + t1.mat[13]*t2.mat[5] + + t1.mat[14]*t2.mat[9]; + double tmp14 = t1.mat[12]*t2.mat[2] + t1.mat[13]*t2.mat[6] + + t1.mat[14]*t2.mat[10]; + double tmp15 = t1.mat[12]*t2.mat[3] + t1.mat[13]*t2.mat[7] + + t1.mat[14]*t2.mat[11] + t1.mat[15]; + mat[12] = tmp12; + mat[13] = tmp13; + mat[14] = tmp14; + mat[15] = tmp15; + } + } else { + tmp0 = t1.mat[0]*t2.mat[0] + t1.mat[1]*t2.mat[4] + + t1.mat[2]*t2.mat[8] + t1.mat[3]*t2.mat[12]; + tmp1 = t1.mat[0]*t2.mat[1] + t1.mat[1]*t2.mat[5] + + t1.mat[2]*t2.mat[9] + t1.mat[3]*t2.mat[13]; + tmp2 = t1.mat[0]*t2.mat[2] + t1.mat[1]*t2.mat[6] + + t1.mat[2]*t2.mat[10] + t1.mat[3]*t2.mat[14]; + tmp3 = t1.mat[0]*t2.mat[3] + t1.mat[1]*t2.mat[7] + + t1.mat[2]*t2.mat[11] + t1.mat[3]*t2.mat[15]; + tmp4 = t1.mat[4]*t2.mat[0] + t1.mat[5]*t2.mat[4] + + t1.mat[6]*t2.mat[8] + t1.mat[7]*t2.mat[12]; + tmp5 = t1.mat[4]*t2.mat[1] + t1.mat[5]*t2.mat[5] + + t1.mat[6]*t2.mat[9] + t1.mat[7]*t2.mat[13]; + tmp6 = t1.mat[4]*t2.mat[2] + t1.mat[5]*t2.mat[6] + + t1.mat[6]*t2.mat[10] + t1.mat[7]*t2.mat[14]; + tmp7 = t1.mat[4]*t2.mat[3] + t1.mat[5]*t2.mat[7] + + t1.mat[6]*t2.mat[11] + t1.mat[7]*t2.mat[15]; + tmp8 = t1.mat[8]*t2.mat[0] + t1.mat[9]*t2.mat[4] + + t1.mat[10]*t2.mat[8] + t1.mat[11]*t2.mat[12]; + tmp9 = t1.mat[8]*t2.mat[1] + t1.mat[9]*t2.mat[5] + + t1.mat[10]*t2.mat[9] + t1.mat[11]*t2.mat[13]; + tmp10 = t1.mat[8]*t2.mat[2] + t1.mat[9]*t2.mat[6] + + t1.mat[10]*t2.mat[10] + t1.mat[11]*t2.mat[14]; + tmp11 = t1.mat[8]*t2.mat[3] + t1.mat[9]*t2.mat[7] + + t1.mat[10]*t2.mat[11] + t1.mat[11]*t2.mat[15]; + + if (t1.isAffine()) { + mat[12] = t2.mat[12]; + mat[13] = t2.mat[13]; + mat[14] = t2.mat[14]; + mat[15] = t2.mat[15]; + } else { + double tmp12 = t1.mat[12]*t2.mat[0] + t1.mat[13]*t2.mat[4] + + t1.mat[14]*t2.mat[8] + t1.mat[15]*t2.mat[12]; + double tmp13 = t1.mat[12]*t2.mat[1] + t1.mat[13]*t2.mat[5] + + t1.mat[14]*t2.mat[9] + t1.mat[15]*t2.mat[13]; + double tmp14 = t1.mat[12]*t2.mat[2] + t1.mat[13]*t2.mat[6] + + t1.mat[14]*t2.mat[10] + t1.mat[15]*t2.mat[14]; + double tmp15 = t1.mat[12]*t2.mat[3] + t1.mat[13]*t2.mat[7] + + t1.mat[14]*t2.mat[11] + t1.mat[15]*t2.mat[15]; + mat[12] = tmp12; + mat[13] = tmp13; + mat[14] = tmp14; + mat[15] = tmp15; + } + } + mat[0] = tmp0; + mat[1] = tmp1; + mat[2] = tmp2; + mat[3] = tmp3; + mat[4] = tmp4; + mat[5] = tmp5; + mat[6] = tmp6; + mat[7] = tmp7; + mat[8] = tmp8; + mat[9] = tmp9; + mat[10] = tmp10; + mat[11] = tmp11; + } + + + if (((t1.dirtyBits & CONGRUENT_BIT) == 0) && + ((t1.type & CONGRUENT) != 0) && + ((t2.dirtyBits & CONGRUENT_BIT) == 0) && + ((t2.type & CONGRUENT) != 0)) { + type = t1.type & t2.type; + dirtyBits = t1.dirtyBits | t2.dirtyBits | CLASSIFY_BIT | + ROTSCALESVD_DIRTY | RIGID_BIT; + } else { + if (aff) { + dirtyBits = ORTHO_BIT | CONGRUENT_BIT | RIGID_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY; + } else { + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + } + } + + if (autoNormalize) { + normalize(); + } + } + + /** + * Multiplies this transform by the inverse of transform t1. The final + * value is placed into this matrix (this = this*t1^-1). + * @param t1 the matrix whose inverse is computed. + */ + public final void mulInverse(Transform3D t1) { + Transform3D t2 = VirtualUniverse.mc.getTransform3D(null); + t2.autoNormalize = false; + t2.invert(t1); + this.mul(t2); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t2); + } + + + /** + * Multiplies transform t1 by the inverse of transform t2. The final + * value is placed into this matrix (this = t1*t2^-1). + * @param t1 the left transform in the multiplication + * @param t2 the transform whose inverse is computed. + */ + public final void mulInverse(Transform3D t1, Transform3D t2) { + Transform3D t3 = VirtualUniverse.mc.getTransform3D(null); + t3.autoNormalize = false; + t3.invert(t2); + this.mul(t1,t3); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t3); + } + + /** + * Multiplies transform t1 by the transpose of transform t2 and places + * the result into this transform (this = t1 * transpose(t2)). + * @param t1 the transform on the left hand side of the multiplication + * @param t2 the transform whose transpose is computed + */ + public final void mulTransposeRight(Transform3D t1, Transform3D t2) { + Transform3D t3 = VirtualUniverse.mc.getTransform3D(null); + t3.autoNormalize = false; + t3.transpose(t2); + mul(t1, t3); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t3); + } + + + /** + * Multiplies the transpose of transform t1 by transform t2 and places + * the result into this matrix (this = transpose(t1) * t2). + * @param t1 the transform whose transpose is computed + * @param t2 the transform on the right hand side of the multiplication + */ + public final void mulTransposeLeft(Transform3D t1, Transform3D t2){ + Transform3D t3 = VirtualUniverse.mc.getTransform3D(null); + t3.autoNormalize = false; + t3.transpose(t1); + mul(t3, t2); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t3); + } + + + /** + * Multiplies the transpose of transform t1 by the transpose of + * transform t2 and places the result into this transform + * (this = transpose(t1) * transpose(t2)). + * @param t1 the transform on the left hand side of the multiplication + * @param t2 the transform on the right hand side of the multiplication + */ + public final void mulTransposeBoth(Transform3D t1, Transform3D t2) { + Transform3D t3 = VirtualUniverse.mc.getTransform3D(null); + Transform3D t4 = VirtualUniverse.mc.getTransform3D(null); + t3.autoNormalize = false; + t4.autoNormalize = false; + t3.transpose(t1); + t4.transpose(t2); + mul(t3, t4); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t3); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t4); + } + + + /** + * Normalizes the rotational components (upper 3x3) of this matrix + * in place using a Singular Value Decomposition (SVD). + * This operation ensures that the column vectors of this matrix + * are orthogonal to each other. The primary use of this method + * is to correct for floating point errors that accumulate over + * time when concatenating a large number of rotation matrices. + * Note that the scale of the matrix is not altered by this method. + */ + public final void normalize() { + + if ((dirtyBits & (ROTATION_BIT|SVD_BIT)) != 0) { + computeScaleRotation(true); + } else if ((dirtyBits & (SCALE_BIT|SVD_BIT)) != 0) { + computeScales(true); + } + + mat[0] = rot[0]*scales[0]; + mat[1] = rot[1]*scales[1]; + mat[2] = rot[2]*scales[2]; + mat[4] = rot[3]*scales[0]; + mat[5] = rot[4]*scales[1]; + mat[6] = rot[5]*scales[2]; + mat[8] = rot[6]*scales[0]; + mat[9] = rot[7]*scales[1]; + mat[10] = rot[8]*scales[2]; + dirtyBits |= CLASSIFY_BIT; + dirtyBits &= ~ORTHO_BIT; + type |= ORTHO; + } + + /** + * Normalizes the rotational components (upper 3x3) of transform t1 + * using a Singular Value Decomposition (SVD), and places the result + * into this transform. + * This operation ensures that the column vectors of this matrix + * are orthogonal to each other. The primary use of this method + * is to correct for floating point errors that accumulate over + * time when concatenating a large number of rotation matrices. + * Note that the scale of the matrix is not altered by this method. + * + * @param t1 the source transform, which is not modified + */ + public final void normalize(Transform3D t1){ + set(t1); + normalize(); + } + + /** + * Normalizes the rotational components (upper 3x3) of this transform + * in place using a Cross Product (CP) normalization. + * This operation ensures that the column vectors of this matrix + * are orthogonal to each other. The primary use of this method + * is to correct for floating point errors that accumulate over + * time when concatenating a large number of rotation matrices. + * Note that the scale of the matrix is not altered by this method. + */ + public final void normalizeCP() { + if ((dirtyBits & SCALE_BIT) != 0) { + computeScales(false); + } + + double mag = mat[0]*mat[0] + mat[4]*mat[4] + + mat[8]*mat[8]; + + if (mag != 0) { + mag = 1.0/Math.sqrt(mag); + mat[0] = mat[0]*mag; + mat[4] = mat[4]*mag; + mat[8] = mat[8]*mag; + } + + mag = mat[1]*mat[1] + mat[5]*mat[5] + + mat[9]*mat[9]; + + if (mag != 0) { + mag = 1.0/Math.sqrt(mag); + mat[1] = mat[1]*mag; + mat[5] = mat[5]*mag; + mat[9] = mat[9]*mag; + } + mat[2] = (mat[4]*mat[9] - mat[5]*mat[8])*scales[0]; + mat[6] = (mat[1]*mat[8] - mat[0]*mat[9])*scales[1]; + mat[10] = (mat[0]*mat[5] - mat[1]*mat[4])*scales[2]; + + mat[0] *= scales[0]; + mat[1] *= scales[0]; + mat[4] *= scales[1]; + mat[5] *= scales[1]; + mat[8] *= scales[2]; + mat[9] *= scales[2]; + + // leave the AFFINE bit + dirtyBits |= CONGRUENT_BIT | RIGID_BIT | CLASSIFY_BIT | ROTATION_BIT | SVD_BIT; + dirtyBits &= ~ORTHO_BIT; + type |= ORTHO; + } + + + /** + * Normalizes the rotational components (upper 3x3) of transform t1 + * using a Cross Product (CP) normalization, and + * places the result into this transform. + * This operation ensures that the column vectors of this matrix + * are orthogonal to each other. The primary use of this method + * is to correct for floating point errors that accumulate over + * time when concatenating a large number of rotation matrices. + * Note that the scale of the matrix is not altered by this method. + * + * @param t1 the transform to be normalized + */ + public final void normalizeCP(Transform3D t1) { + set(t1); + normalizeCP(); + } + + + /** + * Returns true if all of the data members of transform t1 are + * equal to the corresponding data members in this Transform3D. + * @param t1 the transform with which the comparison is made + * @return true or false + */ + public boolean equals(Transform3D t1) { + return (t1 != null) && + (mat[0] == t1.mat[0]) && (mat[1] == t1.mat[1]) && + (mat[2] == t1.mat[2]) && (mat[3] == t1.mat[3]) && + (mat[4] == t1.mat[4]) && (mat[5] == t1.mat[5]) && + (mat[6] == t1.mat[6]) && (mat[7] == t1.mat[7]) && + (mat[8] == t1.mat[8]) && (mat[9] == t1.mat[9]) && + (mat[10] == t1.mat[10]) && (mat[11] == t1.mat[11]) && + (mat[12] == t1.mat[12]) && (mat[13] == t1.mat[13]) && + (mat[14] == t1.mat[14]) && ( mat[15] == t1.mat[15]); + } + + + /** + * Returns true if the Object o1 is of type Transform3D and all of the + * data members of o1 are equal to the corresponding data members in + * this Transform3D. + * @param o1 the object with which the comparison is made. + * @return true or false + */ + public boolean equals(Object o1) { + return (o1 instanceof Transform3D) && equals((Transform3D) o1); + } + + + /** + * Returns true if the L-infinite distance between this matrix + * and matrix m1 is less than or equal to the epsilon parameter, + * otherwise returns false. The L-infinite + * distance is equal to + * MAX[i=0,1,2,3 ; j=0,1,2,3 ; abs[(this.m(i,j) - m1.m(i,j)] + * @param t1 the transform to be compared to this transform + * @param epsilon the threshold value + */ + public boolean epsilonEquals(Transform3D t1, double epsilon) { + double diff; + + for (int i=0 ; i<16 ; i++) { + diff = mat[i] - t1.mat[i]; + if ((diff < 0 ? -diff : diff) > epsilon) { + return false; + } + } + return true; + } + + + /** + * Returns a hash code value based on the data values in this + * object. Two different Transform3D objects with identical data + * values (i.e., Transform3D.equals returns true) will return the + * same hash number. Two Transform3D objects with different data + * members may return the same hash value, although this is not + * likely. + * @return the integer hash code value + */ + public int hashCode() { + long bits = 1L; + + for (int i = 0; i < 16; i++) { + bits = 31L * bits + Double.doubleToLongBits(mat[i]); + } + return (int) (bits ^ (bits >> 32)); + } + + + /** + * Transform the vector vec using this transform and place the + * result into vecOut. + * @param vec the double precision vector to be transformed + * @param vecOut the vector into which the transformed values are placed + */ + public final void transform(Vector4d vec, Vector4d vecOut) { + + if (vec != vecOut) { + vecOut.x = (mat[0]*vec.x + mat[1]*vec.y + + mat[2]*vec.z + mat[3]*vec.w); + vecOut.y = (mat[4]*vec.x + mat[5]*vec.y + + mat[6]*vec.z + mat[7]*vec.w); + vecOut.z = (mat[8]*vec.x + mat[9]*vec.y + + mat[10]*vec.z + mat[11]*vec.w); + vecOut.w = (mat[12]*vec.x + mat[13]*vec.y + + mat[14]*vec.z + mat[15]*vec.w); + } else { + transform(vec); + } + } + + + /** + * Transform the vector vec using this Transform and place the + * result back into vec. + * @param vec the double precision vector to be transformed + */ + public final void transform(Vector4d vec) { + double x = (mat[0]*vec.x + mat[1]*vec.y + + mat[2]*vec.z + mat[3]*vec.w); + double y = (mat[4]*vec.x + mat[5]*vec.y + + mat[6]*vec.z + mat[7]*vec.w); + double z = (mat[8]*vec.x + mat[9]*vec.y + + mat[10]*vec.z + mat[11]*vec.w); + vec.w = (mat[12]*vec.x + mat[13]*vec.y + + mat[14]*vec.z + mat[15]*vec.w); + vec.x = x; + vec.y = y; + vec.z = z; + } + + + /** + * Transform the vector vec using this Transform and place the + * result into vecOut. + * @param vec the single precision vector to be transformed + * @param vecOut the vector into which the transformed values are placed + */ + public final void transform(Vector4f vec, Vector4f vecOut) { + if (vecOut != vec) { + vecOut.x = (float) (mat[0]*vec.x + mat[1]*vec.y + + mat[2]*vec.z + mat[3]*vec.w); + vecOut.y = (float) (mat[4]*vec.x + mat[5]*vec.y + + mat[6]*vec.z + mat[7]*vec.w); + vecOut.z = (float) (mat[8]*vec.x + mat[9]*vec.y + + mat[10]*vec.z + mat[11]*vec.w); + vecOut.w = (float) (mat[12]*vec.x + mat[13]*vec.y + + mat[14]*vec.z + mat[15]*vec.w); + } else { + transform(vec); + } + } + + + /** + * Transform the vector vec using this Transform and place the + * result back into vec. + * @param vec the single precision vector to be transformed + */ + public final void transform(Vector4f vec) { + float x = (float) (mat[0]*vec.x + mat[1]*vec.y + + mat[2]*vec.z + mat[3]*vec.w); + float y = (float) (mat[4]*vec.x + mat[5]*vec.y + + mat[6]*vec.z + mat[7]*vec.w); + float z = (float) (mat[8]*vec.x + mat[9]*vec.y + + mat[10]*vec.z + mat[11]*vec.w); + vec.w = (float) (mat[12]*vec.x + mat[13]*vec.y + + mat[14]*vec.z + mat[15]*vec.w); + vec.x = x; + vec.y = y; + vec.z = z; + } + + + /** + * Transforms the point parameter with this transform and + * places the result into pointOut. The fourth element of the + * point input paramter is assumed to be one. + * @param point the input point to be transformed + * @param pointOut the transformed point + */ + public final void transform(Point3d point, Point3d pointOut) { + if (point != pointOut) { + pointOut.x = mat[0]*point.x + mat[1]*point.y + + mat[2]*point.z + mat[3]; + pointOut.y = mat[4]*point.x + mat[5]*point.y + + mat[6]*point.z + mat[7]; + pointOut.z = mat[8]*point.x + mat[9]*point.y + + mat[10]*point.z + mat[11]; + } else { + transform(point); + } + } + + + /** + * Transforms the point parameter with this transform and + * places the result back into point. The fourth element of the + * point input paramter is assumed to be one. + * @param point the input point to be transformed + */ + public final void transform(Point3d point) { + double x = mat[0]*point.x + mat[1]*point.y + mat[2]*point.z + mat[3]; + double y = mat[4]*point.x + mat[5]*point.y + mat[6]*point.z + mat[7]; + point.z = mat[8]*point.x + mat[9]*point.y + mat[10]*point.z + mat[11]; + point.x = x; + point.y = y; + } + + + /** + * Transforms the normal parameter by this transform and places the value + * into normalOut. The fourth element of the normal is assumed to be zero. + * @param normal the input normal to be transformed + * @param normalOut the transformed normal + */ + public final void transform(Vector3d normal, Vector3d normalOut) { + if (normalOut != normal) { + normalOut.x = mat[0]*normal.x + mat[1]*normal.y + mat[2]*normal.z; + normalOut.y = mat[4]*normal.x + mat[5]*normal.y + mat[6]*normal.z; + normalOut.z = mat[8]*normal.x + mat[9]*normal.y + mat[10]*normal.z; + } else { + transform(normal); + } + } + + + /** + * Transforms the normal parameter by this transform and places the value + * back into normal. The fourth element of the normal is assumed to be zero. + * @param normal the input normal to be transformed + */ + public final void transform(Vector3d normal) { + double x = mat[0]*normal.x + mat[1]*normal.y + mat[2]*normal.z; + double y = mat[4]*normal.x + mat[5]*normal.y + mat[6]*normal.z; + normal.z = mat[8]*normal.x + mat[9]*normal.y + mat[10]*normal.z; + normal.x = x; + normal.y = y; + } + + + /** + * Transforms the point parameter with this transform and + * places the result into pointOut. The fourth element of the + * point input paramter is assumed to be one. + * @param point the input point to be transformed + * @param pointOut the transformed point + */ + public final void transform(Point3f point, Point3f pointOut) { + if (point != pointOut) { + pointOut.x = (float)(mat[0]*point.x + mat[1]*point.y + + mat[2]*point.z + mat[3]); + pointOut.y = (float)(mat[4]*point.x + mat[5]*point.y + + mat[6]*point.z + mat[7]); + pointOut.z = (float)(mat[8]*point.x + mat[9]*point.y + + mat[10]*point.z + mat[11]); + } else { + transform(point); + } + } + + + /** + * Transforms the point parameter with this transform and + * places the result back into point. The fourth element of the + * point input paramter is assumed to be one. + * @param point the input point to be transformed + */ + public final void transform(Point3f point) { + float x = (float) (mat[0]*point.x + mat[1]*point.y + + mat[2]*point.z + mat[3]); + float y = (float) (mat[4]*point.x + mat[5]*point.y + + mat[6]*point.z + mat[7]); + point.z = (float) (mat[8]*point.x + mat[9]*point.y + + mat[10]*point.z + mat[11]); + point.x = x; + point.y = y; + } + + + /** + * Transforms the normal parameter by this transform and places the value + * into normalOut. The fourth element of the normal is assumed to be zero. + * Note: For correct lighting results, if a transform has uneven scaling + * surface normals should transformed by the inverse transpose of + * the transform. This the responsibility of the application and is not + * done automatically by this method. + * @param normal the input normal to be transformed + * @param normalOut the transformed normal + */ + public final void transform(Vector3f normal, Vector3f normalOut) { + if (normal != normalOut) { + normalOut.x = (float) (mat[0]*normal.x + mat[1]*normal.y + + mat[2]*normal.z); + normalOut.y = (float) (mat[4]*normal.x + mat[5]*normal.y + + mat[6]*normal.z); + normalOut.z = (float) (mat[8]*normal.x + mat[9]*normal.y + + mat[10]*normal.z); + } else { + transform(normal); + } + } + + /** + * Transforms the normal parameter by this transform and places the value + * back into normal. The fourth element of the normal is assumed to be zero. + * Note: For correct lighting results, if a transform has uneven scaling + * surface normals should transformed by the inverse transpose of + * the transform. This the responsibility of the application and is not + * done automatically by this method. + * @param normal the input normal to be transformed + */ + public final void transform(Vector3f normal) { + float x = (float) (mat[0]*normal.x + mat[1]*normal.y + + mat[2]*normal.z); + float y = (float) (mat[4]*normal.x + mat[5]*normal.y + + mat[6]*normal.z); + normal.z = (float) (mat[8]*normal.x + mat[9]*normal.y + + mat[10]*normal.z); + normal.x = x; + normal.y = y; + } + + + /** + * Replaces the upper 3x3 matrix values of this transform with the + * values in the matrix m1. + * @param m1 the matrix that will be the new upper 3x3 + */ + public final void setRotationScale(Matrix3f m1) { + mat[0] = m1.m00; mat[1] = m1.m01; mat[2] = m1.m02; + mat[4] = m1.m10; mat[5] = m1.m11; mat[6] = m1.m12; + mat[8] = m1.m20; mat[9] = m1.m21; mat[10] = m1.m22; + + // keep affine bit + dirtyBits |= (RIGID_BIT | CONGRUENT_BIT | ORTHO_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY); + if (autoNormalize) { + normalize(); + } + } + + + /** + * Replaces the upper 3x3 matrix values of this transform with the + * values in the matrix m1. + * @param m1 the matrix that will be the new upper 3x3 + */ + public final void setRotationScale(Matrix3d m1) { + mat[0] = m1.m00; mat[1] = m1.m01; mat[2] = m1.m02; + mat[4] = m1.m10; mat[5] = m1.m11; mat[6] = m1.m12; + mat[8] = m1.m20; mat[9] = m1.m21; mat[10] = m1.m22; + + dirtyBits |= (RIGID_BIT | CONGRUENT_BIT | ORTHO_BIT | + CLASSIFY_BIT | ROTSCALESVD_DIRTY); + if (autoNormalize) { + normalize(); + } + } + + /** + * Scales transform t1 by a Uniform scale matrix with scale + * factor s and then adds transform t2 (this = S*t1 + t2). + * @param s the scale factor + * @param t1 the transform to be scaled + * @param t2 the transform to be added + */ + public final void scaleAdd(double s, Transform3D t1, Transform3D t2) { + for (int i=0 ; i<16 ; i++) { + mat[i] = s*t1.mat[i] + t2.mat[i]; + } + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + + /** + * Scales this transform by a Uniform scale matrix with scale factor + * s and then adds transform t1 (this = S*this + t1). + * @param s the scale factor + * @param t1 the transform to be added + */ + public final void scaleAdd(double s, Transform3D t1) { + for (int i=0 ; i<16 ; i++) { + mat[i] = s*mat[i] + t1.mat[i]; + } + + dirtyBits = CLASSIFY_ALL_DIRTY | ROTSCALESVD_DIRTY; + + if (autoNormalize) { + normalize(); + } + } + + + /** + * Gets the upper 3x3 values of this matrix and places them into + * the matrix m1. + * @param m1 the matrix that will hold the values + */ + public final void getRotationScale(Matrix3f m1) { + m1.m00 = (float) mat[0]; + m1.m01 = (float) mat[1]; + m1.m02 = (float) mat[2]; + m1.m10 = (float) mat[4]; + m1.m11 = (float) mat[5]; + m1.m12 = (float) mat[6]; + m1.m20 = (float) mat[8]; + m1.m21 = (float) mat[9]; + m1.m22 = (float) mat[10]; + } + + + /** + * Gets the upper 3x3 values of this matrix and places them into + * the matrix m1. + * @param m1 the matrix that will hold the values + */ + public final void getRotationScale(Matrix3d m1) { + m1.m00 = mat[0]; + m1.m01 = mat[1]; + m1.m02 = mat[2]; + m1.m10 = mat[4]; + m1.m11 = mat[5]; + m1.m12 = mat[6]; + m1.m20 = mat[8]; + m1.m21 = mat[9]; + m1.m22 = mat[10]; + } + + + /** + * Helping function that specifies the position and orientation of a + * view matrix. The inverse of this transform can be used to control + * the ViewPlatform object within the scene graph. + * @param eye the location of the eye + * @param center a point in the virtual world where the eye is looking + * @param up an up vector specifying the frustum's up direction + */ + public void lookAt(Point3d eye, Point3d center, Vector3d up) { + double forwardx,forwardy,forwardz,invMag; + double upx,upy,upz; + double sidex,sidey,sidez; + + forwardx = eye.x - center.x; + forwardy = eye.y - center.y; + forwardz = eye.z - center.z; + + invMag = 1.0/Math.sqrt( forwardx*forwardx + forwardy*forwardy + forwardz*forwardz); + forwardx = forwardx*invMag; + forwardy = forwardy*invMag; + forwardz = forwardz*invMag; + + + invMag = 1.0/Math.sqrt( up.x*up.x + up.y*up.y + up.z*up.z); + upx = up.x*invMag; + upy = up.y*invMag; + upz = up.z*invMag; + + // side = Up cross forward + sidex = upy*forwardz-forwardy*upz; + sidey = upz*forwardx-upx*forwardz; + sidez = upx*forwardy-upy*forwardx; + + invMag = 1.0/Math.sqrt( sidex*sidex + sidey*sidey + sidez*sidez); + sidex *= invMag; + sidey *= invMag; + sidez *= invMag; + + // recompute up = forward cross side + + upx = forwardy*sidez-sidey*forwardz; + upy = forwardz*sidex-forwardx*sidez; + upz = forwardx*sidey-forwardy*sidex; + + // transpose because we calculated the inverse of what we want + mat[0] = sidex; + mat[1] = sidey; + mat[2] = sidez; + + mat[4] = upx; + mat[5] = upy; + mat[6] = upz; + + mat[8] = forwardx; + mat[9] = forwardy; + mat[10] = forwardz; + + mat[3] = -eye.x*mat[0] + -eye.y*mat[1] + -eye.z*mat[2]; + mat[7] = -eye.x*mat[4] + -eye.y*mat[5] + -eye.z*mat[6]; + mat[11] = -eye.x*mat[8] + -eye.y*mat[9] + -eye.z*mat[10]; + + mat[12] = mat[13] = mat[14] = 0; + mat[15] = 1; + + type = AFFINE | CONGRUENT | RIGID | ORTHO; + dirtyBits = CLASSIFY_BIT | ROTSCALESVD_DIRTY; + } + + + /** + * Creates a perspective projection transform that mimics a standard, + * camera-based, + * view-model. This transform maps coordinates from Eye Coordinates (EC) + * to Clipping Coordinates (CC). Note that unlike the similar function + * in OpenGL, the clipping coordinates generated by the resulting + * transform are in a right-handed coordinate system + * (as are all other coordinate systems in Java 3D). + * <p> + * The frustum function-call establishes a view model with the eye + * at the apex of a symmetric view frustum. The arguments + * define the frustum and its associated perspective projection: + * (left, bottom, -near) and (right, top, -near) specify the + * point on the near clipping plane that maps onto the + * lower-left and upper-right corners of the window respectively, + * assuming the eye is located at (0, 0, 0). + * @param left the vertical line on the left edge of the near + * clipping plane mapped to the left edge of the graphics window + * @param right the vertical line on the right edge of the near + * clipping plane mapped to the right edge of the graphics window + * @param bottom the horizontal line on the bottom edge of the near + * clipping plane mapped to the bottom edge of the graphics window + * @param top the horizontal line on the top edge of the near + * @param near the distance to the frustum's near clipping plane. + * This value must be positive, (the value -near is the location of the + * near clip plane). + * @param far the distance to the frustum's far clipping plane. + * This value must be positive, and must be greater than near. + */ + public void frustum(double left, double right, + double bottom, double top, + double near, double far) { + double dx = 1/(right - left); + double dy = 1/(top - bottom); + double dz = 1/(far - near); + + mat[0] = (2.0*near)*dx; + mat[5] = (2.0*near)*dy; + mat[10] = (far+near)*dz; + mat[2] = (right+left)*dx; + mat[6] = (top+bottom)*dy; + mat[11] = (2.0*far*near)*dz; + mat[14] = -1.0; + mat[1] = mat[3] = mat[4] = mat[7] = mat[8] = mat[9] = mat[12] + = mat[13] = mat[15] = 0; + + // Matrix is a projection transform + type = 0; + dirtyBits = CLASSIFY_BIT | ROTSCALESVD_DIRTY; + } + + + /** + * Creates a perspective projection transform that mimics a standard, + * camera-based, + * view-model. This transform maps coordinates from Eye Coordinates (EC) + * to Clipping Coordinates (CC). Note that unlike the similar function + * in OpenGL, the clipping coordinates generated by the resulting + * transform are in a right-handed coordinate system + * (as are all other coordinate systems in Java 3D). Also note that the + * field of view is specified in radians. + * @param fovx specifies the field of view in the x direction, in radians + * @param aspect specifies the aspect ratio and thus the field of + * view in the x direction. The aspect ratio is the ratio of x to y, + * or width to height. + * @param zNear the distance to the frustum's near clipping plane. + * This value must be positive, (the value -zNear is the location of the + * near clip plane). + * @param zFar the distance to the frustum's far clipping plane + */ + public void perspective(double fovx, double aspect, + double zNear, double zFar) { + double sine, cotangent, deltaZ; + double half_fov = fovx * 0.5; + double x, y; + Vector3d v1, v2, v3, v4; + Vector3d norm = new Vector3d(); + + deltaZ = zFar - zNear; + sine = Math.sin(half_fov); +// if ((deltaZ == 0.0) || (sine == 0.0) || (aspect == 0.0)) { +// return; +// } + cotangent = Math.cos(half_fov) / sine; + + mat[0] = cotangent; + mat[5] = cotangent * aspect; + mat[10] = (zFar + zNear) / deltaZ; + mat[11] = 2.0 * zNear * zFar / deltaZ; + mat[14] = -1.0; + mat[1] = mat[2] = mat[3] = mat[4] = mat[6] = mat[7] = mat[8] = + mat[9] = mat[12] = mat[13] = mat[15] = 0; + + // Matrix is a projection transform + type = 0; + dirtyBits = CLASSIFY_BIT | ROTSCALESVD_DIRTY; + } + + + /** + * Creates an orthographic projection transform that mimics a standard, + * camera-based, + * view-model. This transform maps coordinates from Eye Coordinates (EC) + * to Clipping Coordinates (CC). Note that unlike the similar function + * in OpenGL, the clipping coordinates generated by the resulting + * transform are in a right-handed coordinate system + * (as are all other coordinate systems in Java 3D). + * @param left the vertical line on the left edge of the near + * clipping plane mapped to the left edge of the graphics window + * @param right the vertical line on the right edge of the near + * clipping plane mapped to the right edge of the graphics window + * @param bottom the horizontal line on the bottom edge of the near + * clipping plane mapped to the bottom edge of the graphics window + * @param top the horizontal line on the top edge of the near + * clipping plane mapped to the top edge of the graphics window + * @param near the distance to the frustum's near clipping plane + * (the value -near is the location of the near clip plane) + * @param far the distance to the frustum's far clipping plane + */ + public void ortho(double left, double right, double bottom, + double top, double near, double far) { + double deltax = 1/(right - left); + double deltay = 1/(top - bottom); + double deltaz = 1/(far - near); + +// if ((deltax == 0.0) || (deltay == 0.0) || (deltaz == 0.0)) { +// return; +// } + + mat[0] = 2.0 * deltax; + mat[3] = -(right + left) * deltax; + mat[5] = 2.0 * deltay; + mat[7] = -(top + bottom) * deltay; + mat[10] = 2.0 * deltaz; + mat[11] = (far + near) * deltaz; + mat[1] = mat[2] = mat[4] = mat[6] = mat[8] = + mat[9] = mat[12] = mat[13] = mat[14] = 0; + mat[15] = 1; + // Matrix is a projection transform + type = AFFINE; + dirtyBits = CLASSIFY_BIT | ROTSCALESVD_DIRTY | CONGRUENT_BIT | + RIGID_BIT | ORTHO_BIT; + } + + /** + * get the scaling factor of matrix in this transform, + * use for distance scaling + */ + double getDistanceScale() { + // The caller know that this matrix is affine + // orthogonal before invoke this procedure + + if ((dirtyBits & SCALE_BIT) != 0) { + double max = mat[0]*mat[0] + mat[4]*mat[4] + + mat[8]*mat[8]; + if (((dirtyBits & CONGRUENT_BIT) == 0) && + ((type & CONGRUENT) != 0)) { + // in most case it is congruent + return Math.sqrt(max); + } + double tmp = mat[1]*mat[1] + mat[5]*mat[5] + + mat[9]*mat[9]; + if (tmp > max) { + max = tmp; + } + tmp = mat[2]*mat[2] + mat[6]*mat[6] + mat[10]*mat[10]; + return Math.sqrt((tmp > max) ? tmp : max); + } + return max3(scales); + } + + + static private void mat_mul(double[] m1, double[] m2, double[] m3) { + + double[] result = m3; + if ((m1 == m3) || (m2 == m3)) { + result = new double[9]; + } + + result[0] = m1[0]*m2[0] + m1[1]*m2[3] + m1[2]*m2[6]; + result[1] = m1[0]*m2[1] + m1[1]*m2[4] + m1[2]*m2[7]; + result[2] = m1[0]*m2[2] + m1[1]*m2[5] + m1[2]*m2[8]; + + result[3] = m1[3]*m2[0] + m1[4]*m2[3] + m1[5]*m2[6]; + result[4] = m1[3]*m2[1] + m1[4]*m2[4] + m1[5]*m2[7]; + result[5] = m1[3]*m2[2] + m1[4]*m2[5] + m1[5]*m2[8]; + + result[6] = m1[6]*m2[0] + m1[7]*m2[3] + m1[8]*m2[6]; + result[7] = m1[6]*m2[1] + m1[7]*m2[4] + m1[8]*m2[7]; + result[8] = m1[6]*m2[2] + m1[7]*m2[5] + m1[8]*m2[8]; + + if (result != m3) { + for(int i=0;i<9;i++) { + m3[i] = result[i]; + } + } + } + + static private void transpose_mat(double[] in, double[] out) { + out[0] = in[0]; + out[1] = in[3]; + out[2] = in[6]; + + out[3] = in[1]; + out[4] = in[4]; + out[5] = in[7]; + + out[6] = in[2]; + out[7] = in[5]; + out[8] = in[8]; + } + + + final static private void multipleScale(double m[] , double s[]) { + m[0] *= s[0]; + m[1] *= s[0]; + m[2] *= s[0]; + m[4] *= s[1]; + m[5] *= s[1]; + m[6] *= s[1]; + m[8] *= s[2]; + m[9] *= s[2]; + m[10] *= s[2]; + } + + private void compute_svd(Transform3D matrix, double[] outScale, + double[] outRot) { + + int i,j; + double g,scale; + double m[] = new double[9]; + + // if (!svdAllocd) { + double[] u1 = new double[9]; + double[] v1 = new double[9]; + double[] t1 = new double[9]; + double[] t2 = new double[9]; + // double[] ts = new double[9]; + // double[] svdTmp = new double[9]; It is replaced by t1 + double[] svdRot = new double[9]; + // double[] single_values = new double[3]; replaced by t2 + + double[] e = new double[3]; + double[] svdScales = new double[3]; + + + // TODO: initialize to 0's if alread allocd? Should not have to, since + // no operations depend on these being init'd to zero. + + int converged, negCnt=0; + double cs,sn; + double c1,c2,c3,c4; + double s1,s2,s3,s4; + double cl1,cl2,cl3; + + + svdRot[0] = m[0] = matrix.mat[0]; + svdRot[1] = m[1] = matrix.mat[1]; + svdRot[2] = m[2] = matrix.mat[2]; + svdRot[3] = m[3] = matrix.mat[4]; + svdRot[4] = m[4] = matrix.mat[5]; + svdRot[5] = m[5] = matrix.mat[6]; + svdRot[6] = m[6] = matrix.mat[8]; + svdRot[7] = m[7] = matrix.mat[9]; + svdRot[8] = m[8] = matrix.mat[10]; + + // u1 + + if( m[3]*m[3] < EPS ) { + u1[0] = 1.0; u1[1] = 0.0; u1[2] = 0.0; + u1[3] = 0.0; u1[4] = 1.0; u1[5] = 0.0; + u1[6] = 0.0; u1[7] = 0.0; u1[8] = 1.0; + } else if( m[0]*m[0] < EPS ) { + t1[0] = m[0]; + t1[1] = m[1]; + t1[2] = m[2]; + m[0] = m[3]; + m[1] = m[4]; + m[2] = m[5]; + + m[3] = -t1[0]; // zero + m[4] = -t1[1]; + m[5] = -t1[2]; + + u1[0] = 0.0; u1[1] = 1.0; u1[2] = 0.0; + u1[3] = -1.0; u1[4] = 0.0; u1[5] = 0.0; + u1[6] = 0.0; u1[7] = 0.0; u1[8] = 1.0; + } else { + g = 1.0/Math.sqrt(m[0]*m[0] + m[3]*m[3]); + c1 = m[0]*g; + s1 = m[3]*g; + t1[0] = c1*m[0] + s1*m[3]; + t1[1] = c1*m[1] + s1*m[4]; + t1[2] = c1*m[2] + s1*m[5]; + + m[3] = -s1*m[0] + c1*m[3]; // zero + m[4] = -s1*m[1] + c1*m[4]; + m[5] = -s1*m[2] + c1*m[5]; + + m[0] = t1[0]; + m[1] = t1[1]; + m[2] = t1[2]; + u1[0] = c1; u1[1] = s1; u1[2] = 0.0; + u1[3] = -s1; u1[4] = c1; u1[5] = 0.0; + u1[6] = 0.0; u1[7] = 0.0; u1[8] = 1.0; + } + + // u2 + + if( m[6]*m[6] < EPS ) { + } else if( m[0]*m[0] < EPS ){ + t1[0] = m[0]; + t1[1] = m[1]; + t1[2] = m[2]; + m[0] = m[6]; + m[1] = m[7]; + m[2] = m[8]; + + m[6] = -t1[0]; // zero + m[7] = -t1[1]; + m[8] = -t1[2]; + + t1[0] = u1[0]; + t1[1] = u1[1]; + t1[2] = u1[2]; + u1[0] = u1[6]; + u1[1] = u1[7]; + u1[2] = u1[8]; + + u1[6] = -t1[0]; // zero + u1[7] = -t1[1]; + u1[8] = -t1[2]; + } else { + g = 1.0/Math.sqrt(m[0]*m[0] + m[6]*m[6]); + c2 = m[0]*g; + s2 = m[6]*g; + t1[0] = c2*m[0] + s2*m[6]; + t1[1] = c2*m[1] + s2*m[7]; + t1[2] = c2*m[2] + s2*m[8]; + + m[6] = -s2*m[0] + c2*m[6]; + m[7] = -s2*m[1] + c2*m[7]; + m[8] = -s2*m[2] + c2*m[8]; + m[0] = t1[0]; + m[1] = t1[1]; + m[2] = t1[2]; + + t1[0] = c2*u1[0]; + t1[1] = c2*u1[1]; + u1[2] = s2; + + t1[6] = -u1[0]*s2; + t1[7] = -u1[1]*s2; + u1[8] = c2; + u1[0] = t1[0]; + u1[1] = t1[1]; + u1[6] = t1[6]; + u1[7] = t1[7]; + } + + // v1 + + if( m[2]*m[2] < EPS ) { + v1[0] = 1.0; v1[1] = 0.0; v1[2] = 0.0; + v1[3] = 0.0; v1[4] = 1.0; v1[5] = 0.0; + v1[6] = 0.0; v1[7] = 0.0; v1[8] = 1.0; + } else if( m[1]*m[1] < EPS ) { + t1[2] = m[2]; + t1[5] = m[5]; + t1[8] = m[8]; + m[2] = -m[1]; + m[5] = -m[4]; + m[8] = -m[7]; + + m[1] = t1[2]; // zero + m[4] = t1[5]; + m[7] = t1[8]; + + v1[0] = 1.0; v1[1] = 0.0; v1[2] = 0.0; + v1[3] = 0.0; v1[4] = 0.0; v1[5] =-1.0; + v1[6] = 0.0; v1[7] = 1.0; v1[8] = 0.0; + } else { + g = 1.0/Math.sqrt(m[1]*m[1] + m[2]*m[2]); + c3 = m[1]*g; + s3 = m[2]*g; + t1[1] = c3*m[1] + s3*m[2]; // can assign to m[1]? + m[2] =-s3*m[1] + c3*m[2]; // zero + m[1] = t1[1]; + + t1[4] = c3*m[4] + s3*m[5]; + m[5] =-s3*m[4] + c3*m[5]; + m[4] = t1[4]; + + t1[7] = c3*m[7] + s3*m[8]; + m[8] =-s3*m[7] + c3*m[8]; + m[7] = t1[7]; + + v1[0] = 1.0; v1[1] = 0.0; v1[2] = 0.0; + v1[3] = 0.0; v1[4] = c3; v1[5] = -s3; + v1[6] = 0.0; v1[7] = s3; v1[8] = c3; + } + + // u3 + + if( m[7]*m[7] < EPS ) { + } else if( m[4]*m[4] < EPS ) { + t1[3] = m[3]; + t1[4] = m[4]; + t1[5] = m[5]; + m[3] = m[6]; // zero + m[4] = m[7]; + m[5] = m[8]; + + m[6] = -t1[3]; // zero + m[7] = -t1[4]; // zero + m[8] = -t1[5]; + + t1[3] = u1[3]; + t1[4] = u1[4]; + t1[5] = u1[5]; + u1[3] = u1[6]; + u1[4] = u1[7]; + u1[5] = u1[8]; + + u1[6] = -t1[3]; // zero + u1[7] = -t1[4]; + u1[8] = -t1[5]; + + } else { + g = 1.0/Math.sqrt(m[4]*m[4] + m[7]*m[7]); + c4 = m[4]*g; + s4 = m[7]*g; + t1[3] = c4*m[3] + s4*m[6]; + m[6] =-s4*m[3] + c4*m[6]; // zero + m[3] = t1[3]; + + t1[4] = c4*m[4] + s4*m[7]; + m[7] =-s4*m[4] + c4*m[7]; + m[4] = t1[4]; + + t1[5] = c4*m[5] + s4*m[8]; + m[8] =-s4*m[5] + c4*m[8]; + m[5] = t1[5]; + + t1[3] = c4*u1[3] + s4*u1[6]; + u1[6] =-s4*u1[3] + c4*u1[6]; + u1[3] = t1[3]; + + t1[4] = c4*u1[4] + s4*u1[7]; + u1[7] =-s4*u1[4] + c4*u1[7]; + u1[4] = t1[4]; + + t1[5] = c4*u1[5] + s4*u1[8]; + u1[8] =-s4*u1[5] + c4*u1[8]; + u1[5] = t1[5]; + } + + t2[0] = m[0]; + t2[1] = m[4]; + t2[2] = m[8]; + e[0] = m[1]; + e[1] = m[5]; + + if( e[0]*e[0]>EPS || e[1]*e[1]>EPS ) { + compute_qr( t2, e, u1, v1); + } + + svdScales[0] = t2[0]; + svdScales[1] = t2[1]; + svdScales[2] = t2[2]; + + + // Do some optimization here. If scale is unity, simply return the rotation matric. + if(almostOne(Math.abs(svdScales[0])) && + almostOne(Math.abs(svdScales[1])) && + almostOne(Math.abs(svdScales[2]))) { + + for(i=0;i<3;i++) + if(svdScales[i]<0.0) + negCnt++; + + if((negCnt==0)||(negCnt==2)) { + //System.out.println("Optimize!!"); + outScale[0] = outScale[1] = outScale[2] = 1.0; + for(i=0;i<9;i++) + outRot[i] = svdRot[i]; + + return; + } + } + + // TODO: could eliminate use of t1 and t1 by making a new method which + // transposes and multiplies two matricies + transpose_mat(u1, t1); + transpose_mat(v1, t2); + + + svdReorder( m, t1, t2, svdRot, svdScales, outRot, outScale); + } + + + private void svdReorder( double[] m, double[] t1, double[] t2, double[] rot, + double[] scales, double[] outRot, double[] outScale) { + + int in0, in1, in2, index,i; + int[] svdOut = new int[3]; + double[] svdMag = new double[3]; + + + // check for rotation information in the scales + if(scales[0] < 0.0 ) { // move the rotation info to rotation matrix + scales[0] = -scales[0]; + t2[0] = -t2[0]; + t2[1] = -t2[1]; + t2[2] = -t2[2]; + } + if(scales[1] < 0.0 ) { // move the rotation info to rotation matrix + scales[1] = -scales[1]; + t2[3] = -t2[3]; + t2[4] = -t2[4]; + t2[5] = -t2[5]; + } + if(scales[2] < 0.0 ) { // move the rotation info to rotation matrix + scales[2] = -scales[2]; + t2[6] = -t2[6]; + t2[7] = -t2[7]; + t2[8] = -t2[8]; + } + + + mat_mul(t1,t2,rot); + + // check for equal scales case and do not reorder + if(almostEqual(Math.abs(scales[0]), Math.abs(scales[1])) && + almostEqual(Math.abs(scales[1]), Math.abs(scales[2])) ){ + for(i=0;i<9;i++){ + outRot[i] = rot[i]; + } + for(i=0;i<3;i++){ + outScale[i] = scales[i]; + } + + }else { + + // sort the order of the results of SVD + if( scales[0] > scales[1]) { + if( scales[0] > scales[2] ) { + if( scales[2] > scales[1] ) { + svdOut[0] = 0; svdOut[1] = 2; svdOut[2] = 1; // xzy + } else { + svdOut[0] = 0; svdOut[1] = 1; svdOut[2] = 2; // xyz + } + } else { + svdOut[0] = 2; svdOut[1] = 0; svdOut[2] = 1; // zxy + } + } else { // y > x + if( scales[1] > scales[2] ) { + if( scales[2] > scales[0] ) { + svdOut[0] = 1; svdOut[1] = 2; svdOut[2] = 0; // yzx + } else { + svdOut[0] = 1; svdOut[1] = 0; svdOut[2] = 2; // yxz + } + } else { + svdOut[0] = 2; svdOut[1] = 1; svdOut[2] = 0; // zyx + } + } + + + // sort the order of the input matrix + svdMag[0] = (m[0]*m[0] + m[1]*m[1] + m[2]*m[2]); + svdMag[1] = (m[3]*m[3] + m[4]*m[4] + m[5]*m[5]); + svdMag[2] = (m[6]*m[6] + m[7]*m[7] + m[8]*m[8]); + + + if( svdMag[0] > svdMag[1]) { + if( svdMag[0] > svdMag[2] ) { + if( svdMag[2] > svdMag[1] ) { + // 0 - 2 - 1 + in0 = 0; in2 = 1; in1 = 2;// xzy + } else { + // 0 - 1 - 2 + in0 = 0; in1 = 1; in2 = 2; // xyz + } + } else { + // 2 - 0 - 1 + in2 = 0; in0 = 1; in1 = 2; // zxy + } + } else { // y > x 1>0 + if( svdMag[1] > svdMag[2] ) { // 1>2 + if( svdMag[2] > svdMag[0] ) { // 2>0 + // 1 - 2 - 0 + in1 = 0; in2 = 1; in0 = 2; // yzx + } else { + // 1 - 0 - 2 + in1 = 0; in0 = 1; in2 = 2; // yxz + } + } else { + // 2 - 1 - 0 + in2 = 0; in1 = 1; in0 = 2; // zyx + } + } + + + index = svdOut[in0]; + outScale[0] = scales[index]; + + index = svdOut[in1]; + outScale[1] = scales[index]; + + index = svdOut[in2]; + outScale[2] = scales[index]; + + index = svdOut[in0]; + if (outRot == null) + System.out.println("outRot == null"); + if ( rot == null) + System.out.println("rot == null"); + System.out.flush(); + + outRot[0] = rot[index]; + + index = svdOut[in0]+3; + outRot[0+3] = rot[index]; + + index = svdOut[in0]+6; + outRot[0+6] = rot[index]; + + index = svdOut[in1]; + outRot[1] = rot[index]; + + index = svdOut[in1]+3; + outRot[1+3] = rot[index]; + + index = svdOut[in1]+6; + outRot[1+6] = rot[index]; + + index = svdOut[in2]; + outRot[2] = rot[index]; + + index = svdOut[in2]+3; + outRot[2+3] = rot[index]; + + index = svdOut[in2]+6; + outRot[2+6] = rot[index]; + } + + } + + private int compute_qr( double[] s, double[] e, double[] u, double[] v) { + int i,j,k; + boolean converged; + double shift,ssmin,ssmax,r; + + double utemp,vtemp; + double f,g; + + final int MAX_INTERATIONS = 10; + final double CONVERGE_TOL = 4.89E-15; + + double[] cosl = new double[2]; + double[] cosr = new double[2]; + double[] sinl = new double[2]; + double[] sinr = new double[2]; + double[] qr_m = new double[9]; + + + double c_b48 = 1.; + double c_b71 = -1.; + int first; + converged = false; + + first = 1; + + if( Math.abs(e[1]) < CONVERGE_TOL || Math.abs(e[0]) < CONVERGE_TOL) converged = true; + + for(k=0;k<MAX_INTERATIONS && !converged;k++) { + shift = compute_shift( s[1], e[1], s[2]); + f = (Math.abs(s[0]) - shift) * (d_sign(c_b48, s[0]) + shift/s[0]); + g = e[0]; + r = compute_rot(f, g, sinr, cosr, 0, first); + f = cosr[0] * s[0] + sinr[0] * e[0]; + e[0] = cosr[0] * e[0] - sinr[0] * s[0]; + g = sinr[0] * s[1]; + s[1] = cosr[0] * s[1]; + + r = compute_rot(f, g, sinl, cosl, 0, first); + first = 0; + s[0] = r; + f = cosl[0] * e[0] + sinl[0] * s[1]; + s[1] = cosl[0] * s[1] - sinl[0] * e[0]; + g = sinl[0] * e[1]; + e[1] = cosl[0] * e[1]; + + r = compute_rot(f, g, sinr, cosr, 1, first); + e[0] = r; + f = cosr[1] * s[1] + sinr[1] * e[1]; + e[1] = cosr[1] * e[1] - sinr[1] * s[1]; + g = sinr[1] * s[2]; + s[2] = cosr[1] * s[2]; + + r = compute_rot(f, g, sinl, cosl, 1, first); + s[1] = r; + f = cosl[1] * e[1] + sinl[1] * s[2]; + s[2] = cosl[1] * s[2] - sinl[1] * e[1]; + e[1] = f; + + // update u matrices + utemp = u[0]; + u[0] = cosl[0]*utemp + sinl[0]*u[3]; + u[3] = -sinl[0]*utemp + cosl[0]*u[3]; + utemp = u[1]; + u[1] = cosl[0]*utemp + sinl[0]*u[4]; + u[4] = -sinl[0]*utemp + cosl[0]*u[4]; + utemp = u[2]; + u[2] = cosl[0]*utemp + sinl[0]*u[5]; + u[5] = -sinl[0]*utemp + cosl[0]*u[5]; + + utemp = u[3]; + u[3] = cosl[1]*utemp + sinl[1]*u[6]; + u[6] = -sinl[1]*utemp + cosl[1]*u[6]; + utemp = u[4]; + u[4] = cosl[1]*utemp + sinl[1]*u[7]; + u[7] = -sinl[1]*utemp + cosl[1]*u[7]; + utemp = u[5]; + u[5] = cosl[1]*utemp + sinl[1]*u[8]; + u[8] = -sinl[1]*utemp + cosl[1]*u[8]; + + // update v matrices + + vtemp = v[0]; + v[0] = cosr[0]*vtemp + sinr[0]*v[1]; + v[1] = -sinr[0]*vtemp + cosr[0]*v[1]; + vtemp = v[3]; + v[3] = cosr[0]*vtemp + sinr[0]*v[4]; + v[4] = -sinr[0]*vtemp + cosr[0]*v[4]; + vtemp = v[6]; + v[6] = cosr[0]*vtemp + sinr[0]*v[7]; + v[7] = -sinr[0]*vtemp + cosr[0]*v[7]; + + vtemp = v[1]; + v[1] = cosr[1]*vtemp + sinr[1]*v[2]; + v[2] = -sinr[1]*vtemp + cosr[1]*v[2]; + vtemp = v[4]; + v[4] = cosr[1]*vtemp + sinr[1]*v[5]; + v[5] = -sinr[1]*vtemp + cosr[1]*v[5]; + vtemp = v[7]; + v[7] = cosr[1]*vtemp + sinr[1]*v[8]; + v[8] = -sinr[1]*vtemp + cosr[1]*v[8]; + + // if(debug)System.out.println("\n*********************** iteration #"+k+" ***********************\n"); + + qr_m[0] = s[0]; qr_m[1] = e[0]; qr_m[2] = 0.0; + qr_m[3] = 0.0; qr_m[4] = s[1]; qr_m[5] =e[1]; + qr_m[6] = 0.0; qr_m[7] = 0.0; qr_m[8] =s[2]; + + if( Math.abs(e[1]) < CONVERGE_TOL || Math.abs(e[0]) < CONVERGE_TOL) converged = true; + } + + if( Math.abs(e[1]) < CONVERGE_TOL ) { + compute_2X2( s[0],e[0],s[1],s,sinl,cosl,sinr,cosr, 0); + + utemp = u[0]; + u[0] = cosl[0]*utemp + sinl[0]*u[3]; + u[3] = -sinl[0]*utemp + cosl[0]*u[3]; + utemp = u[1]; + u[1] = cosl[0]*utemp + sinl[0]*u[4]; + u[4] = -sinl[0]*utemp + cosl[0]*u[4]; + utemp = u[2]; + u[2] = cosl[0]*utemp + sinl[0]*u[5]; + u[5] = -sinl[0]*utemp + cosl[0]*u[5]; + + // update v matrices + + vtemp = v[0]; + v[0] = cosr[0]*vtemp + sinr[0]*v[1]; + v[1] = -sinr[0]*vtemp + cosr[0]*v[1]; + vtemp = v[3]; + v[3] = cosr[0]*vtemp + sinr[0]*v[4]; + v[4] = -sinr[0]*vtemp + cosr[0]*v[4]; + vtemp = v[6]; + v[6] = cosr[0]*vtemp + sinr[0]*v[7]; + v[7] = -sinr[0]*vtemp + cosr[0]*v[7]; + } else { + compute_2X2( s[1],e[1],s[2],s,sinl,cosl,sinr,cosr,1); + + utemp = u[3]; + u[3] = cosl[0]*utemp + sinl[0]*u[6]; + u[6] = -sinl[0]*utemp + cosl[0]*u[6]; + utemp = u[4]; + u[4] = cosl[0]*utemp + sinl[0]*u[7]; + u[7] = -sinl[0]*utemp + cosl[0]*u[7]; + utemp = u[5]; + u[5] = cosl[0]*utemp + sinl[0]*u[8]; + u[8] = -sinl[0]*utemp + cosl[0]*u[8]; + + // update v matrices + + vtemp = v[1]; + v[1] = cosr[0]*vtemp + sinr[0]*v[2]; + v[2] = -sinr[0]*vtemp + cosr[0]*v[2]; + vtemp = v[4]; + v[4] = cosr[0]*vtemp + sinr[0]*v[5]; + v[5] = -sinr[0]*vtemp + cosr[0]*v[5]; + vtemp = v[7]; + v[7] = cosr[0]*vtemp + sinr[0]*v[8]; + v[8] = -sinr[0]*vtemp + cosr[0]*v[8]; + } + + return(0); + } + + static final double max( double a, double b) { + return ( a > b ? a : b); + } + + static final double min( double a, double b) { + return ( a < b ? a : b); + } + + static final double d_sign(double a, double b) { + double x = (a >= 0 ? a : - a); + return( b >= 0 ? x : -x); + } + + static final double compute_shift( double f, double g, double h) { + double d__1, d__2; + double fhmn, fhmx, c, fa, ga, ha, as, at, au; + double ssmin; + + fa = Math.abs(f); + ga = Math.abs(g); + ha = Math.abs(h); + fhmn = min(fa,ha); + fhmx = max(fa,ha); + if (fhmn == 0.) { + ssmin = 0.; + if (fhmx == 0.) { + } else { + d__1 = min(fhmx,ga) / max(fhmx,ga); + } + } else { + if (ga < fhmx) { + as = fhmn / fhmx + 1.; + at = (fhmx - fhmn) / fhmx; + d__1 = ga / fhmx; + au = d__1 * d__1; + c = 2. / (Math.sqrt(as * as + au) + Math.sqrt(at * at + au)); + ssmin = fhmn * c; + } else { + au = fhmx / ga; + if (au == 0.) { + + + ssmin = fhmn * fhmx / ga; + } else { + as = fhmn / fhmx + 1.; + at = (fhmx - fhmn) / fhmx; + d__1 = as * au; + d__2 = at * au; + c = 1. / (Math.sqrt(d__1 * d__1 + 1.) + Math.sqrt(d__2 * d__2 + 1.)); + ssmin = fhmn * c * au; + ssmin += ssmin; + } + } + } + + return(ssmin); + } + + static int compute_2X2( double f, double g, double h, double[] single_values, + double[] snl, double[] csl, double[] snr, double[] csr, int index) { + + double c_b3 = 2.; + double c_b4 = 1.; + + double d__1; + int pmax; + double temp; + boolean swap; + double a, d, l, m, r, s, t, tsign, fa, ga, ha; + double ft, gt, ht, mm; + boolean gasmal; + double tt, clt, crt, slt, srt; + double ssmin,ssmax; + + ssmax = single_values[0]; + ssmin = single_values[1]; + clt = 0.0; + crt = 0.0; + slt = 0.0; + srt = 0.0; + tsign = 0.0; + + ft = f; + fa = Math.abs(ft); + ht = h; + ha = Math.abs(h); + + pmax = 1; + if( ha > fa) + swap = true; + else + swap = false; + + if (swap) { + pmax = 3; + temp = ft; + ft = ht; + ht = temp; + temp = fa; + fa = ha; + ha = temp; + + } + gt = g; + ga = Math.abs(gt); + if (ga == 0.) { + + single_values[1] = ha; + single_values[0] = fa; + clt = 1.; + crt = 1.; + slt = 0.; + srt = 0.; + } else { + gasmal = true; + + if (ga > fa) { + pmax = 2; + if (fa / ga < EPS) { + + gasmal = false; + ssmax = ga; + if (ha > 1.) { + ssmin = fa / (ga / ha); + } else { + ssmin = fa / ga * ha; + } + clt = 1.; + slt = ht / gt; + srt = 1.; + crt = ft / gt; + } + } + if (gasmal) { + + d = fa - ha; + if (d == fa) { + + l = 1.; + } else { + l = d / fa; + } + + m = gt / ft; + + t = 2. - l; + + mm = m * m; + tt = t * t; + s = Math.sqrt(tt + mm); + + if (l == 0.) { + r = Math.abs(m); + } else { + r = Math.sqrt(l * l + mm); + } + + a = (s + r) * .5; + + if (ga > fa) { + pmax = 2; + if (fa / ga < EPS) { + + gasmal = false; + ssmax = ga; + if (ha > 1.) { + ssmin = fa / (ga / ha); + } else { + ssmin = fa / ga * ha; + } + clt = 1.; + slt = ht / gt; + srt = 1.; + crt = ft / gt; + } + } + if (gasmal) { + + d = fa - ha; + if (d == fa) { + + l = 1.; + } else { + l = d / fa; + } + + m = gt / ft; + + t = 2. - l; + + mm = m * m; + tt = t * t; + s = Math.sqrt(tt + mm); + + if (l == 0.) { + r = Math.abs(m); + } else { + r = Math.sqrt(l * l + mm); + } + + a = (s + r) * .5; + + + ssmin = ha / a; + ssmax = fa * a; + if (mm == 0.) { + + if (l == 0.) { + t = d_sign(c_b3, ft) * d_sign(c_b4, gt); + } else { + t = gt / d_sign(d, ft) + m / t; + } + } else { + t = (m / (s + t) + m / (r + l)) * (a + 1.); + } + l = Math.sqrt(t * t + 4.); + crt = 2. / l; + srt = t / l; + clt = (crt + srt * m) / a; + slt = ht / ft * srt / a; + } + } + if (swap) { + csl[0] = srt; + snl[0] = crt; + csr[0] = slt; + snr[0] = clt; + } else { + csl[0] = clt; + snl[0] = slt; + csr[0] = crt; + snr[0] = srt; + } + + if (pmax == 1) { + tsign = d_sign(c_b4, csr[0]) * d_sign(c_b4, csl[0]) * d_sign(c_b4, f); + } + if (pmax == 2) { + tsign = d_sign(c_b4, snr[0]) * d_sign(c_b4, csl[0]) * d_sign(c_b4, g); + } + if (pmax == 3) { + tsign = d_sign(c_b4, snr[0]) * d_sign(c_b4, snl[0]) * d_sign(c_b4, h); + } + single_values[index] = d_sign(ssmax, tsign); + d__1 = tsign * d_sign(c_b4, f) * d_sign(c_b4, h); + single_values[index+1] = d_sign(ssmin, d__1); + + + } + return 0; + } + + static double compute_rot( double f, double g, double[] sin, double[] cos, int index, int first) { + int i__1; + double d__1, d__2; + double cs,sn; + int i; + double scale; + int count; + double f1, g1; + double r; + final double safmn2 = 2.002083095183101E-146; + final double safmx2 = 4.994797680505588E+145; + + if (g == 0.) { + cs = 1.; + sn = 0.; + r = f; + } else if (f == 0.) { + cs = 0.; + sn = 1.; + r = g; + } else { + f1 = f; + g1 = g; + scale = max(Math.abs(f1),Math.abs(g1)); + if (scale >= safmx2) { + count = 0; + while(scale >= safmx2) { + ++count; + f1 *= safmn2; + g1 *= safmn2; + scale = max(Math.abs(f1),Math.abs(g1)); + } + r = Math.sqrt(f1*f1 + g1*g1); + cs = f1 / r; + sn = g1 / r; + i__1 = count; + for (i = 1; i <= count; ++i) { + r *= safmx2; + } + } else if (scale <= safmn2) { + count = 0; + while(scale <= safmn2) { + ++count; + f1 *= safmx2; + g1 *= safmx2; + scale = max(Math.abs(f1),Math.abs(g1)); + } + r = Math.sqrt(f1*f1 + g1*g1); + cs = f1 / r; + sn = g1 / r; + i__1 = count; + for (i = 1; i <= count; ++i) { + r *= safmn2; + } + } else { + r = Math.sqrt(f1*f1 + g1*g1); + cs = f1 / r; + sn = g1 / r; + } + if (Math.abs(f) > Math.abs(g) && cs < 0.) { + cs = -cs; + sn = -sn; + r = -r; + } + } + sin[index] = sn; + cos[index] = cs; + return r; + + } + + static final private double max3( double[] values) { + if( values[0] > values[1] ) { + if( values[0] > values[2] ) + return(values[0]); + else + return(values[2]); + } else { + if( values[1] > values[2] ) + return(values[1]); + else + return(values[2]); + } + } + + + final private void computeScales(boolean forceSVD) { + + if(scales == null) + scales = new double[3]; + + if ((!forceSVD || ((dirtyBits & SVD_BIT) == 0)) && isAffine()) { + if (isCongruent()) { + if (((dirtyBits & RIGID_BIT) == 0) && + ((type & RIGID) != 0)) { + scales[0] = scales[1] = scales[2] = 1; + dirtyBits &= ~SCALE_BIT; + return; + } + scales[0] = scales[1] = scales[2] = + Math.sqrt(mat[0]*mat[0] + mat[4]*mat[4] + + mat[8]*mat[8]); + dirtyBits &= ~SCALE_BIT; + return; + } + if (isOrtho()) { + scales[0] = Math.sqrt(mat[0]*mat[0] + mat[4]*mat[4] + + mat[8]*mat[8]); + scales[1] = Math.sqrt(mat[1]*mat[1] + mat[5]*mat[5] + + mat[9]*mat[9]); + scales[2] = Math.sqrt(mat[2]*mat[2] + mat[6]*mat[6] + + mat[10]*mat[10]); + dirtyBits &= ~SCALE_BIT; + return; + } + } + // fall back to use SVD decomposition + if (rot == null) + rot = new double[9]; + + compute_svd(this, scales, rot); + dirtyBits &= ~ROTSCALESVD_DIRTY; + } + + final private void computeScaleRotation(boolean forceSVD) { + + if(rot == null) + rot = new double[9]; + + if(scales == null) + scales = new double[3]; + + if ((!forceSVD || ((dirtyBits & SVD_BIT) == 0)) && isAffine()) { + if (isCongruent()) { + if (((dirtyBits & RIGID_BIT) == 0) && + ((type & RIGID) != 0)) { + rot[0] = mat[0]; + rot[1] = mat[1]; + rot[2] = mat[2]; + rot[3] = mat[4]; + rot[4] = mat[5]; + rot[5] = mat[6]; + rot[6] = mat[8]; + rot[7] = mat[9]; + rot[8] = mat[10]; + scales[0] = scales[1] = scales[2] = 1; + dirtyBits &= (~ROTATION_BIT | ~SCALE_BIT); + return; + } + double s = Math.sqrt(mat[0]*mat[0] + mat[4]*mat[4] + mat[8]*mat[8]); + if (s == 0) { + compute_svd(this, scales, rot); + return; + } + scales[0] = scales[1] = scales[2] = s; + s = 1/s; + rot[0] = mat[0]*s; + rot[1] = mat[1]*s; + rot[2] = mat[2]*s; + rot[3] = mat[4]*s; + rot[4] = mat[5]*s; + rot[5] = mat[6]*s; + rot[6] = mat[8]*s; + rot[7] = mat[9]*s; + rot[8] = mat[10]*s; + dirtyBits &= (~ROTATION_BIT | ~SCALE_BIT); + return; + } + if (isOrtho()) { + double s; + + scales[0] = Math.sqrt(mat[0]*mat[0] + mat[4]*mat[4] + mat[8]*mat[8]); + scales[1] = Math.sqrt(mat[1]*mat[1] + mat[5]*mat[5] + mat[9]*mat[9]); + scales[2] = Math.sqrt(mat[2]*mat[2] + mat[6]*mat[6] + mat[10]*mat[10]); + + if ((scales[0] == 0) || (scales[1] == 0) || (scales[2] == 0)) { + compute_svd(this, scales, rot); + return; + } + s = 1/scales[0]; + rot[0] = mat[0]*s; + rot[3] = mat[4]*s; + rot[6] = mat[8]*s; + s = 1/scales[1]; + rot[1] = mat[1]*s; + rot[4] = mat[5]*s; + rot[7] = mat[9]*s; + s = 1/scales[2]; + rot[2] = mat[2]*s; + rot[5] = mat[6]*s; + rot[8] = mat[10]*s; + dirtyBits &= (~ROTATION_BIT | ~SCALE_BIT); + return; + } + } + // fall back to use SVD decomposition + compute_svd(this, scales, rot); + dirtyBits &= ~ROTSCALESVD_DIRTY; + } + + + final void getRotation(Transform3D t) { + if ((dirtyBits & ROTATION_BIT)!= 0) { + computeScaleRotation(false); + } + + t.mat[3] = t.mat[7] = t.mat[11] = t.mat[12] = t.mat[13] = + t.mat[14] = 0; + t.mat[15] = 1; + t.mat[0] = rot[0]; + t.mat[1] = rot[1]; + t.mat[2] = rot[2]; + t.mat[4] = rot[3]; + t.mat[5] = rot[4]; + t.mat[6] = rot[5]; + t.mat[8] = rot[6]; + t.mat[9] = rot[7]; + t.mat[10] = rot[8]; + + t.type = ORTHOGONAL | RIGID | CONGRUENT| AFFINE | ORTHO; + if ((dirtyBits & SVD_BIT) != 0) { + t.dirtyBits = CLASSIFY_BIT | ROTSCALESVD_DIRTY; + } else { + t.dirtyBits = CLASSIFY_BIT | ROTATION_BIT | SCALE_BIT; + } + } + + // somehow CanvasViewCache will directly modify mat[] + // instead of calling ortho(). So we need to reset dirty bit + final void setOrthoDirtyBit() { + dirtyBits = CLASSIFY_BIT | ROTSCALESVD_DIRTY; + type = 0; + } +} diff --git a/src/classes/share/javax/media/j3d/TransformGroup.java b/src/classes/share/javax/media/j3d/TransformGroup.java new file mode 100644 index 0000000..8895f1e --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransformGroup.java @@ -0,0 +1,180 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Group node that contains a transform. The TransformGroup node + * specifies a single spatial transformation, via a Transform3D + * object, that can position, orient, and scale all of its children. + * <P> + * The specified transformation must be affine. Further, if the + * TransformGroup node is used as an ancestor of a ViewPlatform node + * in the scene graph, the transformation must be congruent-only + * rotations, translations, and uniform scales are allowed in + * a direct path from a Locale to a ViewPlatform node. + * <P> + * Note: Even though arbitrary affine transformations are + * allowed, better performance will result if all matrices + * within a branch graph are congruent, containing only rotations + * translation, and uniform scale. + * <P> + * The effects of transformations in the scene graph are cumulative. + * The concatenation of the transformations of each TransformGroup in + * a direct path from the Locale to a Leaf node defines a composite + * model transformation (CMT) that takes points in that Leaf node's + * local coordinates and transforms them into Virtual World (Vworld) + * coordinates. This composite transformation is used to + * transform points, normals, and distances into Vworld coordinates. + * Points are transformed by the CMT. Normals are transformed by the + * inverse-transpose of the CMT. Distances are transformed by the scale + * of the CMT. In the case of a transformation containing a nonuniform + * scale or shear, the maximum scale value in + * any direction is used. This ensures, for example, that a transformed + * bounding sphere, which is specified as a point and a radius, + * continues to enclose all objects that are also transformed using + * a nonuniform scale. + * <P> + */ + +public class TransformGroup extends Group { + /** + * Specifies that the node allows access to + * its object's transform information. + */ + public static final int + ALLOW_TRANSFORM_READ = CapabilityBits.TRANSFORM_GROUP_ALLOW_TRANSFORM_READ; + + /** + * Specifies that the node allows writing + * its object's transform information. + */ + public static final int + ALLOW_TRANSFORM_WRITE = CapabilityBits.TRANSFORM_GROUP_ALLOW_TRANSFORM_WRITE; + + /** + * Constructs and initializes a TransformGroup using an + * identity transform. + */ + public TransformGroup() { + } + + /** + * Constructs and initializes a TransformGroup from + * the Transform passed. + * @param t1 the transform3D object + * @exception BadTransformException if the transform is not affine. + */ + public TransformGroup(Transform3D t1) { + if (!t1.isAffine()) { + throw new BadTransformException(J3dI18N.getString("TransformGroup0")); + } + + ((TransformGroupRetained)this.retained).setTransform(t1); + } + + /** + * Creates the retained mode TransformGroupRetained object that this + * TransformGroup object will point to. + */ + void createRetained() { + this.retained = new TransformGroupRetained(); + this.retained.setSource(this); + } + + /** + * Sets the transform component of this TransformGroup to the value of + * the passed transform. + * @param t1 the transform to be copied + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception BadTransformException if the transform is not affine. + */ + public void setTransform(Transform3D t1) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TRANSFORM_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TransformGroup1")); + + if (!t1.isAffine()) { + throw new BadTransformException(J3dI18N.getString("TransformGroup0")); + } + + ((TransformGroupRetained)this.retained).setTransform(t1); + } + + /** + * Copies the transform component of this TransformGroup into + * the passed transform object. + * @param t1 the transform object to be copied into + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void getTransform(Transform3D t1) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_TRANSFORM_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TransformGroup2")); + + ((TransformGroupRetained)this.retained).getTransform(t1); + } + + + /** + * Creates a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + TransformGroup tg = new TransformGroup(); + tg.duplicateNode(this, forceDuplicate); + return tg; + } + + + /** + * Copies all TransformGroup information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + Transform3D t = VirtualUniverse.mc.getTransform3D(null); + ((TransformGroupRetained) originalNode.retained).getTransform(t); + ((TransformGroupRetained) retained).setTransform(t); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, t); + } +} diff --git a/src/classes/share/javax/media/j3d/TransformGroupData.java b/src/classes/share/javax/media/j3d/TransformGroupData.java new file mode 100644 index 0000000..91a90e5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransformGroupData.java @@ -0,0 +1,23 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class TransformGroupData extends NodeData { + // per path node data + // TODO: replace per path mirror objects with node data + // TODO: move other TransfromGroup related data here + boolean switchDirty = false; + + // use for eliminate multiple updates and generate unique targets + boolean markedDirty = false; +} diff --git a/src/classes/share/javax/media/j3d/TransformGroupRetained.java b/src/classes/share/javax/media/j3d/TransformGroupRetained.java new file mode 100644 index 0000000..d09e1c8 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransformGroupRetained.java @@ -0,0 +1,1219 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * Group node that contains a transform. + */ + +class TransformGroupRetained extends GroupRetained implements TargetsInterface +{ + + /** + * The Transform value for the TransformGroup. + */ + Transform3D transform = new Transform3D(); + + /** + * The inverse of the transform + */ + Transform3D invTransform = null; + + /** + * The transpose of the inverse of the transform + */ + Transform3D normalTransform = null; + + /** + * The Transform value currently being used internally + */ + Transform3D currentTransform = new Transform3D(); + + /** + * localVworld values for children of this TG + */ + Transform3D[][] childLocalToVworld = null; + int[][] childLocalToVworldIndex = null; + + // working variable for children transforms + Transform3D[][] childTrans = null; + int[][] childTransIndex = null; + + + /** + * A bitmask of the types in targets + */ + int localTargetThreads = 0; + + // combined localTargetThreads and decendants' localTargetThreads + int targetThreads = 0; + + /** + * A list of WakeupOnTransformChange conditions for this Transform + */ + WakeupIndexedList transformChange = null; + + // The current list of child transform group nodes or link nodes + // under a transform group + ArrayList childTransformLinks = new ArrayList(1); + + // working area while compile + boolean needNormalsTransform = false; // true if normals transformation + // is needed to push this + // transform down to geometry + + // key which identifies a unique path from a + // locale to this transform group + HashKey currentKey = new HashKey(); + + boolean aboveAViewPlatform = false; + + // maximum transform level of all shared path + int maxTransformLevel = -1; + + // List of transform level, one per shared path + int transformLevels[] = null; + + // J3d copy. + CachedTargets[] j3dCTs = null; + + // User copy. + CachedTargets[] cachedTargets = null; + + // Contains per path data, TODO: move to NodeRetained + TransformGroupData[] perPathData = null; + + + /** + * The constructor + */ + TransformGroupRetained() { + this.nodeType = NodeRetained.TRANSFORMGROUP; + } + + /** + * Sets the transform component of this TransformGroup to the value of + * the passed transform. + * @param t1 the transform to be copied + */ + void setTransform(Transform3D t1) { + J3dMessage tchangeMessage = null; + int i, j; + Transform3D trans = null; + + if (staticTransform != null) { + // this writeable transformGroup has a static transform + // merged into this node + + trans = VirtualUniverse.mc.getTransform3D(staticTransform.transform); + trans.mul(t1); + + transform.setWithLock(trans); + + } else { + trans = VirtualUniverse.mc.getTransform3D(t1); + transform.setWithLock(t1); + } + + if (transformChange != null) { + notifyConditions(); + } + + if (source.isLive()) { + + if (aboveAViewPlatform && !t1.isCongruent()) { + throw new BadTransformException(J3dI18N.getString("ViewPlatformRetained0")); + } + + tchangeMessage = VirtualUniverse.mc.getMessage(); + tchangeMessage.type = J3dMessage.TRANSFORM_CHANGED; + tchangeMessage.threads = targetThreads; + tchangeMessage.args[1] = this; + tchangeMessage.args[2] = trans; + + tchangeMessage.universe = universe; + //System.out.println("TransformGroupRetained --- TRANSFORM_CHANGED " + this); + VirtualUniverse.mc.processMessage(tchangeMessage); + } + } + + /** + * Copies the transform component of this TransformGroup into + * the passed transform object. + * @param t1 the transform object to be copied into + */ + void getTransform(Transform3D t1) { + transform.getWithLock(t1); + + // if staticTransform exists for this node, need to + // redetermine the original user specified transform + + if (staticTransform != null) { + Transform3D invTransform = staticTransform.getInvTransform(); + t1.mul(invTransform, t1); + } + } + + + // get the inverse of the transform -- note: this method only + // supports static transform + + Transform3D getInvTransform() { + if (invTransform == null) { + invTransform = new Transform3D(transform); + invTransform.invert(); + } + return invTransform; + } + + + // get the inverse of the transpose -- note: this method only + // supports static transform, the translation component will + // not transform + Transform3D getNormalTransform() { + if (normalTransform == null) { + normalTransform = new Transform3D(transform); + normalTransform.invert(); + normalTransform.transpose(); + } + return normalTransform; + } + + // synchronized with TransformStructure + synchronized void setNodeData(SetLiveState s) { + int i; + + super.setNodeData(s); + + childTrans = new Transform3D[s.currentTransforms.length][2]; + childTransIndex = new int[s.currentTransforms.length][2]; + + for (i=0; i< s.currentTransforms.length; i++) { + childTrans[i][0] = new Transform3D(); + + childTrans[i][0].mul(s.currentTransforms[i] + [s.currentTransformsIndex[i] + [CURRENT_LOCAL_TO_VWORLD]], currentTransform); + childTrans[i][1] = new Transform3D(childTrans[i][0]); + childTransIndex[i][0] = 0; + childTransIndex[i][1] = 0; + + } + + if (!s.inSharedGroup) { + s.transformLevels[0] += 1; + maxTransformLevel = s.transformLevels[0]; + } else { + for (i=0; i<s.keys.length; i++) { + s.transformLevels[i] += 1; + if (s.transformLevels[i] > maxTransformLevel) { + maxTransformLevel = s.transformLevels[i]; + } + } + } + + if (!inSharedGroup) { + if (childLocalToVworld == null) { + // If the node is a transformGroup then need to keep + // the child transforms as well + childLocalToVworld = new Transform3D[1][]; + childLocalToVworldIndex = new int[1][]; + transformLevels = new int[1]; + // Use by TransformStructure + cachedTargets = new CachedTargets[1]; + perPathData = new TransformGroupData[1]; + } + childLocalToVworld[0] = childTrans[0]; + childLocalToVworldIndex[0] = childTransIndex[0]; + transformLevels[0] = s.transformLevels[0]; + + setAuxData(s, 0, 0); + } else { + + // For inSharedGroup case. + int j, len; + + if (childLocalToVworld == null) { + childLocalToVworld = new Transform3D[s.keys.length][]; + childLocalToVworldIndex = new int[s.keys.length][]; + transformLevels = new int[s.keys.length]; + cachedTargets = new CachedTargets[s.keys.length]; + perPathData = new TransformGroupData[s.keys.length]; + len=0; + } else { + + len = localToVworld.length - s.keys.length; + + int newLen = localToVworld.length; + + Transform3D newChildTList[][] = new Transform3D[newLen][]; + int newChildIndexList[][] = new int[newLen][]; + int newTransformLevels[] = new int[newLen]; + CachedTargets newTargets[] = new CachedTargets[newLen]; + TransformGroupData newPerPathData[] = new TransformGroupData[newLen]; + + System.arraycopy(childLocalToVworld, 0, + newChildTList, 0, childLocalToVworld.length); + System.arraycopy(childLocalToVworldIndex, 0, + newChildIndexList, 0, childLocalToVworldIndex.length); + System.arraycopy(transformLevels, 0, + newTransformLevels, 0, transformLevels.length); + + System.arraycopy(cachedTargets, 0, + newTargets, 0, cachedTargets.length); + + System.arraycopy(perPathData, 0, + newPerPathData, 0, perPathData.length); + + childLocalToVworld = newChildTList; + childLocalToVworldIndex = newChildIndexList; + transformLevels = newTransformLevels; + cachedTargets = newTargets; + perPathData = newPerPathData; + } + + int hkIndex; + int hkIndexPlus1, blkSize; + + for(i=len, j=0; i<localToVworld.length; i++, j++) { + hkIndex = s.keys[j].equals(localToVworldKeys, 0, + localToVworldKeys.length); + + if(hkIndex < 0) { + System.out.println("Can't Find matching hashKey in setNodeData."); + System.out.println("We're in TROUBLE!!!"); + break; + } else if(hkIndex >= i) { // Append to last. + childLocalToVworld[i] = childTrans[j]; + childLocalToVworldIndex[i] = childTransIndex[j]; + transformLevels[i] = s.transformLevels[j]; + } else { + hkIndexPlus1 = hkIndex + 1; + blkSize = i - hkIndex; + + System.arraycopy(childLocalToVworld, hkIndex, + childLocalToVworld, hkIndexPlus1, blkSize); + + System.arraycopy(childLocalToVworldIndex, hkIndex, + childLocalToVworldIndex, hkIndexPlus1, blkSize); + + System.arraycopy(transformLevels, hkIndex, + transformLevels, hkIndexPlus1, blkSize); + + System.arraycopy(cachedTargets, hkIndex, + cachedTargets, hkIndexPlus1, blkSize); + + System.arraycopy(perPathData, hkIndex, + perPathData, hkIndexPlus1, blkSize); + + childLocalToVworld[hkIndex] = childTrans[j]; + childLocalToVworldIndex[hkIndex] = childTransIndex[j]; + transformLevels[hkIndex] = s.transformLevels[j]; + } + + setAuxData(s, j, hkIndex); + } + } + if (s.childTransformLinks != null) { + // do not duplicate shared nodes + synchronized(s.childTransformLinks) { + if(!inSharedGroup || !s.childTransformLinks.contains(this)) { + s.childTransformLinks.add(this); + } + } + } + + s.localToVworld = childLocalToVworld; + s.localToVworldIndex = childLocalToVworldIndex; + s.currentTransforms = childTrans; + s.currentTransformsIndex = childTransIndex; + + s.childTransformLinks = childTransformLinks; + s.parentTransformLink = this; + } + + void setAuxData(SetLiveState s, int index, int hkIndex) { + super.setAuxData(s, index, hkIndex); + perPathData[hkIndex] = new TransformGroupData(); + perPathData[hkIndex].switchState = + (SwitchState)s.switchStates.get(hkIndex); + } + + + // Add a WakeupOnTransformChange to the list + void removeCondition(WakeupOnTransformChange wakeup) { + synchronized (transformChange) { + transformChange.remove(wakeup); + } + } + + // Add a WakeupOnTransformChange to the list + void addCondition(WakeupOnTransformChange wakeup) { + synchronized (transformChange) { + transformChange.add(wakeup); + } + } + + void notifyConditions() { + synchronized (transformChange) { + WakeupOnTransformChange list[] = (WakeupOnTransformChange []) + transformChange.toArray(false); + for (int i=transformChange.size()-1; i >=0; i--) { + list[i].setTriggered(); + } + } + } + + boolean isStatic() { + if (!super.isStatic() || + source.getCapability(TransformGroup.ALLOW_TRANSFORM_READ) || + source.getCapability(TransformGroup.ALLOW_TRANSFORM_WRITE)) { + return false; + } else { + return true; + } + } + + void mergeTransform(TransformGroupRetained xform) { + super.mergeTransform(xform); + transform.mul(xform.transform, transform); + } + + void traverse(boolean sameLevel, int level) { + + System.out.println(); + for (int i = 0; i < level; i++) { + System.out.print("."); + } + System.out.print(this); + + if (isStatic()) { + System.out.print(" (s)"); + } else { + System.out.print(" (w)"); + } + System.out.println(); + System.out.println(transform.toString()); + super.traverse(true, level); + } + + void compile(CompileState compState) { + + // save and reset the keepTG and needNormalsTransform flags + + boolean saveKeepTG = compState.keepTG; + compState.keepTG = false; + + boolean saveNeedNormalsTransform = compState.needNormalsTransform; + compState.needNormalsTransform = false; + + super.compile(compState); + + if (compState.keepTG) { + // keep this transform group, don't merge it + + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + } + + if (J3dDebug.devPhase && J3dDebug.debug) { + compState.numTransformGroups++; + if (isStatic()) + compState.numStaticTransformGroups++; + if (mergeFlag == SceneGraphObjectRetained.MERGE) + compState.numMergedTransformGroups++; + } + + if (mergeFlag == SceneGraphObjectRetained.DONT_MERGE) { + // a non-mergeable TG will trigger a merge of its subtree + + compState.staticTransform = null; + compState.parentGroup = null; + super.merge(compState); + + } else { + // flag this TG as to be merged later on + mergeFlag = SceneGraphObjectRetained.MERGE; + } + + // restore compile state + compState.keepTG = saveKeepTG; + this.needNormalsTransform = compState.needNormalsTransform; + compState.needNormalsTransform = saveNeedNormalsTransform; + } + + void merge(CompileState compState) { + + TransformGroupRetained saveStaticTransform; + + // merge the transforms + if (compState.staticTransform != null) { + staticTransform = compState.staticTransform; + mergeTransform(compState.staticTransform); + } + + if (mergeFlag == SceneGraphObjectRetained.MERGE) { + + // before we push down the static transform, check + // to see if the transform will be pushed down to shapes + // with geometry_with_normals and if so, check if + // the normal transform has uniform scale or not. If + // it doesn't, don't push it down. + + if (this.needNormalsTransform) { + Transform3D normalXform = this.getNormalTransform(); + if (!normalXform.isCongruent()) { + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + } + } + } + + if (mergeFlag == SceneGraphObjectRetained.MERGE) { + saveStaticTransform = compState.staticTransform; + compState.staticTransform = this; + + // go to the merge method of the group node to start + // pushing down the static transform until it hits + // a leaf or a subtree which is already merged. + super.merge(compState); + + // reset the compile state + compState.staticTransform = saveStaticTransform; + + } else { + compState.parentGroup.compiledChildrenList.add(this); + parent = compState.parentGroup; + } + + mergeFlag = SceneGraphObjectRetained.MERGE_DONE; + } + + /** + * This setlive simply concatinates it's transform onto all the ones + * passed in. + */ + void setLive(SetLiveState s) { + int i,j; + Transform3D trans = null; + Targets[] newTargets = null; + Targets[] savedTransformTargets = null; + int oldTraverseFlags = 0; + int len; + Object obj; + + // TODO - optimization for targetThreads computation, require + // cleanup in GroupRetained.doSetLive() + //int savedTargetThreads = 0; + //savedTargetThreads = s.transformTargetThreads; + //s.transformTargetThreads = 0; + + oldTraverseFlags = s.traverseFlags; + + savedTransformTargets = s.transformTargets; + + int numPaths = (s.inSharedGroup)? s.keys.length : 1; + newTargets = new Targets[numPaths]; + for(i=0; i<numPaths; i++) { + newTargets[i] = new Targets(); + } + + s.transformTargets = newTargets; + s.traverseFlags = 0; + + // This is needed b/c super.setlive is called after inSharedGroup check. + inSharedGroup = s.inSharedGroup; + + trans = VirtualUniverse.mc.getTransform3D(null); + transform.getWithLock(trans); + currentTransform.set(trans); + + + ArrayList savedChildTransformLinks = s.childTransformLinks; + GroupRetained savedParentTransformLink = s.parentTransformLink; + Transform3D[][] oldCurrentList = s.currentTransforms; + int[][] oldCurrentIndexList = s.currentTransformsIndex; + + + super.doSetLive(s); + + + if (! inSharedGroup) { + if (s.transformTargets[0] != null) { + cachedTargets[0] = s.transformTargets[0].snapShotInit(); + } + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.GRP_TARGETS); + } + } else { + int hkIndex; + for(i=0; i<numPaths; i++) { + if (s.transformTargets[i] != null) { + hkIndex = s.keys[i].equals(localToVworldKeys, 0, + localToVworldKeys.length); + cachedTargets[hkIndex] = s.transformTargets[i].snapShotInit(); + } + if (s.switchTargets != null && + s.switchTargets[i] != null) { + s.switchTargets[i].addNode(this, Targets.GRP_TARGETS); + } + } + } + + // Assign data in cachedTargets to j3dCTs. + j3dCTs = new CachedTargets[cachedTargets.length]; + copyCachedTargets(TargetsInterface.TRANSFORM_TARGETS, j3dCTs); + + computeTargetThreads(TargetsInterface.TRANSFORM_TARGETS, cachedTargets); + + // restore setLiveState from it's local variables. + // setNodeData did keep a reference to these variables. + s.localToVworld = localToVworld; + s.localToVworldIndex = localToVworldIndex; + s.currentTransforms = oldCurrentList; + s.currentTransformsIndex = oldCurrentIndexList; + + s.childTransformLinks = savedChildTransformLinks; + s.parentTransformLink = savedParentTransformLink; + + s.transformTargets = savedTransformTargets; + + if (!s.inSharedGroup) { + s.transformLevels[0] -= 1; + } else { + for (i=0; i<s.keys.length; i++) { + s.transformLevels[i] -= 1; + } + } + + + if ((s.traverseFlags & NodeRetained.CONTAINS_VIEWPLATFORM) != 0) { + aboveAViewPlatform = true; + } + s.traverseFlags |= oldTraverseFlags; + + if (aboveAViewPlatform && !trans.isCongruent()) { + throw new BadTransformException(J3dI18N.getString("ViewPlatformRetained0")); + } + + super.markAsLive(); + } + + + /** + * remove the localToVworld transform for a transformGroup + */ + void removeNodeData(SetLiveState s) { + + synchronized (this) { // synchronized with TransformStructure + + if (refCount <= 0) { + childLocalToVworld = null; + childLocalToVworldIndex = null; + transformLevels = null; + // only use by TransformStruct. + cachedTargets = null; + perPathData = null; + targetThreads = 0; + + + if (parentTransformLink != null) { + ArrayList obj; + if (parentTransformLink + instanceof TransformGroupRetained) { + obj = ((TransformGroupRetained) + parentTransformLink).childTransformLinks; + } else { + obj = ((SharedGroupRetained) + parentTransformLink).childTransformLinks; + } + synchronized(obj) { + obj.remove(this); + } + } + aboveAViewPlatform = false; + } + else { + int i, index, len; + // Remove the localToVworld key + int newLen = localToVworld.length - s.keys.length; + + Transform3D[][] newChildTList = new Transform3D[newLen][]; + int[][] newChildIndexList = new int[newLen][]; + int[] newTransformLevels = new int[newLen]; + ArrayList[] newChildPTG = new ArrayList[newLen]; + CachedTargets[] newTargets = new CachedTargets[newLen]; + TransformGroupData[] newPerPathData = new TransformGroupData[newLen]; + + int[] tempIndex = new int[s.keys.length]; + int curStart =0, newStart =0; + boolean found = false; + for(i=0;i<s.keys.length;i++) { + index = s.keys[i].equals(localToVworldKeys, 0, localToVworldKeys.length); + + tempIndex[i] = index; + + if(index >= 0) { + found = true; + + if(index == curStart) { + curStart++; + } + else { + len = index - curStart; + System.arraycopy(childLocalToVworld, curStart, newChildTList, + newStart, len); + System.arraycopy(childLocalToVworldIndex, curStart, newChildIndexList, + newStart, len); + System.arraycopy(transformLevels, curStart, newTransformLevels, + newStart, len); + System.arraycopy(cachedTargets, curStart, newTargets, newStart, len); + System.arraycopy(perPathData, curStart, + newPerPathData, newStart, len); + + curStart = index+1; + newStart = newStart + len; + } + } + else { + found = false; + System.out.println("TG.removeNodeData-Can't find matching hashKey."); + System.out.println("We're in TROUBLE!!!"); + } + } + + if((found == true) && (curStart < localToVworld.length)) { + len = localToVworld.length - curStart; + System.arraycopy(childLocalToVworld, curStart, newChildTList, + newStart, len); + System.arraycopy(childLocalToVworldIndex, curStart, newChildIndexList, + newStart, len); + System.arraycopy(transformLevels, curStart, newTransformLevels, + newStart, len); + System.arraycopy(cachedTargets, curStart, newTargets, newStart, len); + System.arraycopy(perPathData, curStart, + newPerPathData, newStart, len); + } + + childLocalToVworld = newChildTList; + childLocalToVworldIndex = newChildIndexList; + transformLevels = newTransformLevels; + cachedTargets = newTargets; + perPathData = newPerPathData; + } + super.removeNodeData(s); + // Set it back to its parent localToVworld data. + // This is b/c the parent has changed it localToVworld data arrays. + s.localToVworld = childLocalToVworld; + s.localToVworldIndex = childLocalToVworldIndex; + } + } + + void clearLive(SetLiveState s) { + + Targets[] savedTransformTargets = null; + + savedTransformTargets = s.transformTargets; + // no need to gather targets from tg in clear live + s.transformTargets = null; + + super.clearLive(s); + + // restore setLiveState from it's local variables. + // removeNodeData has altered these variables. + s.localToVworld = localToVworld; + s.localToVworldIndex = localToVworldIndex; + s.transformTargets = savedTransformTargets; + + synchronized (this) { // synchronized with TransformStructure + if (inSharedGroup) { + if (transformLevels != null) { + maxTransformLevel = transformLevels[0]; + for (int i=1; i<transformLevels.length; i++) { + if (transformLevels[i] > maxTransformLevel) { + maxTransformLevel = transformLevels[i]; + } + } + } else { + maxTransformLevel = -1; + } + + if (s.switchTargets != null) { + for (int i=0; i<s.switchTargets.length; i++) { + if (s.switchTargets[i] != null) { + s.switchTargets[i].addNode(this, Targets.GRP_TARGETS); + } + } + } + + } else { + maxTransformLevel = -1; + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.GRP_TARGETS); + } + } + } + // TODO: recontruct targetThreads + } + + + void computeCombineBounds(Bounds bounds) { + NodeRetained child; + BoundingSphere boundingSphere = new BoundingSphere(); + boundingSphere.setRadius(-1.0); + + if(boundsAutoCompute) { + for (int i=children.size()-1; i>=0; i--) { + child = (NodeRetained)children.get(i); + if(child != null) + child.computeCombineBounds(boundingSphere); + } + } + else { + // Should this be lock too ? ( MT safe ? ) + synchronized(localBounds) { + boundingSphere.set(localBounds); + } + } + + // Should this be lock too ? ( MT safe ? ) + // Thoughts : + // Make a temp copy with lock : transform.getWithLock(trans);, but this will cause gc ... + synchronized(transform) { + boundingSphere.transform(transform); + } + bounds.combine(boundingSphere); + + } + + void processChildLocalToVworld(ArrayList dirtyTransformGroups, + ArrayList keySet, + UpdateTargets targets, + ArrayList blUsers) { + + synchronized(this) { // sync with setLive/clearLive + + if (inSharedGroup) { + if (localToVworldKeys != null) { + for(int j=0; j<localToVworldKeys.length; j++) { + if (perPathData[j].markedDirty) { + updateChildLocalToVworld(localToVworldKeys[j], j, + dirtyTransformGroups, + keySet, targets, + blUsers); + } else { + //System.out.println("tg.procChild markedDiry skip"); + } + } + } + } else { + if (perPathData != null && perPathData[0].markedDirty) { + updateChildLocalToVworld(dirtyTransformGroups, keySet, + targets, blUsers); + } else { + //System.out.println("tg.procChild markedDiry skip"); + } + } + } + } + + // for shared case + void updateChildLocalToVworld(HashKey key, int index, + ArrayList dirtyTransformGroups, + ArrayList keySet, + UpdateTargets targets, + ArrayList blUsers) { + + int i, j; + Object obj; + Transform3D lToVw, childLToVw; + TransformGroupRetained tg; + LinkRetained ln; + CachedTargets ct; + + synchronized(this) { // sync with setLive/clearLive + + if (localToVworld != null) { + perPathData[index].markedDirty = false; + // update immediate child's localToVworld + + if (perPathData[index].switchState.currentSwitchOn ) { + lToVw = getCurrentLocalToVworld(index); + childLToVw = getUpdateChildLocalToVworld(index); + childLToVw.mul(lToVw, currentTransform); + dirtyTransformGroups.add(this); + keySet.add(key); + ct = j3dCTs[index]; + if (ct != null) { + targets.addCachedTargets(ct); + if (ct.targetArr[Targets.BLN_TARGETS] != null) { + gatherBlUsers(blUsers, + ct.targetArr[Targets.BLN_TARGETS]); + } + } + } else { + perPathData[index].switchDirty = true; + //System.out.println("tg.updateChild skip"); + } + + + + // update child's localToVworld of its children + // transformLink may contain link nodes + synchronized(childTransformLinks) { + for (i=0; i<childTransformLinks.size(); i++) { + obj = childTransformLinks.get(i); + + if (obj instanceof TransformGroupRetained) { + tg = (TransformGroupRetained)obj; + tg.updateChildLocalToVworld( + tg.localToVworldKeys[index], + index, dirtyTransformGroups, keySet, + targets, blUsers); + } else { // LinkRetained + ln = (LinkRetained)obj; + currentKey.set(localToVworldKeys[index]); + currentKey.append(LinkRetained.plus).append(ln.nodeId); + if ((ln.sharedGroup != null) && + (ln.sharedGroup.localToVworldKeys != null)) { + j = currentKey.equals(ln.sharedGroup.localToVworldKeys,0, + ln.sharedGroup.localToVworldKeys.length); + if(j < 0) { + System.out. + println("TransformGroupRetained : Can't find hashKey"); + } + + if (j < ln.sharedGroup.localToVworldKeys.length) { + ln.sharedGroup. + updateChildLocalToVworld(ln.sharedGroup. + localToVworldKeys[j], j, + dirtyTransformGroups, keySet, + targets, blUsers); + } + } + } + } + } + } + } + } + + // for non-shared case + void updateChildLocalToVworld(ArrayList dirtyTransformGroups, + ArrayList keySet, + UpdateTargets targets, + ArrayList blUsers) { + int i, j; + Object obj; + Transform3D lToVw, childLToVw; + TransformGroupRetained tg; + LinkRetained ln; + CachedTargets ct; + + synchronized(this) { // sync with setLive/clearLive + + if (localToVworld != null) { + perPathData[0].markedDirty = false; + // update immediate child's localToVworld + + if (perPathData[0].switchState.currentSwitchOn ) { + lToVw = getCurrentLocalToVworld(0); + childLToVw = getUpdateChildLocalToVworld(0); + childLToVw.mul(lToVw, currentTransform); + dirtyTransformGroups.add(this); + ct = j3dCTs[0]; + if (ct != null) { + targets.addCachedTargets(ct); + if (ct.targetArr[Targets.BLN_TARGETS] != null) { + gatherBlUsers(blUsers, + ct.targetArr[Targets.BLN_TARGETS]); + } + } + } else { + perPathData[0].switchDirty = true; + //System.out.println("tg.updateChild skip"); + } + + + // update child's localToVworld of its children + // transformLink contains top level transform group nodes + // and link nodes + synchronized(childTransformLinks) { + for (i=0; i<childTransformLinks.size(); i++) { + obj = childTransformLinks.get(i); + + if (obj instanceof TransformGroupRetained) { + tg = (TransformGroupRetained)obj; + tg.updateChildLocalToVworld(dirtyTransformGroups, + keySet, targets, blUsers); + + } else { // LinkRetained + ln = (LinkRetained)obj; + currentKey.reset(); + currentKey.append(locale.nodeId); + currentKey.append(LinkRetained.plus).append(ln.nodeId); + if ((ln.sharedGroup != null) && + (ln.sharedGroup.localToVworldKeys != null)) { + j = currentKey.equals(ln.sharedGroup.localToVworldKeys,0, + ln.sharedGroup.localToVworldKeys.length); + if(j < 0) { + System.out. + println("TransformGroupRetained : Can't find hashKey"); + } + + if (j<ln.sharedGroup.localToVworldKeys.length) { + ln.sharedGroup. + updateChildLocalToVworld( + ln.sharedGroup. + localToVworldKeys[j], + j, dirtyTransformGroups, + keySet, targets, blUsers); + } + } + } + } + } + } + } + } + + /** + * Transform the input bound by the current LocalToVWorld, this + * one overwrite the one defined in NodeRetained since for + * TransformGroup, it has to use currentChildLocalToVworld + * instead of currentLocalToVworld + */ + void transformBounds(SceneGraphPath path, Bounds bound) { + if (!((NodeRetained) path.item.retained).inSharedGroup) { + bound.transform(getCurrentChildLocalToVworld()); + } else { + HashKey key = new HashKey(""); + path.getHashKey(key); + bound.transform(getCurrentChildLocalToVworld(key)); + } + } + + + /** + * get the to be updated child localToVworld + */ + Transform3D getUpdateChildLocalToVworld(int index) { + int currentIndex = childLocalToVworldIndex[index][NodeRetained.CURRENT_LOCAL_TO_VWORLD]; + + if (currentIndex == childLocalToVworldIndex[index][NodeRetained.LAST_LOCAL_TO_VWORLD]) { + currentIndex = currentIndex ^ 1; + childLocalToVworldIndex[index][NodeRetained.CURRENT_LOCAL_TO_VWORLD] = currentIndex; + } + return childLocalToVworld[index][currentIndex]; + } + + + /** + * Get the current child localToVworld transform for a node + */ + Transform3D getCurrentChildLocalToVworld() { + return getCurrentChildLocalToVworld(0); + } + + Transform3D getCurrentChildLocalToVworld(int index) { + return childLocalToVworld[index][childLocalToVworldIndex[index][NodeRetained.CURRENT_LOCAL_TO_VWORLD]]; + } + + Transform3D getCurrentChildLocalToVworld(HashKey key) { + if (!inSharedGroup) { + return childLocalToVworld[0][childLocalToVworldIndex[0][NodeRetained.CURRENT_LOCAL_TO_VWORLD]]; + } else { + int i = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + if(i>= 0) { + return childLocalToVworld[i] + [childLocalToVworldIndex[i][NodeRetained.CURRENT_LOCAL_TO_VWORLD]]; + } + } + return new Transform3D(); + } + + + /** + * Get the last child localToVworld transform for a node + */ + Transform3D getLastChildLocalToVworld(HashKey key) { + + if (!inSharedGroup) { + return childLocalToVworld[0][childLocalToVworldIndex[0][NodeRetained.LAST_LOCAL_TO_VWORLD]]; + } else { + int i = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + if(i>= 0) { + return childLocalToVworld[i] + [childLocalToVworldIndex[i][NodeRetained.LAST_LOCAL_TO_VWORLD]]; + } + } + return new Transform3D(); + } + + // **************************** + // TargetsInterface methods + // **************************** + + public int getTargetThreads(int type) { + // type is ignored here, only need for SharedGroup + if (type == TargetsInterface.TRANSFORM_TARGETS) { + return targetThreads; + } else { + System.out.println("getTargetsThreads: wrong arguments"); + return -1; + } + } + + public CachedTargets getCachedTargets(int type, int index, int child) { + // type is ignored here, only need for SharedGroup + // child is ignored here + if (type == TargetsInterface.TRANSFORM_TARGETS) { + return cachedTargets[index]; + } else { + System.out.println("getCachedTargets: wrong arguments"); + return null; + } + } + + TargetsInterface getClosestTargetsInterface(int type) { + return (type == TargetsInterface.TRANSFORM_TARGETS)? + (TargetsInterface)this: + (TargetsInterface)parentSwitchLink; + } + + // re-evalute localTargetThreads using newCachedTargets and + // re-evaluate targetThreads + public void computeTargetThreads(int type, + CachedTargets[] newCachedTargets) { + + // type is ignored here, only need for SharedGroup + if (type == TargetsInterface.TRANSFORM_TARGETS) { + localTargetThreads = J3dThread.UPDATE_TRANSFORM; + + for(int i=0; i<newCachedTargets.length; i++) { + if (newCachedTargets[i] != null) { + localTargetThreads |= newCachedTargets[i].computeTargetThreads(); + } + } + targetThreads = localTargetThreads; + + int numLinks = childTransformLinks.size(); + TargetsInterface childLink; + NodeRetained node; + for(int i=0; i<numLinks; i++) { + + node = (NodeRetained)childTransformLinks.get(i); + if (node.nodeType == NodeRetained.LINK) { + childLink = (TargetsInterface) + ((LinkRetained)node).sharedGroup; + } else { + childLink = (TargetsInterface) node; + } + if (childLink != null) { + targetThreads |= + childLink.getTargetThreads(TargetsInterface.TRANSFORM_TARGETS); + } + } + } else { + System.out.println("computeTargetsThreads: wrong arguments"); + } + + } + + // re-compute localTargetThread, targetThreads and + // propagate changes to ancestors + public void updateTargetThreads(int type, + CachedTargets[] newCachedTargets) { + // type is ignored here, only need for SharedGroup + if (type == TargetsInterface.TRANSFORM_TARGETS) { + computeTargetThreads(type, newCachedTargets); + if (parentTransformLink != null) { + TargetsInterface pti = (TargetsInterface)parentTransformLink; + pti.propagateTargetThreads(TargetsInterface.TRANSFORM_TARGETS, + targetThreads); + } + } else { + System.out.println("updateTargetThreads: wrong arguments"); + } + } + + // re-evaluate targetThreads using childTargetThreads and + // propagate changes to ancestors + public void propagateTargetThreads(int type, int childTargetThreads) { + // type is ignored here, only need for SharedGroup + + if (type == TargetsInterface.TRANSFORM_TARGETS) { + // TODO : For now we'll OR more than exact. + //targetThreads = localTargetThreads | childTargetThreads; + targetThreads = targetThreads | childTargetThreads; + if (parentTransformLink != null) { + TargetsInterface pti = (TargetsInterface)parentTransformLink; + pti.propagateTargetThreads(TargetsInterface.TRANSFORM_TARGETS, + targetThreads); + } + } else { + System.out.println("propagateTargetThreads: wrong arguments"); + } + } + + public void updateCachedTargets(int type, CachedTargets[] newCt) { + // type is ignored here, only need for SharedGroup + if (type == TargetsInterface.TRANSFORM_TARGETS) { + j3dCTs = newCt; + } else { + System.out.println("updateCachedTargets: wrong arguments"); + } + } + + public void copyCachedTargets(int type, CachedTargets[] newCt) { + // type is ignored here, only need for SharedGroup + if (type == TargetsInterface.TRANSFORM_TARGETS) { + int size = cachedTargets.length; + for (int i=0; i<size; i++) { + newCt[i] = cachedTargets[i]; + } + } else { + System.out.println("copyCachedTargets: wrong arguments"); + } + } + + public void resetCachedTargets(int type, + CachedTargets[] newCtArr, int child) { + // type is ignored here, only need for SharedGroup + // child is ignored here + if (type == TargetsInterface.TRANSFORM_TARGETS) { + cachedTargets = newCtArr; + } else { + System.out.println("resetCachedTargets: wrong arguments"); + } + } + + public ArrayList getTargetsData(int type, int index) { + // not used + return null; + } + + void childCheckSetLive(NodeRetained child, int childIndex, + SetLiveState s, NodeRetained linkNode) { + s.currentTransforms = childLocalToVworld; + s.currentTransformsIndex = childLocalToVworldIndex; + s.parentTransformLink = this; + s.childTransformLinks = childTransformLinks; + s.localToVworld = s.currentTransforms; + s.localToVworldIndex = s.currentTransformsIndex; + + child.setLive(s); + } +} diff --git a/src/classes/share/javax/media/j3d/TransformInterpolator.java b/src/classes/share/javax/media/j3d/TransformInterpolator.java new file mode 100644 index 0000000..93a8f93 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransformInterpolator.java @@ -0,0 +1,234 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; +/** + * TransformInterpolator is an abstract class that extends + * Interpolator to provide common methods used by various transform + * related interpolator subclasses. These include methods to set/get + * the target of TransformGroup, and set/get transform of axis. + * + * @since Java 3D 1.3 + */ + +public abstract class TransformInterpolator extends Interpolator { + /** + * The TransformGroup node affected by this transformInterpolator + */ + protected TransformGroup target = null; + + /** + * The transform that defines the local coordinate + */ + protected Transform3D axis = new Transform3D(); + + /** + * The inverse transform that defines the local coordinate + */ + protected Transform3D axisInverse = new Transform3D(); + + /** + * The transform which is passed into computeTransform() when computeTransform() + * is called implicitly from processStimulus() + */ + private Transform3D currentTransform = new Transform3D(); + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + + /** + * Constructs a TransformInterpolator node with a null alpha value and + * a null target of TransformGroup + */ + public TransformInterpolator() { + } + + /** + * Constructs a trivial transform interpolator with a specified alpha, + * a specified target and an default axis set to Identity. + * @param alpha The alpha object for this transform Interpolator + * @param target The target TransformGroup for this TransformInterpolator + */ + public TransformInterpolator(Alpha alpha, TransformGroup target) { + super(alpha); + this.target = target; + axis.setIdentity(); + axisInverse.setIdentity(); + } + /** + * Constructs a new transform interpolator that set an specified alpha, + * a specified targe and a specified axisOfTransform. + * @param alpha the alpha object for this interpolator + * @param target the transformGroup node affected by this transformInterpolator + * @param axisOfTransform the transform that defines the local coordinate + * system in which this interpolator operates. + */ + public TransformInterpolator(Alpha alpha, + TransformGroup target, + Transform3D axisOfTransform){ + + super(alpha); + this.target = target; + axis.set(axisOfTransform); + axisInverse.invert(axis); + } + + /** + * This method sets the target TransformGroup node for this + * interpolator. + * @param target The target TransformGroup + */ + public void setTarget(TransformGroup target) { + this.target = target; + } + + /** + * This method retrieves this interpolator's TransformGroup + * node reference. + * @return the Interpolator's target TransformGroup + */ + public TransformGroup getTarget() { + return target; + } + + /** + * This method sets the axis of transform for this interpolator. + * @param axisOfTransform the transform that defines the local coordinate + * system in which this interpolator operates + */ + public void setTransformAxis(Transform3D axisOfTransform) { + this.axis.set(axisOfTransform); + this.axisInverse.invert(this.axis); + } + + /** + * This method retrieves this interpolator's axis of transform. + * @return the interpolator's axis of transform + */ + public Transform3D getTransformAxis() { + return new Transform3D(this.axis); + } + + /** + * Computes the new transform for this interpolator for a given + * alpha value. + * + * @param alphaValue alpha value between 0.0 and 1.0 + * @param transform object that receives the computed transform for + * the specified alpha value + */ + public abstract void computeTransform(float alphaValue, + Transform3D transform); + + /** + * This method is invoked by the behavior scheduler every frame. + * First it gets the alpha value that corresponds to the current time. + * Then it calls computeTransform() method to computes the transform based on this + * alpha vaule, and updates the specified TransformGroup node with this new transform. + * @param criteria an enumeration of the criteria that caused the + * stimulus + */ + public void processStimulus(Enumeration criteria) { + // Handle stimulus + WakeupCriterion criterion = passiveWakeupCriterion; + + if (alpha != null) { + float value = alpha.value(); + if (value != prevAlphaValue) { + computeTransform(value, currentTransform); + target.setTransform(currentTransform); + prevAlphaValue = value; + } + if (!alpha.finished() && !alpha.isPaused()) { + criterion = defaultWakeupCriterion; + } + } + wakeupOn(criterion); + } + + /** + * Copies all TransformInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + TransformInterpolator ti = (TransformInterpolator) originalNode; + + setTransformAxis(ti.getTransformAxis()); + + // this reference will be updated in updateNodeReferences() + setTarget(ti.getTarget()); + } + + /** + * Callback used to allow a node to check if any scene graph objects + * referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any object references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding object in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * object is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + super.updateNodeReferences(referenceTable); + + // check TransformGroup + Node n = getTarget(); + + if (n != null) { + setTarget((TransformGroup) referenceTable.getNewObjectReference(n)); + } + } +} diff --git a/src/classes/share/javax/media/j3d/TransformStructure.java b/src/classes/share/javax/media/j3d/TransformStructure.java new file mode 100644 index 0000000..6cd60ca --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransformStructure.java @@ -0,0 +1,724 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +/** + * A transform update is a object that manages TransformGroups + */ + +class TransformStructure extends J3dStructure implements ObjectUpdate { + /** + * An ArrayList of TransformGroups to traverse + */ + UnorderList traverseList = new UnorderList(TransformGroupRetained.class); + + /** + * A Parallel ArrayList of Transforms for the traverse list + */ + UnorderList transformList = new UnorderList(Transform3D.class); + + ArrayList objectList = new ArrayList(); + + /** + * arraylist of the bounding leaf users affected by the transform + */ + ArrayList blUsers = new ArrayList(); + + // to gather transform targets + UpdateTargets targets = new UpdateTargets(); + + /** + * An arrayList of nodes that need collisionBounds updates + */ + ArrayList collisionObjectList = new ArrayList(); + + // The object that contains the dynamic HashKey - a string type object + HashKey key = new HashKey(250); + + // List of dirty TransformGroups + ArrayList dirtyTransformGroups = new ArrayList(); + + // Associated Keys with the dirtyNodeGroup + ArrayList keySet = new ArrayList(); + + // current locale under traversal + Locale locale = null; + + // The transform used in intermediate calc + Transform3D currentTrans = new Transform3D(); + + TransformGroupRetained tgs[]; + Transform3D t3ds[]; + + // the active list contains changed TransformGroup minus those that + // have been switched-off, plus those that have been changed but + // just switched-on + UnorderList activeTraverseList = + new UnorderList(TransformGroupRetained.class); + + // contains TG that have been previously changed but just switched-on + ArrayList switchDirtyTgList = new ArrayList(1); + + boolean lazyUpdate = false; + + // ArrayList of switches that have changed, use for lastSwitchOn updates + ArrayList switchChangedList = new ArrayList(); + + // true if already in MasterControl's update object list + boolean inUpdateObjectList = false; + + /** + * This constructor does nothing + */ + TransformStructure(VirtualUniverse u) { + super(u, J3dThread.UPDATE_TRANSFORM); + } + + void processMessages(long referenceTime) { + J3dMessage[] messages = getMessages(referenceTime); + int nMsg = getNumMessage(); + J3dMessage m; + int i, index; + + if (nMsg <= 0) { + return; + } + + targets.clearNodes(); + objectList.clear(); + blUsers.clear(); + inUpdateObjectList = false; + + synchronized (universe.sceneGraphLock) { + // first compact the TRANSFORM_CHANGED messages by going + // backwards through the messages + for (i = (nMsg-1); i >= 0; i--) { + m = messages[i]; + if (m.type == J3dMessage.TRANSFORM_CHANGED) { + index = traverseList.indexOf(m.args[1]); + // if this transform group isn't in our list, add + // the information + if (index == -1) { + traverseList.add(m.args[1]); + transformList.add(m.args[2]); + } + } + } + + for (i=0; i<nMsg; i++) { + m = messages[i]; + + switch (m.type) { + case J3dMessage.INSERT_NODES: + objectList.add(m.args[0]); + if (m.args[1] != null) { + TargetsInterface ti = (TargetsInterface)m.args[1]; + ti.updateCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, + (CachedTargets[])m.args[2]); + } + break; + case J3dMessage.REMOVE_NODES: + removeNodes(m); + break; + case J3dMessage.SWITCH_CHANGED: + processSwitchChanged(m); + break; + case J3dMessage.SHAPE3D_CHANGED: + objectList.add(m.args[3]); + if (m.args[4] != null) { + TargetsInterface ti = (TargetsInterface)m.args[4]; + ti.updateCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, + (CachedTargets[])m.args[5]); + } + break; + case J3dMessage.GEOMETRY_CHANGED: + objectList.add(m.args[0]); + break; + case J3dMessage.MORPH_CHANGED: + objectList.add(m.args[3]); + break; + case J3dMessage.TEXT3D_DATA_CHANGED: + objectList.add(m.args[1]); + Object tiArr[] = (Object[])m.args[2]; + if (tiArr != null) { + Object newCtArr[] = (Object[])m.args[3]; + for (int j=0; j<tiArr.length;j++) { + TargetsInterface ti = + (TargetsInterface)tiArr[j]; + ti.updateCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, + (CachedTargets[])newCtArr[j]); + } + } + break; + case J3dMessage.TEXT3D_TRANSFORM_CHANGED: + objectList.add(m.args[0]); + break; + case J3dMessage.BOUNDS_AUTO_COMPUTE_CHANGED: + processBoundsAutoComputeChanged(m); + break; + case J3dMessage.REGION_BOUND_CHANGED: + processRegionBoundChanged(m); + break; + case J3dMessage.COLLISION_BOUND_CHANGED: + processCollisionBoundChanged(m); + break; + } + m.decRefcount(); + } + processCurrentLocalToVworld(); + + // TODO: temporary -- processVwcBounds will be + // done in GeometryStructure + if (objectList.size() > 0) { + processGeometryAtomVwcBounds(); + } + processVwcBounds(); + } + + Arrays.fill(messages, 0, nMsg, null); + } + + void processCurrentLocalToVworld() { + int i, j, tSize, sSize; + TransformGroupRetained tg; + BranchGroupRetained bgr; + Transform3D t; + TransformGroupData data; + + lazyUpdate = false; + + tSize = transformList.size(); + sSize = switchDirtyTgList.size(); + if (tSize <= 0 && sSize <= 0) { + return; + } + + // process TG with setTransform changes + // update Transform3D, switchDirty and lToVwDrity flags + if (tSize > 0) { + tgs = (TransformGroupRetained[])traverseList.toArray(false); + t3ds = (Transform3D[])transformList.toArray(false); + for (i=0; i<tSize; i++) { + tg = tgs[i]; + tg.currentTransform.set(t3ds[i]); + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + t3ds[i]); + + synchronized(tg) { // synchronized with tg.set/clearLive + if(tg.perPathData != null) { + if (! tg.inSharedGroup) { + data = tg.perPathData[0]; + if (! data.switchState.inSwitch) { + // always add to activetraverseList if not in switch + activeTraverseList.add(tg); + data.markedDirty = true; + data.switchDirty = false; + } else { + // if in switch, add to activetraverseList only if it is + // currently switched on, otherwise, mark it as + // switchDirty + if (data.switchState.currentSwitchOn) { + activeTraverseList.add(tg); + data.switchDirty = false; + data.markedDirty = true; + } else { + data.switchDirty = true; + data.markedDirty = false; + } + } + } else { + int npaths = tg.perPathData.length; + boolean added = false; + + for (int k=0; k<npaths; k++) { + data = tg.perPathData[k]; + if (!data.switchState.inSwitch) { + if (!added) { + // add to activetraverseList if not in switch + added = true; + activeTraverseList.add(tg); + } + data.markedDirty = true; + data.switchDirty = false; + } else { + // if in switch, add to activetraverseList only if + // it is currently switched on, otherwise, + // mark it as switchDirty + if (data.switchState.currentSwitchOn) { + if (!added) { + added = true; + activeTraverseList.add(tg); + } + data.switchDirty = false; + data.markedDirty = true; + } else { + data.switchDirty = true; + data.markedDirty = false; + } + } + } + } + } + } + } + } + + // merge switchDirty into activeTraverseList + if (sSize > 0) { + int size = switchDirtyTgList.size(); + for (i=0; i<size; i++) { + // Note: UnorderList does not implement addAll + activeTraverseList.add(switchDirtyTgList.get(i)); + } + switchDirtyTgList.clear(); + lazyUpdate = true; + } + + // activeTraverseList contains switched-on tg as well + tgs = (TransformGroupRetained[])activeTraverseList.toArray(false); + tSize = activeTraverseList.size(); + + // process active TGs + if (tSize > 0) { + + sortTransformGroups(tSize); + + // update lToVw and gather targets + for (i=0; i<tSize; i++) { + tgs[i].processChildLocalToVworld(dirtyTransformGroups, keySet, + targets, blUsers); + } + if (!inUpdateObjectList) { + VirtualUniverse.mc.addMirrorObject(this); + inUpdateObjectList = true; + } + } + + transformList.clear(); + traverseList.clear(); + activeTraverseList.clear(); + } + + + private void sortTransformGroups(int size) { + if (size < 7) { + insertSort(size); + } else { + quicksort(0, size-1); + } + } + + // Insertion sort on smallest arrays + private void insertSort(int size) { + for (int i=0; i<size; i++) { + for (int j=i; j>0 && + (tgs[j-1].maxTransformLevel > tgs[j].maxTransformLevel); j--) { + TransformGroupRetained tmptg = tgs[j]; + tgs[j] = tgs[j-1]; + tgs[j-1] = tmptg; + } + } + } + + private void quicksort( int l, int r ) { + int i = l; + int j = r; + double k = tgs[(l+r) / 2].maxTransformLevel; + do { + while (tgs[i].maxTransformLevel<k) i++; + while (k<tgs[j].maxTransformLevel) j--; + if (i<=j) { + TransformGroupRetained tmptg = tgs[i]; + tgs[i] = tgs[j]; + tgs[j] = tmptg; + + i++; + j--; + } + } while (i<=j); + + if (l<j) quicksort(l,j); + if (l<r) quicksort(i,r); + } + + + public void updateObject() { + processLastLocalToVworld(); + processLastSwitchOn(); + } + + + void processLastSwitchOn() { + int size = switchChangedList.size(); + if (size > 0) { + SwitchState switchState; + + for (int i = 0; i < size; i++) { + switchState = (SwitchState)switchChangedList.get(i); + switchState.updateLastSwitchOn(); + } + switchChangedList.clear(); + } + } + + + void processLastLocalToVworld() { + int i, j, k; + TransformGroupRetained tg; + HashKey key; + + + int dTGSize = dirtyTransformGroups.size(); + if (J3dDebug.devPhase && J3dDebug.debug) { + J3dDebug.doDebug(J3dDebug.transformStructure, J3dDebug.LEVEL_5, + "processLastLocalToVworld(): dTGSize= " + dTGSize + "\n"); + } + + for (i=0, k=0; i < dTGSize; i++) { + tg = (TransformGroupRetained)dirtyTransformGroups.get(i); + // Check if the transformGroup is still alive + + // TODO: This is a hack, should be fixed after EA + // Null pointer checking should be removed! + // should call trans = tg.getCurrentChildLocalToVworld(key); + synchronized(tg) { + if (tg.childLocalToVworld != null) { + if (tg.inSharedGroup) { + key = (HashKey) keySet.get(k++); + for (j=0; j<tg.localToVworldKeys.length; j++) { + if (tg.localToVworldKeys[j].equals(key)) { + break; + } + } + if (j < tg.localToVworldKeys.length) { + // last index = current index + tg.childLocalToVworldIndex[j][NodeRetained.LAST_LOCAL_TO_VWORLD] = + tg.childLocalToVworldIndex[j][NodeRetained.CURRENT_LOCAL_TO_VWORLD]; + } + } + else { + // last index = current index + tg.childLocalToVworldIndex[0][NodeRetained.LAST_LOCAL_TO_VWORLD] = + tg.childLocalToVworldIndex[0][NodeRetained.CURRENT_LOCAL_TO_VWORLD]; + } + } + + } + + } + dirtyTransformGroups.clear(); + keySet.clear(); + + } + + void processGeometryAtomVwcBounds() { + + + Shape3DRetained ms; + GeometryAtom ga; + + //int num_locales = universe.listOfLocales.size(); + int oSize = objectList.size(); + for (int i = 0; i < oSize; i++) { + Object[] nodes = (Object[]) objectList.get(i); + if (J3dDebug.devPhase && J3dDebug.debug) { + J3dDebug.doDebug(J3dDebug.transformStructure, J3dDebug.LEVEL_5, + "vwcBounds computed this frame = " + nodes.length + "\n"); + } + for (int j = 0; j < nodes.length; j++) { + // If the list has geometry atoms, update the vwc bounds + synchronized(nodes[j]) { + if (nodes[j] instanceof GeometryAtom) { + ga = (GeometryAtom) nodes[j]; + ms = ga.source; + + // update mirrorShape's vwcBounds if in use + // shape with multiple geometries only needed to be + // updated once + + synchronized(ms.bounds) { + ms.vwcBounds.transform(ms.bounds, + ms.getCurrentLocalToVworld(0)); + } + if (ms.collisionBound != null) { + ms.collisionVwcBound.transform( + ms.collisionBound, + ms.getCurrentLocalToVworld(0)); + } + ga.centroidIsDirty = true; + } else if (nodes[j] instanceof GroupRetained) { + // Update collisionVwcBounds of mirror GroupRetained + GroupRetained g = (GroupRetained) nodes[j]; + Bounds bound = (g.sourceNode.collisionBound != null ? + g.sourceNode.collisionBound : + g.sourceNode.getEffectiveBounds()); + g.collisionVwcBounds.transform(bound, + g.getCurrentLocalToVworld()); + } + } + } + } + // process collision bounds only update + for (int i = 0; i < collisionObjectList.size(); i++) { + Object[] nodes = (Object[]) collisionObjectList.get(i); + for (int j = 0; j < nodes.length; j++) { + synchronized(nodes[j]) { + if (nodes[j] instanceof GeometryAtom) { + ga = (GeometryAtom) nodes[j]; + ms = ga.source; + + if (ms.collisionVwcBound != null) { + ms.collisionVwcBound.transform( + ms.collisionBound, + ms.getCurrentLocalToVworld(0)); + } + } + } + } + } + collisionObjectList.clear(); + } + + void processVwcBounds() { + + + int size; + int i,j; + GeometryAtom ga; + Shape3DRetained ms; + Object nodes[], nodesArr[]; + + UnorderList arrList = targets.targetList[Targets.GEO_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (i = 0; i<size; i++) { + nodes = (Object[])nodesArr[i]; + for (j = 0; j < nodes.length; j++) { + synchronized(nodes[j]) { + ga = (GeometryAtom) nodes[j]; + ms = ga.source; + synchronized(ms.bounds) { + ms.vwcBounds.transform(ms.bounds, + ms.getCurrentLocalToVworld(0)); + } + if (ms.collisionBound != null) { + ms.collisionVwcBound.transform( + ms.collisionBound, + ms.getCurrentLocalToVworld(0)); + } + ga.centroidIsDirty = true; + } + } + } + } + + arrList = targets.targetList[Targets.GRP_TARGETS]; + if (arrList != null) { + size = arrList.size(); + nodesArr = arrList.toArray(false); + + for (i = 0; i<size; i++) { + nodes = (Object[])nodesArr[i]; + for (j = 0; j < nodes.length; j++) { + // Update collisionVwcBounds of mirror GroupRetained + GroupRetained g = (GroupRetained)nodes[j]; + Bounds bound = (g.sourceNode.collisionBound != null ? + g.sourceNode.collisionBound : + g.sourceNode.getEffectiveBounds()); + g.collisionVwcBounds.transform(bound, + g.getCurrentLocalToVworld()); + } + } + } + + // process collision bounds only update + for (i = 0; i < collisionObjectList.size(); i++) { + nodes = (Object[]) collisionObjectList.get(i); + for (j = 0; j < nodes.length; j++) { + synchronized(nodes[j]) { + if (nodes[j] instanceof GeometryAtom) { + ga = (GeometryAtom) nodes[j]; + ms = ga.source; + + if (ms.collisionVwcBound != null) { + ms.collisionVwcBound.transform( + ms.collisionBound, + ms.getCurrentLocalToVworld(0)); + } + } + } + } + } + collisionObjectList.clear(); + } + + void processRegionBoundChanged(J3dMessage m) { + // need to update mirrorShape's bounds + processBoundsChanged((Object[]) m.args[0], (Bounds)m.args[1]); + } + + void processBoundsChanged(Object[] gaArray, Bounds updateBounds) { + int i; + GeometryAtom ga; + Shape3DRetained ms; + + for (i=0; i<gaArray.length; i++) { + ga = (GeometryAtom)gaArray[i]; + ms = ga.source; + + // update mirrorShape's bound objects + // since boundsAutoCompute is false and user specified a bound + ms.bounds = updateBounds; + if (ms.collisionBound == null) { + ms.collisionVwcBound = ms.vwcBounds; + } + } + objectList.add(gaArray); + } + + + void processCollisionBoundChanged(J3dMessage m) { + int i; + Shape3DRetained ms; + Bounds collisionBound = (Bounds)m.args[1]; + + if (m.args[0] instanceof GroupRetained) { + GroupRetained g = (GroupRetained) m.args[0]; + if (g.mirrorGroup != null) { + objectList.add(g.mirrorGroup); + } + } else { + Object[] gaArray = (Object[]) m.args[0]; + GeometryAtom ga; + + for (i=0; i<gaArray.length; i++) { + ga = (GeometryAtom)gaArray[i]; + ms = ga.source; + + ms.collisionBound = collisionBound; + + if (ms.collisionBound != null) { + // may be previously points to ms.vwcBounds, therefore + // needs to create one + ms.collisionVwcBound = (Bounds)ms.collisionBound.clone(); + } else { + ms.collisionVwcBound = ms.vwcBounds; + } + } + collisionObjectList.add(gaArray); + } + } + + void processBoundsAutoComputeChanged(J3dMessage m) { + // need to update mirrorShape's bounds + processBoundsChanged((Object[]) m.args[0], (Bounds) m.args[1]); + } + + void processSwitchChanged(J3dMessage m) { + ArrayList switchList = (ArrayList)m.args[2]; + + + int size = switchList.size(); + if (size > 0) { + // update SwitchState's CurrentSwitchOn flag + SwitchState switchState; + for (int j=0; j<size; j++) { + switchState = (SwitchState)switchList.get(j); + switchState.updateCurrentSwitchOn(); + } + + // process switch dirty TranformGroups + UpdateTargets targets = (UpdateTargets)m.args[0]; + UnorderList arrList = targets.targetList[Targets.GRP_TARGETS]; + + if (arrList != null) { + + Object[] nodes; + Object[] nodesArr = arrList.toArray(false); + int aSize = arrList.size(); + int nPaths; + boolean added; + + TransformGroupRetained tg; + TransformGroupData data; + + for (int j=0; j<aSize; j++) { + nodes = (Object[])nodesArr[j]; + + for (int i=0; i<nodes.length; i++) { + added = false; + tg = (TransformGroupRetained)nodes[i]; + + synchronized(tg) { // synchronized with tg.set/clearLive + if (tg.perPathData != null) { + nPaths = tg.perPathData.length; + + for (int k=0; k<nPaths; k++) { + data = tg.perPathData[k]; + if (data.switchState.currentSwitchOn && + data.switchDirty) { + if (!added) { + // only needed to add once + switchDirtyTgList.add(tg); + added = true; + } + data.switchDirty = false; + data.markedDirty = true; + } + } + } + } + } + } + } + + // gather a list of SwitchState for lastSwitchOn update + switchChangedList.addAll(switchList); + + if (!inUpdateObjectList) { + VirtualUniverse.mc.addMirrorObject(this); + inUpdateObjectList = true; + } + } + } + + UpdateTargets getTargetList() { + return targets; + } + + ArrayList getBlUsers() { + return blUsers; + } + + boolean getLazyUpdate() { + return lazyUpdate; + } + + void removeNodes(J3dMessage m) { + if (m.args[1] != null) { + TargetsInterface ti = (TargetsInterface)m.args[1]; + ti.updateCachedTargets( + TargetsInterface.TRANSFORM_TARGETS, + (CachedTargets[])m.args[2]); + } + } + + void cleanup() {} +} diff --git a/src/classes/share/javax/media/j3d/TransparencyAttributes.java b/src/classes/share/javax/media/j3d/TransparencyAttributes.java new file mode 100644 index 0000000..11dc4c1 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransparencyAttributes.java @@ -0,0 +1,531 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The TransparencyAttributes object defines all attributes affecting + * transparency of the object. The transparency attributes are:<P> + * <UL> + * <LI>Transparency mode - defines how transparency is applied to + * this Appearance component object:</LI><P> + * <UL> + * <LI>FASTEST - uses the fastest available method for transparency.</LI><P> + * <LI>NICEST - uses the nicest available method for transparency.</LI><P> + * <LI>SCREEN_DOOR - uses screen-door transparency. This is done using + * an on/off stipple pattern in which the percentage of transparent pixels + * is approximately equal to the value specified by the transparency + * parameter.</LI><P> + * <LI>BLENDED - uses alpha blended transparency. The blend equation is + * specified by the srcBlendFunction and dstBlendFunction attributes. + * The default equation is: + * <ul> + * <code>alpha<sub><font size=-1>src</font></sub>*src + + * (1-alpha<sub><font size=-1>src</font></sub>)*dst</code> + * </ul> + * where <code>alpha<sub><font size=-1>src</font></sub></code> is + * <code>1-transparency</code>. + * When this mode is used with a Raster object or with a Geometry + * that contains per-vertex colors with alpha, the alpha values in + * the Raster's image or in the Geometry's per-vertex colors are + * combined with the transparency value in this TransparencyAttributes + * object to perform blending. In this case, the alpha value used for + * blending at each pixel is: + * <ul> + * <code>alpha<sub><font size=-1>src</font></sub> = + * alpha<sub><font size=-1>pix</font></sub> * + * (1-transparency)</code>. + * </ul> + * </LI><P> + * <LI>NONE - no transparency; opaque object.</LI><P> + * </UL> + * <LI>Blend function - used in blended transparency and antialiasing + * operations. The source function specifies the factor that is + * multiplied by the source color. This value is added to the product + * of the destination factor and the destination color. The default + * source blend function is BLEND_SRC_ALPHA. The source blend function + * is one of the following:</LI><P> + * <UL> + * <LI>BLEND_ZERO - the blend function is <code>f = 0</code>.</LI> + * <LI>BLEND_ONE - the blend function is <code>f = 1</code>.</LI> + * <LI>BLEND_SRC_ALPHA - the blend function is <code>f = + * alpha<sub><font size=-1>src</font></sub></code>.</LI> + * <LI>BLEND_ONE_MINUS_SRC_ALPHA - the blend function is <code>f = + * 1 - alpha<sub><font size=-1>src</font></sub></code>.</LI></UL><P> + * <LI>Blend value - the amount of transparency to be applied to this + * Appearance component object. The transparency values are in the + * range [0.0, 1.0], with 0.0 being fully opaque and 1.0 being + * fully transparent.</LI><P> + * </UL> + */ +public class TransparencyAttributes extends NodeComponent { + /** + * Specifies that this TransparencyAttributes object + * allows reading its transparency mode component information. + */ + public static final int + ALLOW_MODE_READ = CapabilityBits.TRANSPARENCY_ATTRIBUTES_ALLOW_MODE_READ; + + /** + * Specifies that this TransparencyAttributes object + * allows writing its transparency mode component information. + */ + public static final int + ALLOW_MODE_WRITE = CapabilityBits.TRANSPARENCY_ATTRIBUTES_ALLOW_MODE_WRITE; + + /** + * Specifies that this TransparencyAttributes object + * allows reading its transparency value. + */ + public static final int + ALLOW_VALUE_READ = CapabilityBits.TRANSPARENCY_ATTRIBUTES_ALLOW_VALUE_READ; + + /** + * Specifies that this TransparencyAttributes object + * allows writing its transparency value. + */ + public static final int + ALLOW_VALUE_WRITE = CapabilityBits.TRANSPARENCY_ATTRIBUTES_ALLOW_VALUE_WRITE; + + /** + * Specifies that this TransparencyAttributes object + * allows reading its blend function. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_BLEND_FUNCTION_READ = + CapabilityBits.TRANSPARENCY_ATTRIBUTES_ALLOW_BLEND_FUNCTION_READ; + + /** + * Specifies that this TransparencyAttributes object + * allows writing its blend function. + * + * @since Java 3D 1.2 + */ + public static final int ALLOW_BLEND_FUNCTION_WRITE = + CapabilityBits.TRANSPARENCY_ATTRIBUTES_ALLOW_BLEND_FUNCTION_WRITE; + + /** + * Use the fastest available method for transparency. + * @see #setTransparencyMode + */ + public static final int FASTEST = 0; + + /** + * Use the nicest available method for transparency. + * @see #setTransparencyMode + */ + public static final int NICEST = 1; + + /** + * Use alpha blended transparency. The blend equation is + * specified by the srcBlendFunction and dstBlendFunction attributes. + * The default equation is: + * <ul> + * <code>alpha<sub><font size=-1>src</font></sub>*src + + * (1-alpha<sub><font size=-1>src</font></sub>)*dst</code> + * </ul> + * where <code>alpha<sub><font size=-1>src</font></sub></code> is + * <code>1-transparency</code>. + * When this mode is used with a Raster object or with a Geometry + * that contains per-vertex colors with alpha, the alpha values in + * the Raster's image or in the Geometry's per-vertex colors are + * combined with the transparency value in this TransparencyAttributes + * object to perform blending. In this case, the alpha value used for + * blending at each pixel is: + * <ul> + * <code>alpha<sub><font size=-1>src</font></sub> = + * alpha<sub><font size=-1>pix</font></sub> * + * (1-transparency)</code>. + * </ul> + * + * @see #setTransparencyMode + * @see #setSrcBlendFunction + * @see #setDstBlendFunction + */ + public static final int BLENDED = 2; + + /** + * Use screen-door transparency. This is done using an on/off stipple + * pattern where the percentage of pixels that are transparent is + * approximately equal to the value specified by the transparency + * parameter. + * @see #setTransparencyMode + */ + public static final int SCREEN_DOOR = 3; + + /** + * No transparency, opaque object. + * @see #setTransparencyMode + */ + public static final int NONE = 4; + + /** + * Blend function: <code>f = 0</code>. + * @see #setSrcBlendFunction + * @see #setDstBlendFunction + * @since Java 3D 1.2 + */ + public static final int BLEND_ZERO = 0; + + /** + * Blend function: <code>f = 1</code>. + * @see #setSrcBlendFunction + * @see #setDstBlendFunction + * @since Java 3D 1.2 + */ + public static final int BLEND_ONE = 1; + + /** + * Blend function: + * <code>f = alpha<sub><font size=-1>src</font></sub></code>. + * @see #setSrcBlendFunction + * @see #setDstBlendFunction + * @since Java 3D 1.2 + */ + public static final int BLEND_SRC_ALPHA = 2; + + /** + * Blend function: + * <code>f = 1-alpha<sub><font size=-1>src</font></sub></code>. + * @see #setSrcBlendFunction + * @see #setDstBlendFunction + * @since Java 3D 1.2 + */ + public static final int BLEND_ONE_MINUS_SRC_ALPHA = 3; + + + /** + * Constructs a TransparencyAttributes object with default parameters. + * The default values are as follows: + * <ul> + * transparency mode : <code>NONE</code><br> + * transparency value : 0.0<br> + * source blend function : <code>BLEND_SRC_ALPHA</code><br> + * destination blend function : <code>BLEND_ONE_MINUS_SRC_ALPHA</code><br> + * </ul> + */ + public TransparencyAttributes() { + // Just use the default for all attributes + } + + /** + * Construct TransparencyAttributes object with specified values. + * @param tMode the transparency mode + * @param tVal the transparency value + * @exception IllegalArgumentException if + * <code>tMode</code> is a value other than + * <code>NONE</code>, <code>FASTEST</code>, <code>NICEST</code>, + * <code>SCREEN_DOOR</code>, or <code>BLENDED</code> + * + */ + public TransparencyAttributes(int tMode, float tVal){ + this(tMode, tVal, BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA); + } + + /** + * Construct TransparencyAttributes object with specified values. + * @param tMode the transparency mode + * @param tVal the transparency value + * @param srcBlendFunction the blend function to be used for the source + * color, one of <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * @param dstBlendFunction the blend function to be used for the + * destination + * color, one of <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * @exception IllegalArgumentException if + * <code>tMode</code> is a value other than + * <code>NONE</code>, <code>FASTEST</code>, <code>NICEST</code>, + * <code>SCREEN_DOOR</code>, or <code>BLENDED</code> + * @exception IllegalArgumentException if + * <code>srcBlendFunction</code> or <code>dstBlendFunction</code> + * is a value other than <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or + * <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * + * @since Java 3D 1.2 + */ + public TransparencyAttributes(int tMode, + float tVal, + int srcBlendFunction, + int dstBlendFunction) { + if ((tMode < FASTEST) ||(tMode > NONE)) { + throw new IllegalArgumentException(J3dI18N.getString("TransparencyAttributes6")); + } + + if ((srcBlendFunction < BLEND_ZERO) || + (srcBlendFunction > BLEND_ONE_MINUS_SRC_ALPHA)) { + throw new IllegalArgumentException(J3dI18N.getString("TransparencyAttributes7")); + } + + if ((dstBlendFunction < BLEND_ZERO) || + (dstBlendFunction > BLEND_ONE_MINUS_SRC_ALPHA)) { + throw new IllegalArgumentException(J3dI18N.getString("TransparencyAttributes8")); + } + + ((TransparencyAttributesRetained)this.retained).initTransparencyMode(tMode); + ((TransparencyAttributesRetained)this.retained).initTransparency(tVal); + ((TransparencyAttributesRetained)this.retained).initSrcBlendFunction(srcBlendFunction); + ((TransparencyAttributesRetained)this.retained).initDstBlendFunction(dstBlendFunction); + } + + /** + * Sets the transparency mode for this + * appearance component object. + * @param transparencyMode the transparency mode to be used, one of + * <code>NONE</code>, <code>FASTEST</code>, <code>NICEST</code>, + * <code>SCREEN_DOOR</code>, or <code>BLENDED</code> + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if + * <code>transparencyMode</code> is a value other than + * <code>NONE</code>, <code>FASTEST</code>, <code>NICEST</code>, + * <code>SCREEN_DOOR</code>, or <code>BLENDED</code> + */ + public void setTransparencyMode(int transparencyMode) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MODE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TransparencyAttributes0")); + + if ((transparencyMode < FASTEST) || (transparencyMode > NONE)) { + throw new IllegalArgumentException(J3dI18N.getString("TransparencyAttributes6")); + } + + if (isLive()) + ((TransparencyAttributesRetained)this.retained).setTransparencyMode(transparencyMode); + else + ((TransparencyAttributesRetained)this.retained).initTransparencyMode(transparencyMode); + } + + + + /** + * Gets the transparency mode for this + * appearance component object. + * @return transparencyMode the transparency mode + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getTransparencyMode() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_MODE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TransparencyAttributes1")); + + return ((TransparencyAttributesRetained)this.retained).getTransparencyMode(); + } + + /** + * Sets this appearance's transparency. + * @param transparency the appearance's transparency + * in the range [0.0, 1.0] with 0.0 being + * fully opaque and 1.0 being fully transparent + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setTransparency(float transparency) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_VALUE_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TransparencyAttributes2")); + + + if (isLive()) + ((TransparencyAttributesRetained)this.retained).setTransparency(transparency); + else + ((TransparencyAttributesRetained)this.retained).initTransparency(transparency); + + } + + + /** + * Retrieves this appearance's transparency. + * @return the appearance's transparency + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public float getTransparency() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_VALUE_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TransparencyAttributes3")); + + return ((TransparencyAttributesRetained)this.retained).getTransparency(); + } + + /** + * Sets the source blend function used in blended transparency + * and antialiasing operations. The source function specifies the + * factor that is multiplied by the source color; this value is + * added to the product of the destination factor and the + * destination color. The default source blend function is + * <code>BLEND_SRC_ALPHA</code>. + * + * @param blendFunction the blend function to be used for the source + * color, one of <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if + * <code>blendFunction</code> + * is a value other than <code>BLEND_ZERO</code>, + * <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or + * <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * + * @since Java 3D 1.2 + */ + public void setSrcBlendFunction(int blendFunction) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_BLEND_FUNCTION_WRITE)) + throw new + CapabilityNotSetException(J3dI18N.getString("TransparencyAttributes4")); + + if ((blendFunction < BLEND_ZERO) || + (blendFunction > BLEND_ONE_MINUS_SRC_ALPHA)) { + throw new IllegalArgumentException(J3dI18N.getString("TransparencyAttributes7")); + } + + + if (isLive()) + ((TransparencyAttributesRetained)this.retained).setSrcBlendFunction(blendFunction); + else + ((TransparencyAttributesRetained)this.retained).initSrcBlendFunction(blendFunction); + } + + + + /** + * Gets the source blend function for this + * TransparencyAttributes object. + * @return the source blend function. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getSrcBlendFunction() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_BLEND_FUNCTION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TransparencyAttributes5")); + return ((TransparencyAttributesRetained)this.retained).getSrcBlendFunction(); + } + + /** + * Sets the destination blend function used in blended transparency + * and antialiasing operations. The destination function specifies the + * factor that is multiplied by the destination color; this value is + * added to the product of the source factor and the + * source color. The default destination blend function is + * <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * + * @param blendFunction the blend function to be used for the destination + * color, one of <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @exception IllegalArgumentException if + * <code>blendFunction</code> + * is a value other than <code>BLEND_ZERO</code>, + * <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or + * <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * + * @since Java 3D 1.2 + */ + public void setDstBlendFunction(int blendFunction) { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_BLEND_FUNCTION_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("TransparencyAttributes4")); + + if ((blendFunction < BLEND_ZERO) || + (blendFunction > BLEND_ONE_MINUS_SRC_ALPHA)) { + throw new IllegalArgumentException(J3dI18N.getString("TransparencyAttributes8")); + } + + if (isLive()) + ((TransparencyAttributesRetained)this.retained).setDstBlendFunction(blendFunction); + else + ((TransparencyAttributesRetained)this.retained).initDstBlendFunction(blendFunction); + } + + + + /** + * Gets the destination blend function for this + * TransparencyAttributes object. + * @return the destination blend function. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.2 + */ + public int getDstBlendFunction() { + if (isLiveOrCompiled()) + if (!this.getCapability(ALLOW_BLEND_FUNCTION_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("TransparencyAttributes5")); + + return ((TransparencyAttributesRetained)this.retained).getDstBlendFunction(); + } + + /** + * Creates a retained mode TransparencyAttributesRetained object that this + * TransparencyAttributes component object will point to. + */ + void createRetained() { + this.retained = new TransparencyAttributesRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + TransparencyAttributes transa = new TransparencyAttributes(); + transa.duplicateNodeComponent(this); + return transa; + } + + + + /** + * Copies all node information from <code>originalNodeComponent</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). + * + * @param originalNodeComponent the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(NodeComponent originalNodeComponent, + boolean forceDuplicate) { + super.duplicateAttributes(originalNodeComponent, forceDuplicate); + + TransparencyAttributesRetained attr = + (TransparencyAttributesRetained) originalNodeComponent.retained; + TransparencyAttributesRetained rt = + (TransparencyAttributesRetained) retained; + + rt.initTransparencyMode(attr.getTransparencyMode()); + rt.initTransparency(attr.getTransparency()); + rt.initSrcBlendFunction(attr.getSrcBlendFunction()); + rt.initDstBlendFunction(attr.getDstBlendFunction()); + } + +} diff --git a/src/classes/share/javax/media/j3d/TransparencyAttributesRetained.java b/src/classes/share/javax/media/j3d/TransparencyAttributesRetained.java new file mode 100644 index 0000000..75da932 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransparencyAttributesRetained.java @@ -0,0 +1,347 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.ArrayList; + +/** + * The TransparencyAttributes object defines all attributes affecting + * transparency of the object. + */ +class TransparencyAttributesRetained extends NodeComponentRetained { + // A list of pre-defined bits to indicate which component + // in this TransparencyAttributes object changed. + static final int MODE_CHANGED = 0x01; + static final int VALUE_CHANGED = 0x02; + static final int SRC_BLEND_FUNCTION_CHANGED = 0x04; + static final int DST_BLEND_FUNCTION_CHANGED = 0x08; + + // Integer flag that contains bitset to indicate + // which field changed. + int isDirty = 0xffff; + + // Transparency mode (alpha, screen_door) + int transparencyMode = TransparencyAttributes.NONE; + float transparency = 0.0f; + + // Transparency blend functions + int srcBlendFunction = TransparencyAttributes.BLEND_SRC_ALPHA; + int dstBlendFunction = TransparencyAttributes.BLEND_ONE_MINUS_SRC_ALPHA; + + // Here are some blend functions that are used in multi-pass only + static final int BLEND_ZERO = 0; + static final int BLEND_ONE = 1; + static final int BLEND_SRC_ALPHA = 2; + static final int BLEND_ONE_MINUS_SRC_ALPHA = 3; + static final int BLEND_DST_COLOR = 4; + static final int BLEND_SRC_COLOR = 5; + static final int BLEND_ONE_MINUS_SRC_COLOR = 6; + static final int BLEND_CONSTANT_COLOR = 7; + + /** + * Sets the transparency mode for this + * appearance component object. + * @param transparencyMode the transparency mode to be used, one of + * <code>NONE</code>, <code>FASTEST</code>, <code>NICEST</code>, + * <code>SCREEN_DOOR</code>, or <code>BLENDED</code> + */ + final void initTransparencyMode(int transparencyMode) { + this.transparencyMode = transparencyMode; + } + + /** + * Sets the transparency mode for this + * appearance component object and sends a message notifying + * the interested structures of the change. + * @param transparencyMode the transparency mode to be used, one of + * <code>FASTEST</code>, <code>NICEST</code>, + * <code>SCREEN_DOOR</code>, or <code>BLENDED</code> + */ + final void setTransparencyMode(int transparencyMode) { + initTransparencyMode(transparencyMode); + sendMessage(MODE_CHANGED, new Integer(transparencyMode)); + } + + /** + * Gets the transparency mode for this + * appearance component object. + * @return transparencyMode the transparency mode + */ + final int getTransparencyMode() { + return transparencyMode; + } + + /** + * Sets this appearance's transparency. + * @param transparency the appearance's transparency + * in the range [0.0, 1.0] with 0.0 being + * fully opaque and 1.0 being fully transparent + */ + final void initTransparency(float transparency) { + this.transparency = transparency; + } + + /** + * Sets this appearance's transparency and sends a message notifying + * the interested structures of the change. + * @param transparency the appearance's transparency + * in the range [0.0, 1.0] with 0.0 being + * fully opaque and 1.0 being fully transparent + */ + final void setTransparency(float transparency) { + initTransparency(transparency); + sendMessage(VALUE_CHANGED, new Float(transparency)); + } + + /** + * Retrieves this appearance's transparency. + * @return the appearance's transparency + */ + final float getTransparency() { + return this.transparency; + } + + /** + * Sets the source blend function used in blended transparency + * and antialiasing operations. The source function specifies the + * factor that is multiplied by the source color; this value is + * added to the product of the destination factor and the + * destination color. The default source blend function is + * <code>BLEND_SRC_ALPHA</code>. + * + * @param blendFunction the blend function to be used for the source + * color, one of <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + */ + final void initSrcBlendFunction(int blendFunction) { + this.srcBlendFunction = blendFunction; + } + + + /** + * Sets the source blend function used in blended transparency + * and antialiasing operations and sends a message notifying the + * interested structures of the change. The source function specifies the + * factor that is multiplied by the source color; this value is + * added to the product of the destination factor and the + * destination color. The default source blend function is + * <code>BLEND_SRC_ALPHA</code>. + * + * @param blendFunction the blend function to be used for the source + * color, one of <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + */ + final void setSrcBlendFunction(int blendFunction) { + initSrcBlendFunction(blendFunction); + sendMessage(SRC_BLEND_FUNCTION_CHANGED, new Integer(blendFunction)); + } + + + /** + * Retrieves this appearance's source blend function. + * @return the appearance's source blend function + */ + final int getSrcBlendFunction() { + return srcBlendFunction; + } + + + /** + * Sets the destination blend function used in blended transparency + * and antialiasing operations. The destination function specifies the + * factor that is multiplied by the destination color; this value is + * added to the product of the source factor and the + * source color. The default destination blend function is + * <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * + * @param blendFunction the blend function to be used for the destination + * color, one of <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * + */ + final void initDstBlendFunction(int blendFunction) { + this.dstBlendFunction = blendFunction; + } + + + /** + * Sets the destination blend function used in blended transparency + * and antialiasing operations and sends a message notifying the + * interested structures of the change. The destination function + * specifies the factor that is multiplied by the destination + * color; this value is added to the product of the source factor + * and the source color. The default destination blend function is + * <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + * + * @param blendFunction the blend function to be used for the destination + * color, one of <code>BLEND_ZERO</code>, <code>BLEND_ONE</code>, + * <code>BLEND_SRC_ALPHA</code>, or <code>BLEND_ONE_MINUS_SRC_ALPHA</code>. + */ + final void setDstBlendFunction(int blendFunction) { + initDstBlendFunction(blendFunction); + sendMessage(DST_BLEND_FUNCTION_CHANGED, new Integer(blendFunction)); + } + + + /** + * Retrieves this appearance's destination blend function. + * @return the appearance's destination blend function + */ + final int getDstBlendFunction() { + return dstBlendFunction; + } + + + /** + * Creates and initializes a mirror object, point the mirror object + * to the retained object if the object is not editable + */ + synchronized void createMirrorObject() { + if (mirror == null) { + // Check the capability bits and let the mirror object + // point to itself if is not editable + if (isStatic()) { + mirror = this; + } else { + TransparencyAttributesRetained mirrorTa + = new TransparencyAttributesRetained(); + mirrorTa.source = source; + mirrorTa.set(this); + mirror = mirrorTa; + + } + } else { + ((TransparencyAttributesRetained) mirror).set(this); + } + } + + /** + * These two native methods update the native context. + */ + native void updateNative(long ctx, + float alpha, int geometryType, + int polygonMode, + boolean lineAA, boolean pointAA, + int transparencyMode, + int srcBlendFunction, + int dstBlendFunction); + + void updateNative(long ctx, + float alpha, int geometryType, int polygonMode, + boolean lineAA, + boolean pointAA) { + updateNative(ctx, alpha, geometryType, polygonMode, + lineAA, pointAA, transparencyMode, + srcBlendFunction, dstBlendFunction); + } + + /** + * Initializes a mirror object, point the mirror object to the retained + * object if the object is not editable + */ + synchronized void initMirrorObject() { + ((TransparencyAttributesRetained)mirror).set(this); + } + + /** + * Update the "component" field of the mirror object with the + * given "value" + */ + synchronized void updateMirrorObject(int component, Object value) { + + TransparencyAttributesRetained mirrorTa = + (TransparencyAttributesRetained) mirror; + + if ((component & MODE_CHANGED) != 0) { + mirrorTa.transparencyMode = ((Integer)value).intValue(); + } + else if ((component & VALUE_CHANGED) != 0) { + mirrorTa.transparency = ((Float)value).floatValue(); + } + else if ((component & SRC_BLEND_FUNCTION_CHANGED) != 0) { + mirrorTa.srcBlendFunction = ((Integer) value).intValue(); + } + else if ((component & DST_BLEND_FUNCTION_CHANGED) != 0) { + mirrorTa.dstBlendFunction = ((Integer) value).intValue(); + } + } + + + boolean equivalent(TransparencyAttributesRetained tr) { + return ((tr != null) && + (tr.transparencyMode == transparencyMode) && + (tr.transparency == transparency) && + (tr.srcBlendFunction == srcBlendFunction) && + (tr.dstBlendFunction == dstBlendFunction)); + } + + protected void set(TransparencyAttributesRetained transp) { + super.set(transp); + transparencyMode = transp.transparencyMode; + transparency = transp.transparency; + srcBlendFunction = transp.srcBlendFunction; + dstBlendFunction = transp.dstBlendFunction; + } + + + + final void sendMessage(int attrMask, Object attr) { + + ArrayList univList = new ArrayList(); + ArrayList gaList = Shape3DRetained.getGeomAtomsList(mirror.users, univList); + + // Send to rendering attribute structure, regardless of + // whether there are users or not (alternate appearance case ..) + J3dMessage createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDERING_ATTRIBUTES; + createMessage.type = J3dMessage.TRANSPARENCYATTRIBUTES_CHANGED; + createMessage.universe = null; + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + createMessage.args[3] = new Integer(changedFrequent); + VirtualUniverse.mc.processMessage(createMessage); + + + // System.out.println("univList.size is " + univList.size()); + for(int i=0; i<univList.size(); i++) { + createMessage = VirtualUniverse.mc.getMessage(); + createMessage.threads = J3dThread.UPDATE_RENDER; + createMessage.type = J3dMessage.TRANSPARENCYATTRIBUTES_CHANGED; + + createMessage.universe = (VirtualUniverse) univList.get(i); + createMessage.args[0] = this; + createMessage.args[1]= new Integer(attrMask); + createMessage.args[2] = attr; + + ArrayList gL = (ArrayList) gaList.get(i); + GeometryAtom[] gaArr = new GeometryAtom[gL.size()]; + gL.toArray(gaArr); + createMessage.args[3] = gaArr; + + VirtualUniverse.mc.processMessage(createMessage); + } + + } + + void handleFrequencyChange(int bit) { + if (bit == TransparencyAttributes.ALLOW_MODE_WRITE || + bit == TransparencyAttributes.ALLOW_VALUE_WRITE|| + bit == TransparencyAttributes.ALLOW_BLEND_FUNCTION_WRITE) { + setFrequencyChangeMask(bit, 0x1); + } + } + + + +} diff --git a/src/classes/share/javax/media/j3d/TransparencyInterpolator.java b/src/classes/share/javax/media/j3d/TransparencyInterpolator.java new file mode 100644 index 0000000..c524f6a --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransparencyInterpolator.java @@ -0,0 +1,271 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; + +/** + * TransparencyInterpolator behavior. This class defines a behavior + * that modifies the transparency of its target TransparencyAttributes + * object by linearly interpolating between a pair of specified + * transparency values (using the value generated by the specified + * Alpha object). + * <P> + * There are two forms of constructor to specify the + * type of transparency interpolation. The first constructor takes + * an Alpha and a TransparencyAttributes object and creates a transparency + * interpolator that maps an Alpha value of 1.0 to a transparency + * value of 1.0, and an Alpha value of 0.0 and maps it to a + * transparency value of 0.0. The second constructor takes an Alpha, + * a TransparencyAttributes object, a minimum transparency value and a + * maximum transparency value. This constructor provides more + * flexibility by specifying how the Alpha values are mapped + * to the transparency values - an Alpha of 1.0 maps to the + * maximum transparency value and an Alpha of 0.0 maps to the + * minimum transparency value.<P> + * + * @see Alpha + * @see TransparencyAttributes + */ + + +public class TransparencyInterpolator extends Interpolator { + + TransparencyAttributes target; + float minimumTransparency; + float maximumTransparency; + + // We can't use a boolean flag since it is possible + // that after alpha change, this procedure only run + // once at alpha.finish(). So the best way is to + // detect alpha value change. + private float prevAlphaValue = Float.NaN; + private WakeupCriterion passiveWakeupCriterion = + (WakeupCriterion) new WakeupOnElapsedFrames(0, true); + + // non-public, default constructor used by cloneNode + TransparencyInterpolator() { + } + + /** + * Constructs a trivial transparency interpolator with a specified target, + * a minimum transparency of 0.0f and a maximum transparency of 1.0f. + * @param alpha the alpha object for this interpolator + * @param target the TransparencyAttributes component object affected + * by this interpolator + */ + public TransparencyInterpolator(Alpha alpha, + TransparencyAttributes target) { + + super(alpha); + + this.target = target; + this.minimumTransparency = 0.0f; + this.maximumTransparency = 1.0f; + } + + /** + * Constructs a new transparency interpolator that varies the target + * material's transparency between the two transparency values. + * @param alpha the alpha object for this Interpolator + * @param target the TransparencyAttributes component object affected + * by this interpolator + * @param minimumTransparency the starting transparency + * @param maximumTransparency the ending transparency + */ + public TransparencyInterpolator(Alpha alpha, + TransparencyAttributes target, + float minimumTransparency, + float maximumTransparency) { + + super(alpha); + + this.target = target; + this.minimumTransparency = minimumTransparency; + this.maximumTransparency = maximumTransparency; + } + + /** + * This method sets the minimumTransparency for this interpolator. + * @param transparency the new minimum transparency + */ + public void setMinimumTransparency(float transparency) { + this.minimumTransparency = transparency; + } + + /** + * This method retrieves this interpolator's minimumTransparency. + * @return the interpolator's minimum transparency value + */ + public float getMinimumTransparency() { + return this.minimumTransparency; + } + + /** + * This method sets the maximumTransparency for this interpolator. + * @param transparency the new maximum transparency + */ + public void setMaximumTransparency(float transparency) { + this.maximumTransparency = transparency; + } + + /** + * This method retrieves this interpolator's maximumTransparency. + * @return the interpolator's maximal transparency vslue + */ + public float getMaximumTransparency() { + return this.maximumTransparency; + } + + /** + * This method sets the target TransparencyAttributes object + * for this interpolator. + * @param target the target TransparencyAttributes object + */ + public void setTarget(TransparencyAttributes target) { + this.target = target; + } + + /** + * This method retrieves this interpolator's target reference. + * @return the interpolator's target TransparencyAttributes object + */ + public TransparencyAttributes getTarget() { + return target; + } + + // The TransparencyInterpolator's initialize routine uses the default + // initialization routine. + + /** + * This method is invoked by the behavior scheduler every frame. It + * maps the alpha value that corresponds to the current time into a + * transparency value and updates the specified TransparencyAttributes + * object with this new transparency value. + * @param criteria an enumeration of the criteria that caused the + * stimulus + */ + public void processStimulus(Enumeration criteria) { + // Handle stimulus + WakeupCriterion criterion = passiveWakeupCriterion; + + if (alpha != null) { + float value = alpha.value(); + + if (value != prevAlphaValue) { + float val = (float)((1.0-value)*minimumTransparency + + value*maximumTransparency); + + target.setTransparency(val); + prevAlphaValue = value; + } + if (!alpha.finished() && !alpha.isPaused()) { + criterion = defaultWakeupCriterion; + } + } + wakeupOn(criterion); + } + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + TransparencyInterpolator ti = new TransparencyInterpolator(); + ti.duplicateNode(this, forceDuplicate); + return ti; + } + + + /** + * Copies all TransparencyInterpolator information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + TransparencyInterpolator ti = + (TransparencyInterpolator) originalNode; + + setMinimumTransparency(ti.getMinimumTransparency()); + setMaximumTransparency(ti.getMaximumTransparency()); + + // this reference will be updated in updateNodeReferences() + setTarget(ti.getTarget()); + } + + /** + * Callback used to allow a node to check if any nodes referenced + * by that node have been duplicated via a call to <code>cloneTree</code>. + * This method is called by <code>cloneTree</code> after all nodes in + * the sub-graph have been duplicated. The cloned Leaf node's method + * will be called and the Leaf node can then look up any node references + * by using the <code>getNewObjectReference</code> method found in the + * <code>NodeReferenceTable</code> object. If a match is found, a + * reference to the corresponding Node in the newly cloned sub-graph + * is returned. If no corresponding reference is found, either a + * DanglingReferenceException is thrown or a reference to the original + * node is returned depending on the value of the + * <code>allowDanglingReferences</code> parameter passed in the + * <code>cloneTree</code> call. + * <p> + * NOTE: Applications should <i>not</i> call this method directly. + * It should only be called by the cloneTree method. + * + * @param referenceTable a NodeReferenceTableObject that contains the + * <code>getNewObjectReference</code> method needed to search for + * new object instances. + * @see NodeReferenceTable + * @see Node#cloneTree + * @see DanglingReferenceException + */ + public void updateNodeReferences(NodeReferenceTable referenceTable) { + super.updateNodeReferences(referenceTable); + + // check TransparencyAttributes + NodeComponent nc = getTarget(); + + if (nc != null) { + setTarget((TransparencyAttributes) referenceTable.getNewObjectReference(nc)); + } + } + + +} diff --git a/src/classes/share/javax/media/j3d/TransparentRenderingInfo.java b/src/classes/share/javax/media/j3d/TransparentRenderingInfo.java new file mode 100644 index 0000000..5008b22 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TransparentRenderingInfo.java @@ -0,0 +1,104 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import javax.vecmath.*; +import java.util.*; + +class TransparentRenderingInfo extends Object { + // For DepthSortedTransparency, rm is the rendermolecule + // that this rInfo is part of + // For non depth sorted transparency, rm is one of the rendermolecules + // in the textureBin that is rendered, all renderMolecules under + // rm.textureBin's will be rendered + RenderMolecule rm; + RenderAtomListInfo rInfo; + TransparentRenderingInfo prev; + TransparentRenderingInfo next; + double zVal; // Used in DepthSorted Transparency + // TODO: Add Dirty info + + /** + * update state before rendering transparent objects + */ + boolean updateState(Canvas3D cv) { + + TextureBin textureBin = rm.textureBin; + AttributeBin attributeBin = textureBin.attributeBin; + + // Get a collection to check if switch is on + + RenderMolecule rm = textureBin.transparentRMList ; + + // Optimization to skip updating Attributes if + // all switch are off. Note that switch condition + // is check again in rm.render(). + while (rm != null) { + if (rm.isSwitchOn()) { + break; + } + if (rm.next != null) { + rm = rm.next; + } else { + rm = rm.nextMap; + } + } + + if (rm == null) { + return false; + } + + if (cv.environmentSet != attributeBin.environmentSet) { + + boolean visible = (attributeBin.definingRenderingAttributes == null || + attributeBin.definingRenderingAttributes.visible); + + if ( (attributeBin.environmentSet.renderBin.view.viewCache.visibilityPolicy + == View.VISIBILITY_DRAW_VISIBLE && !visible) || + (attributeBin.environmentSet.renderBin.view.viewCache.visibilityPolicy + == View.VISIBILITY_DRAW_INVISIBLE && visible)) { + return false; + } + attributeBin.environmentSet.lightBin.updateAttributes(cv); + attributeBin.environmentSet.updateAttributes(cv); + attributeBin.updateAttributes(cv); + } + else { + if (cv.attributeBin != attributeBin) { + boolean visible = (attributeBin.definingRenderingAttributes == null || + attributeBin.definingRenderingAttributes.visible); + + if ( (attributeBin.environmentSet.renderBin.view.viewCache.visibilityPolicy + == View.VISIBILITY_DRAW_VISIBLE && !visible) || + (attributeBin.environmentSet.renderBin.view.viewCache.visibilityPolicy + == View.VISIBILITY_DRAW_INVISIBLE && visible)) { + return false; + } + attributeBin.updateAttributes(cv); + } + } + return true; + } + + void render(Canvas3D cv) { + if (updateState(cv)) { + rm.textureBin.render(cv, rm.textureBin.transparentRMList); + } + } + + + void sortRender(Canvas3D cv) { + if (updateState(cv)) { + rm.textureBin.render(cv, this); + } + } +} diff --git a/src/classes/share/javax/media/j3d/TriangleArray.java b/src/classes/share/javax/media/j3d/TriangleArray.java new file mode 100644 index 0000000..984e87f --- /dev/null +++ b/src/classes/share/javax/media/j3d/TriangleArray.java @@ -0,0 +1,154 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The TriangleArray object draws the array of vertices as individual + * triangles. Each group + * of three vertices defines a triangle to be drawn. + */ + +public class TriangleArray extends GeometryArray { + + // non-public, no parameter constructor + TriangleArray() {} + + /** + * Constructs an empty TriangleArray object with the specified + * number of vertices, and vertex format. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @exception IllegalArgumentException if vertexCount is less than 3 + * or vertexCount is <i>not</i> a multiple of 3 + */ + public TriangleArray(int vertexCount, int vertexFormat) { + super(vertexCount,vertexFormat); + + if (vertexCount < 3 || ((vertexCount%3) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("TriangleArray0")); + } + + /** + * Constructs an empty TriangleArray object with the specified + * number of vertices, and vertex format, number of texture coordinate + * sets, and texture coordinate mapping array. + * + * @param vertexCount the number of vertex elements in this array<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used. + * + * @exception IllegalArgumentException if vertexCount is less than 3 + * or vertexCount is <i>not</i> a multiple of 3 + * + * @since Java 3D 1.2 + */ + public TriangleArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap); + + if (vertexCount < 3 || ((vertexCount%3) != 0)) + throw new IllegalArgumentException(J3dI18N.getString("TriangleArray0")); + } + + /** + * Creates the retained mode TriangleArrayRetained object that this + * TriangleArray object will point to. + */ + void createRetained() { + this.retained = new TriangleArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + TriangleArrayRetained rt = (TriangleArrayRetained) retained; + int texSetCount = rt.getTexCoordSetCount(); + TriangleArray t; + + if (texSetCount == 0) { + t = new TriangleArray(rt.getVertexCount(), + rt.getVertexFormat()); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + t = new TriangleArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap); + } + t.duplicateNodeComponent(this); + return t; + } +} diff --git a/src/classes/share/javax/media/j3d/TriangleArrayRetained.java b/src/classes/share/javax/media/j3d/TriangleArrayRetained.java new file mode 100644 index 0000000..749e3a2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TriangleArrayRetained.java @@ -0,0 +1,414 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The TriangleArray object draws the array of vertices as individual + * triangles. Each group + * of three vertices defines a triangle to be drawn. + */ + +class TriangleArrayRetained extends GeometryArrayRetained { + + TriangleArrayRetained() { + this.geoType = GEO_TYPE_TRI_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[3]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectRay(pnts, pickRay, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectSegment(pnts, pickSegment.start, + pickSegment.end, sdist, iPnt) + && (sdist[0] <= 1.0)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, + sdist,iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectCylinder(pnts, pickCylinder, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("TriangleArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + } + + + boolean intersect(Point3d[] pnts) { + Point3d[] points = new Point3d[3]; + double dist[] = new double[1]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle + while (i<validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2])) { + return true; + } + } + break; + case 4: // Quad + while (i<validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[2], pnts[3])) { + return true; + } + } + break; + case 2: // Line + while (i<validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + if (intersectSegment(points, pnts[0], pnts[1], dist, + null)) { + return true; + } + } + break; + case 1: // Point + while (i<validVertexCount) { + getVertexData(i++, points[0]); + getVertexData(i++, points[1]); + getVertexData(i++, points[2]); + if (intersectTriPnt(points[0], points[1], points[2], + pnts[0])) { + return true; + } + } + break; + } + return false; + } + + + boolean intersect(Transform3D thisToOtherVworld, + GeometryRetained geom) { + + Point3d[] pnts = new Point3d[3]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + thisToOtherVworld.transform(pnts[0]); + thisToOtherVworld.transform(pnts[1]); + thisToOtherVworld.transform(pnts[2]); + if (geom.intersect(pnts)) { + return true; + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + Point3d[] pnts = new Point3d[3]; + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[1]); + if (intersectBoundingSphere(pnts, bsphere, null, + null)) { + return true; + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < validVertexCount) { + getVertexData(i++, pnts[0]); + getVertexData(i++, pnts[1]); + getVertexData(i++, pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, + null, null)) { + return true; + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + + return false; + } + + // From Graphics Gems IV (pg5) and Graphics Gems II, Pg170 + void computeCentroid() { + int i = ((vertexFormat & GeometryArray.BY_REFERENCE) == 0 ? + initialVertexIndex : initialCoordIndex); + + Point3d pnt0 = getPoint3d(); + Point3d pnt1 = getPoint3d(); + Point3d pnt2 = getPoint3d(); + Vector3d vec = getVector3d(); + Vector3d normal = getVector3d(); + Vector3d tmpvec = getVector3d(); + + double area; + double totalarea = 0; + + centroid.x = 0; + centroid.y = 0; + centroid.z = 0; + + + while(i < validVertexCount) { + getVertexData(i++, pnt0); + getVertexData(i++, pnt1); + getVertexData(i++, pnt2); + + // Determine the normal + vec.sub(pnt0, pnt1); + tmpvec.sub(pnt1, pnt2); + + // Do the cross product + normal.cross(vec, tmpvec); + normal.normalize(); + + // If a degenerate triangle, don't include + if (Double.isNaN(normal.x + normal.y + normal.z)) + continue; + + // compute the area + getCrossValue(pnt0, pnt1, tmpvec); + getCrossValue(pnt1, pnt2, tmpvec); + getCrossValue(pnt2, pnt0, tmpvec); + area = normal.dot(tmpvec); + centroid.x += (pnt0.x + pnt1.x + pnt2.x)* area; + centroid.y += (pnt0.y + pnt1.y + pnt2.y)* area; + centroid.z += (pnt0.z + pnt1.z + pnt2.z)* area; + totalarea += area; + + } + if (totalarea != 0.0) { + area = 1.0/(3.0 * totalarea); + centroid.x *= area; + centroid.y *= area; + centroid.z *= area; + } + freeVector3d(tmpvec); + freeVector3d(vec); + freeVector3d(normal); + freePoint3d(pnt0); + freePoint3d(pnt1); + freePoint3d(pnt2); + } + + int getClassType() { + return TRIANGLE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/TriangleFanArray.java b/src/classes/share/javax/media/j3d/TriangleFanArray.java new file mode 100644 index 0000000..c5757f9 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TriangleFanArray.java @@ -0,0 +1,179 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The TriangleFanArray object draws an array of vertices as a set of + * connected triangle fans. An array of per-strip + * vertex counts specifies where the separate strips (fans) appear + * in the vertex array. For every strip in the set, + * each vertex, beginning with the third vertex in the array, + * defines a triangle to be drawn using the current vertex, + * the previous vertex and the first vertex. This can be thought of + * as a collection of convex polygons. + */ + +public class TriangleFanArray extends GeometryStripArray { + + // non-public, no parameter constructor + TriangleFanArray() {} + + /** + * Constructs an empty TriangleFanArray object with the specified + * number of vertices, vertex format, and + * array of per-strip vertex counts. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 3 + * or any element in the stripVertexCounts array is less than 3 + */ + public TriangleFanArray(int vertexCount, + int vertexFormat, + int stripVertexCounts[]) { + + super(vertexCount, vertexFormat, stripVertexCounts); + + if (vertexCount < 3 ) + throw new IllegalArgumentException(J3dI18N.getString("TriangleFanArray0")); + } + + /** + * Constructs an empty TriangleFanArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and + * array of per-strip vertex counts. + * + * @param vertexCount the number of vertex elements in this array<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 3 + * or any element in the stripVertexCounts array is less than 3 + * + * @since Java 3D 1.2 + */ + public TriangleFanArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int stripVertexCounts[]) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + stripVertexCounts); + + if (vertexCount < 3 ) + throw new IllegalArgumentException(J3dI18N.getString("TriangleFanArray0")); + } + + /** + * Creates the retained mode TriangleFanArrayRetained object that this + * TriangleFanArray object will point to. + */ + void createRetained() { + this.retained = new TriangleFanArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + TriangleFanArrayRetained rt = (TriangleFanArrayRetained) retained; + int stripcounts[] = new int[rt.getNumStrips()]; + rt.getStripVertexCounts(stripcounts); + int texSetCount = rt.getTexCoordSetCount(); + TriangleFanArray t; + if (texSetCount == 0) { + t = new TriangleFanArray(rt.getVertexCount(), + rt.getVertexFormat(), + stripcounts); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + t = new TriangleFanArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + stripcounts); + } + t.duplicateNodeComponent(this); + return t; + } + +} diff --git a/src/classes/share/javax/media/j3d/TriangleFanArrayRetained.java b/src/classes/share/javax/media/j3d/TriangleFanArrayRetained.java new file mode 100644 index 0000000..68a42f4 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TriangleFanArrayRetained.java @@ -0,0 +1,499 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The TriangleFanArray object draws an array of vertices as a set of + * connected triangle fans. An array of per-strip + * vertex counts specifies where the separate strips (fans) appear + * in the vertex array. For every strip in the set, + * each vertex, beginning with the third vertex in the array, + * defines a triangle to be drawn using the current vertex, + * the previous vertex and the first vertex. This can be thought of + * as a collection of convex polygons. + */ + +class TriangleFanArrayRetained extends GeometryStripArrayRetained { + + TriangleFanArrayRetained() { + this.geoType = GEO_TYPE_TRI_FAN_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[3]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = 0; + int j, end; + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectRay(pnts, pickRay, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectSegment(pnts, pickSegment.start, + pickSegment.end, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, + sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("TriangleFanArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + } + + // intersect pnts[] with every triangle in this object + boolean intersect(Point3d[] pnts) { + int j, end; + Point3d[] points = new Point3d[3]; + double dist[] = new double[1]; + int i = 0; + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + + + + switch (pnts.length) { + case 3: // Triangle + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + getVertexData(j++, points[1]); + while (j < end) { + getVertexData(j++, points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2])) { + return true; + } + points[1].set(points[2]); + } + } + break; + case 4: // Quad + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + getVertexData(j++, points[1]); + while (j < end) { + getVertexData(j++, points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[2], pnts[3])) { + return true; + } + points[1].set(points[2]); + } + } + break; + case 2: // Line + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + getVertexData(j++, points[1]); + while (j < end) { + getVertexData(j++, points[2]); + if (intersectSegment(points, pnts[0], pnts[1], + dist, null)) { + return true; + } + points[1].set(points[2]); + } + } + break; + case 1: // Point + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + getVertexData(j++, points[1]); + while (j < end) { + getVertexData(j++, points[2]); + if (intersectTriPnt(points[0], points[1], points[2], + pnts[0])) { + return true; + } + points[1].set(points[2]); + } + } + break; + } + return false; + } + + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) { + int i = 0, j, end; + Point3d[] pnts = new Point3d[3]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + thisToOtherVworld.transform(pnts[0]); + thisToOtherVworld.transform(pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + thisToOtherVworld.transform(pnts[2]); + if (geom.intersect(pnts)) { + return true; + } + pnts[1].set(pnts[2]); + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + int i = 0; + int j, end; + Point3d[] pnts = new Point3d[3]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + end = j + stripVertexCounts[i++]; + while ( j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while ( j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, null, null)) { + return true; + } + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while ( j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, null, null)) { + return true; + } + pnts[1].set(pnts[2]); + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + // From Graphics Gems IV (pg5) and Graphics Gems II, Pg170 + void computeCentroid() { + Vector3d vec = getVector3d(); + Vector3d normal = getVector3d(); + Vector3d tmpvec = getVector3d(); + Point3d pnt0 = getPoint3d(); + Point3d pnt1 = getPoint3d(); + Point3d pnt2 = getPoint3d(); + double area, totalarea = 0; + int end, replaceIndex, j, i = 0; + centroid.x = 0; + centroid.y = 0; + centroid.z = 0; + + while( i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnt0); + getVertexData(j++, pnt1); + replaceIndex = 2; + while (j < end) { + area = 0; + if (replaceIndex == 2) { + getVertexData(j++, pnt2); + replaceIndex = 1; + } else { + getVertexData(j++, pnt1); + replaceIndex = 2; + } + + // Determine the normal + vec.sub(pnt0, pnt1); + tmpvec.sub(pnt1, pnt2); + + // Do the cross product + normal.cross(vec, tmpvec); + normal.normalize(); + // If a degenerate triangle, don't include + if (Double.isNaN(normal.x + normal.y + normal.z)) + continue; + + tmpvec.set(0,0,0); + + // compute the area + getCrossValue(pnt0, pnt1, tmpvec); + getCrossValue(pnt1, pnt2, tmpvec); + getCrossValue(pnt2, pnt0, tmpvec); + area = normal.dot(tmpvec); + totalarea += area; + centroid.x += (pnt0.x + pnt1.x + pnt2.x) * area; + centroid.y += (pnt0.y + pnt1.y + pnt2.y) * area; + centroid.z += (pnt0.z + pnt1.z + pnt2.z) * area; + + } + } + + if (totalarea != 0.0) { + area = 1.0/(3.0 * totalarea); + centroid.x *= area; + centroid.y *= area; + centroid.z *= area; + } + freeVector3d(tmpvec); + freeVector3d(vec); + freeVector3d(normal); + freePoint3d(pnt0); + freePoint3d(pnt1); + freePoint3d(pnt2); + } + + int getClassType() { + return TRIANGLE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/TriangleStripArray.java b/src/classes/share/javax/media/j3d/TriangleStripArray.java new file mode 100644 index 0000000..48c05c5 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TriangleStripArray.java @@ -0,0 +1,178 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The TriangleStripArray object draws an array of vertices as a set of + * connected triangle strips. An array of per-strip vertex counts specifies + * where the separate strips appear in the vertex array. + * For every strip in the set, + * each vertex, beginning with the third vertex in the array, + * defines a triangle to be drawn using the current vertex and + * the two previous vertices. + */ + +public class TriangleStripArray extends GeometryStripArray { + + // non-public, no parameter constructor + TriangleStripArray() {} + + /** + * Constructs an empty TriangleStripArray object with the specified + * number of vertices, vertex format, and + * array of per-strip vertex counts. + * @param vertexCount the number of vertex elements in this array + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D. + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 3 + * or any element in the stripVertexCounts array is less than 3 + */ + public TriangleStripArray(int vertexCount, + int vertexFormat, + int stripVertexCounts[]) { + + super(vertexCount, vertexFormat, stripVertexCounts); + + if (vertexCount < 3 ) + throw new IllegalArgumentException(J3dI18N.getString("TriangleStripArray0")); + } + + /** + * Constructs an empty TriangleStripArray object with the specified + * number of vertices, vertex format, number of texture coordinate + * sets, texture coordinate mapping array, and + * array of per-strip vertex counts. + * + * @param vertexCount the number of vertex elements in this array<p> + * + * @param vertexFormat a mask indicating which components are + * present in each vertex. This is specified as one or more + * individual flags that are bitwise "OR"ed together to describe + * the per-vertex data. + * The flags include: COORDINATES, to signal the inclusion of + * vertex positions--always present; NORMALS, to signal + * the inclusion of per vertex normals; one of COLOR_3, + * COLOR_4, to signal the inclusion of per vertex + * colors (without or with color information); and one of + * TEXTURE_COORDINATE_2, TEXTURE_COORDINATE_3 or TEXTURE_COORDINATE_4, + * to signal the + * inclusion of per-vertex texture coordinates 2D, 3D or 4D.<p> + * + * @param texCoordSetCount the number of texture coordinate sets + * in this GeometryArray object. If <code>vertexFormat</code> + * does not include one of <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3 or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetCount</code> parameter is not used.<p> + * + * @param texCoordSetMap an array that maps texture coordinate + * sets to texture units. The array is indexed by texture unit + * number for each texture unit in the associated Appearance + * object. The values in the array specify the texture coordinate + * set within this GeometryArray object that maps to the + * corresponding texture + * unit. All elements within the array must be less than + * <code>texCoordSetCount</code>. A negative value specifies that + * no texture coordinate set maps to the texture unit + * corresponding to the index. If there are more texture units in + * any associated Appearance object than elements in the mapping + * array, the extra elements are assumed to be -1. The same + * texture coordinate set may be used for more than one texture + * unit. Each texture unit in every associated Appearance must + * have a valid source of texture coordinates: either a + * non-negative texture coordinate set must be specified in the + * mapping array or texture coordinate generation must be enabled. + * Texture coordinate generation will take precedence for those + * texture units for which a texture coordinate set is specified + * and texture coordinate generation is enabled. If + * <code>vertexFormat</code> does not include one of + * <code>TEXTURE_COORDINATE_2</code>, + * <code>TEXTURE_COORDINATE_3</code> or + * <code>TEXTURE_COORDINATE_4</code>, the + * <code>texCoordSetMap</code> array is not used.<p> + * + * @param stripVertexCounts array that specifies + * the count of the number of vertices for each separate strip. + * The length of this array is the number of separate strips. + * + * @exception IllegalArgumentException if vertexCount is less than 3 + * or any element in the stripVertexCounts array is less than 3 + * + * @since Java 3D 1.2 + */ + public TriangleStripArray(int vertexCount, + int vertexFormat, + int texCoordSetCount, + int[] texCoordSetMap, + int stripVertexCounts[]) { + + super(vertexCount, vertexFormat, + texCoordSetCount, texCoordSetMap, + stripVertexCounts); + + if (vertexCount < 3 ) + throw new IllegalArgumentException(J3dI18N.getString("TriangleStripArray0")); + } + + /** + * Creates the retained mode TriangleStripArrayRetained object that this + * TriangleStripArray object will point to. + */ + void createRetained() { + this.retained = new TriangleStripArrayRetained(); + this.retained.setSource(this); + } + + + /** + * @deprecated replaced with cloneNodeComponent(boolean forceDuplicate) + */ + public NodeComponent cloneNodeComponent() { + TriangleStripArrayRetained rt = (TriangleStripArrayRetained) retained; + int stripcounts[] = new int[rt.getNumStrips()]; + rt.getStripVertexCounts(stripcounts); + int texSetCount = rt.getTexCoordSetCount(); + TriangleStripArray t; + if (texSetCount == 0) { + t = new TriangleStripArray(rt.getVertexCount(), + rt.getVertexFormat(), + stripcounts); + } else { + int texMap[] = new int[rt.getTexCoordSetMapLength()]; + rt.getTexCoordSetMap(texMap); + t = new TriangleStripArray(rt.getVertexCount(), + rt.getVertexFormat(), + texSetCount, + texMap, + stripcounts); + + } + t.duplicateNodeComponent(this); + return t; + } +} diff --git a/src/classes/share/javax/media/j3d/TriangleStripArrayRetained.java b/src/classes/share/javax/media/j3d/TriangleStripArrayRetained.java new file mode 100644 index 0000000..9547520 --- /dev/null +++ b/src/classes/share/javax/media/j3d/TriangleStripArrayRetained.java @@ -0,0 +1,519 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; + +/** + * The TriangleStripArray object draws an array of vertices as a set of + * connected triangle strips. An array of per-strip vertex counts specifies + * where the separate strips appear in the vertex array. + * For every strip in the set, + * each vertex, beginning with the third vertex in the array, + * defines a triangle to be drawn using the current vertex and + * the two previous vertices. + */ + +class TriangleStripArrayRetained extends GeometryStripArrayRetained { + + TriangleStripArrayRetained() { + this.geoType = GEO_TYPE_TRI_STRIP_SET; + } + + boolean intersect(PickShape pickShape, double dist[], Point3d iPnt) { + Point3d pnts[] = new Point3d[3]; + double sdist[] = new double[1]; + double minDist = Double.MAX_VALUE; + double x = 0, y = 0, z = 0; + int i = 0; + int j, end; + + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + switch (pickShape.getPickType()) { + case PickShape.PICKRAY: + PickRay pickRay= (PickRay) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectRay(pnts, pickRay, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKSEGMENT: + PickSegment pickSegment = (PickSegment) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectSegment(pnts, pickSegment.start, + pickSegment.end, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGBOX: + BoundingBox bbox = (BoundingBox) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingBox(pnts, bbox, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, sdist, + iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) + ((PickBounds) pickShape).bounds; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, + sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKCYLINDER: + PickCylinder pickCylinder= (PickCylinder) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectCylinder(pnts, pickCylinder, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKCONE: + PickCone pickCone= (PickCone) pickShape; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + if (intersectCone(pnts, pickCone, sdist, iPnt)) { + if (dist == null) { + return true; + } + if (sdist[0] < minDist) { + minDist = sdist[0]; + x = iPnt.x; + y = iPnt.y; + z = iPnt.z; + } + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKPOINT: + // Should not happen since API already check for this + throw new IllegalArgumentException(J3dI18N.getString("TriangleStripArrayRetained0")); + default: + throw new RuntimeException ("PickShape not supported for intersection"); + } + + if (minDist < Double.MAX_VALUE) { + dist[0] = minDist; + iPnt.x = x; + iPnt.y = y; + iPnt.z = z; + return true; + } + return false; + } + + // intersect pnts[] with every triangle in this object + boolean intersect(Point3d[] pnts) { + int j, end; + Point3d[] points = new Point3d[3]; + double dist[] = new double[1]; + int i = 0; + + points[0] = new Point3d(); + points[1] = new Point3d(); + points[2] = new Point3d(); + + switch (pnts.length) { + case 3: // Triangle + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + getVertexData(j++, points[1]); + while (j < end) { + getVertexData(j++, points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2])) { + return true; + } + points[0].set(points[1]); + points[1].set(points[2]); + } + } + break; + case 4: // Quad + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + getVertexData(j++, points[1]); + while (j < end) { + getVertexData(j++, points[2]); + if (intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[1], pnts[2]) || + intersectTriTri(points[0], points[1], points[2], + pnts[0], pnts[2], pnts[3])) { + return true; + } + points[0].set(points[1]); + points[1].set(points[2]); + } + } + break; + case 2: // Line + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + getVertexData(j++, points[1]); + while (j < end) { + getVertexData(j++, points[2]); + if (intersectSegment(points, pnts[0], pnts[1], + dist, null)) { + return true; + } + points[0].set(points[1]); + points[1].set(points[2]); + } + } + break; + case 1: // Point + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, points[0]); + getVertexData(j++, points[1]); + while (j < end) { + getVertexData(j++, points[2]); + if (intersectTriPnt(points[0], points[1], points[2], + pnts[0])) { + return true; + } + points[0].set(points[1]); + points[1].set(points[2]); + } + } + break; + } + return false; + } + + boolean intersect(Transform3D thisToOtherVworld, GeometryRetained geom) { + int i = 0, j, end; + Point3d[] pnts = new Point3d[3]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + thisToOtherVworld.transform(pnts[0]); + thisToOtherVworld.transform(pnts[1]); + while (j < end) { + getVertexData(j++, pnts[2]); + thisToOtherVworld.transform(pnts[2]); + if (geom.intersect(pnts)) { + return true; + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + return false; + } + + // the bounds argument is already transformed + boolean intersect(Bounds targetBound) { + int i = 0; + int j, end; + Point3d[] pnts = new Point3d[3]; + pnts[0] = new Point3d(); + pnts[1] = new Point3d(); + pnts[2] = new Point3d(); + + + switch(targetBound.getPickType()) { + case PickShape.PICKBOUNDINGBOX: + BoundingBox box = (BoundingBox) targetBound; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while ( j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingBox(pnts, box, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGSPHERE: + BoundingSphere bsphere = (BoundingSphere) targetBound; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while ( j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingSphere(pnts, bsphere, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + case PickShape.PICKBOUNDINGPOLYTOPE: + BoundingPolytope bpolytope = (BoundingPolytope) targetBound; + + while (i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnts[0]); + getVertexData(j++, pnts[1]); + while ( j < end) { + getVertexData(j++, pnts[2]); + if (intersectBoundingPolytope(pnts, bpolytope, null, null)) { + return true; + } + pnts[0].set(pnts[1]); + pnts[1].set(pnts[2]); + } + } + break; + default: + throw new RuntimeException("Bounds not supported for intersection " + + targetBound); + } + return false; + } + + // From Graphics Gems IV (pg5) and Graphics Gems II, Pg170 + void computeCentroid() { + Point3d pnt0 = getPoint3d(); + Point3d pnt1 = getPoint3d(); + Point3d pnt2 = getPoint3d(); + Vector3d vec = getVector3d(); + Vector3d normal = getVector3d(); + Vector3d tmpvec = getVector3d(); + + double area, totalarea = 0; + int end, replaceIndex, j, i = 0; + centroid.x = 0; + centroid.y = 0; + centroid.z = 0; + + while( i < stripVertexCounts.length) { + j = stripStartVertexIndices[i]; + end = j + stripVertexCounts[i++]; + getVertexData(j++, pnt0); + getVertexData(j++, pnt1); + replaceIndex = 2; + while (j < end) { + area = 0; + switch (replaceIndex) { + case 0: + getVertexData(j++, pnt0); + replaceIndex = 1; + break; + case 1: + getVertexData(j++, pnt1); + replaceIndex = 2; + break; + default: + getVertexData(j++, pnt2); + replaceIndex = 0; + } + + // Determine the normal + vec.sub(pnt0, pnt1); + tmpvec.sub(pnt1, pnt2); + + // Do the cross product + normal.cross(vec, tmpvec); + normal.normalize(); + // If a degenerate triangle, don't include + if (Double.isNaN(normal.x + normal.y + normal.z)) + continue; + + tmpvec.set(0,0,0); + + // compute the area + getCrossValue(pnt0, pnt1, tmpvec); + getCrossValue(pnt1, pnt2, tmpvec); + getCrossValue(pnt2, pnt0, tmpvec); + area = normal.dot(tmpvec); + totalarea += area; + centroid.x += (pnt0.x + pnt1.x + pnt2.x) * area; + centroid.y += (pnt0.y + pnt1.y + pnt2.y) * area; + centroid.z += (pnt0.z + pnt1.z + pnt2.z) * area; + + } + } + + if (totalarea != 0.0) { + area = 1.0/(3.0 * totalarea); + centroid.x *= area; + centroid.y *= area; + centroid.z *= area; + } + + freeVector3d(tmpvec); + freeVector3d(vec); + freeVector3d(normal); + freePoint3d(pnt0); + freePoint3d(pnt1); + freePoint3d(pnt2); + } + + + int getClassType() { + return TRIANGLE_TYPE; + } +} diff --git a/src/classes/share/javax/media/j3d/UnorderList.java b/src/classes/share/javax/media/j3d/UnorderList.java new file mode 100644 index 0000000..c1bdc75 --- /dev/null +++ b/src/classes/share/javax/media/j3d/UnorderList.java @@ -0,0 +1,574 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.Arrays; + +/** + * A strongly type unorder array list. + * The operation add(Object o) & remove(int i) take O(1) time. + * The class is designed to optimize speed. So many reductance + * procedures call and range check as found in ArrayList are + * removed. + * + * <p> + * Use the following code to iterate through an array. + * + * <pre> + * UnorderList list = new UnorderList(YourClass.class); + * // add element here + * + * YourClass[] arr = (YourClass []) list.toArray(); + * int size = list.arraySize(); + * for (int i=0; i < size; i++) { + * YourClass obj = arr[i]; + * .... + * } + * </pre> + * + * <p> + * Note: + * <ul> + * 1) The array return is a copied of internal array.<br> + * 2) Don't use arr.length , use list.arraySize();<br> + * 3) No need to do casting for individual element as in + * ArrayList.<br> + * 4) UnorderList is thread safe. + * </ul> + */ + +class UnorderList implements Cloneable, java.io.Serializable { + + /** + * The array buffer into which the elements of the ArrayList are stored. + * The capacity of the ArrayList is the length of this array buffer. + * + * It is non-private to enable compiler do inlining for get(), + * set(), remove() when -O flag turn on. + */ + transient Object elementData[]; + + /** + * Clone copy of elementData return by toArray(true); + */ + transient Object cloneData[]; + // size of the above clone objec. + transient int cloneSize; + + transient boolean isDirty = true; + + /** + * Component Type of individual array element entry + */ + + Class componentType; + + /** + * The size of the ArrayList (the number of elements it contains). + * + * We make it non-private to enable compiler do inlining for + * getSize() when -O flag turn on. + */ + int size; + + + /** + * Constructs an empty list with the specified initial capacity. + * and the class data Type + * + * @param initialCapacity the initial capacity of the list. + * @param componentType class type of element in the list. + */ + UnorderList(int initialCapacity, Class componentType) { + this.componentType = componentType; + this.elementData = (Object[])java.lang.reflect.Array.newInstance( + componentType, initialCapacity); + } + + /** + * Constructs an empty list. + * @param componentType class type of element in the list. + */ + UnorderList(Class componentType) { + this(10, componentType); + } + + + /** + * Constructs an empty list with the specified initial capacity. + * + * @param initialCapacity the initial capacity of the list. + */ + UnorderList(int initialCapacity) { + this(initialCapacity, Object.class); + } + + + /** + * Constructs an empty list. + * componentType default to Object. + */ + UnorderList() { + this(10, Object.class); + } + + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list. + */ + final int size() { + return size; + } + + + /** + * Returns the size of entry use in toArray() number of elements + * in this list. + * + * @return the number of elements in this list. + */ + final int arraySize() { + return cloneSize; + } + + /** + * Tests if this list has no elements. + * + * @return <tt>true</tt> if this list has no elements; + * <tt>false</tt> otherwise. + */ + final boolean isEmpty() { + return size == 0; + } + + /** + * Returns <tt>true</tt> if this list contains the specified element. + * + * @param o element whose presence in this List is to be tested. + */ + synchronized final boolean contains(Object o) { + if (o != null) { // common case first + for (int i=size-1; i >= 0; i--) + if (o.equals(elementData[i])) + return true; + } else { + for (int i=size-1; i >= 0; i--) + if (elementData[i]==null) + return true; + } + return false; + + } + + + /** + * Add Object into the list if it is not already exists. + * + * @param o an object to add into the list + * @return true if object successfully add, false if duplicate found + */ + synchronized final boolean addUnique(Object o) { + if (!contains(o)) { + add(o); + return true; + } + return false; + } + + /** + * Searches for the last occurence of the given argument, testing + * for equality using the <tt>equals</tt> method. + * + * @param o an object. + * @return the index of the first occurrence of the argument in this + * list; returns <tt>-1</tt> if the object is not found. + * @see Object#equals(Object) + */ + synchronized final int indexOf(Object o) { + if (o != null) { // common case first + for (int i=size-1; i >= 0; i--) + if (o.equals(elementData[i])) + return i; + } else { + for (int i=size-1; i >= 0; i--) + if (elementData[i]==null) + return i; + } + return -1; + } + + /** + * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The + * elements themselves are not copied.) + * + * @return a clone of this <tt>ArrayList</tt> instance. + */ + synchronized protected final Object clone() { + try { + UnorderList v = (UnorderList)super.clone(); + v.elementData = (Object[])java.lang.reflect.Array.newInstance( + componentType, size); + System.arraycopy(elementData, 0, v.elementData, 0, size); + isDirty = true; // can't use the old cloneData reference + return v; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + + /** + * Returns an array containing all of the elements in this list. + * The size of the array may longer than the actual size. Use + * arraySize() to retrieve the size. + * The array return is a copied of internal array. if copy + * is true. + * + * @return an array containing all of the elements in this list + */ + synchronized final Object[] toArray(boolean copy) { + if (copy) { + if (isDirty) { + if ((cloneData == null) || cloneData.length < size) { + cloneData = (Object[])java.lang.reflect.Array.newInstance( + componentType, size); + } + System.arraycopy(elementData, 0, cloneData, 0, size); + cloneSize = size; + isDirty = false; + } + return cloneData; + } else { + cloneSize = size; + return elementData; + } + + } + + /** + * Returns an array containing all of the elements in this list. + * The size of the array may longer than the actual size. Use + * arraySize() to retrieve the size. + * The array return is a copied of internal array. So another + * thread can continue add/delete the current list. However, + * it should be noticed that two call to toArray() may return + * the same copy. + * + * @return an array containing all of the elements in this list + */ + synchronized final Object[] toArray() { + return toArray(true); + } + + + /** + * Returns an array containing elements starting from startElement + * all of the elements in this list. A new array of exact size + * is always allocated. + * + * @param startElement starting element to copy + * + * @return an array containing elements starting from + * startElement, null if element not found. + * + */ + synchronized final Object[] toArray(Object startElement) { + int idx = indexOf(startElement); + if (idx < 0) { + return (Object[])java.lang.reflect.Array.newInstance(componentType, 0); + } + + int s = size - idx; + Object data[] = (Object[])java.lang.reflect.Array.newInstance(componentType, s); + System.arraycopy(elementData, idx, data, 0, s); + return data; + } + + // copy element to objs and clear the array + synchronized final void toArrayAndClear(Object[] objs) { + System.arraycopy(elementData, 0, objs, 0, size); + Arrays.fill(elementData, 0, size, null); + size = 0; + isDirty = true; + } + + + /** + * Trims the capacity of this <tt>ArrayList</tt> instance to be the + * list's current size. An application can use this operation to minimize + * the storage of an <tt>ArrayList</tt> instance. + */ + synchronized final void trimToSize() { + if (elementData.length > size) { + Object oldData[] = elementData; + elementData = (Object[])java.lang.reflect.Array.newInstance( + componentType, + size); + System.arraycopy(oldData, 0, elementData, 0, size); + } + } + + + // Positional Access Operations + + /** + * Returns the element at the specified position in this list. + * + * @param index index of element to return. + * @return the element at the specified position in this list. + * @throws IndexOutOfBoundsException if index is out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final Object get(int index) { + return elementData[index]; + } + + /** + * Replaces the element at the specified position in this list with + * the specified element. + * + * @param index index of element to replace. + * @param element element to be stored at the specified position. + * @return the element previously at the specified position. + * @throws IndexOutOfBoundsException if index out of range + * <tt>(index < 0 || index >= size())</tt>. + */ + synchronized final void set(int index, Object element) { + elementData[index] = element; + isDirty = true; + } + + /** + * Appends the specified element to the end of this list. + * It is the user responsible to ensure that the element add is of + * the same type as array componentType. + * + * @param o element to be appended to this list. + */ + synchronized final void add(Object o) { + if (elementData.length == size) { + Object oldData[] = elementData; + elementData = (Object[])java.lang.reflect.Array.newInstance( + componentType, + (size << 1)); + System.arraycopy(oldData, 0, elementData, 0, size); + + } + elementData[size++] = o; + isDirty = true; + } + + + /** + * Removes the element at the specified position in this list. + * Replace the removed element by the last one. + * + * @param index the index of the element to removed. + * @throws IndexOutOfBoundsException if index out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final void remove(int index) { + elementData[index] = elementData[--size]; + elementData[size] = null; + isDirty = true; + /* + if ((cloneData != null) && (index < cloneData.length)) { + cloneData[index] = null; // for gc + } + */ + } + + + /** + * Removes the element at the specified position in this list. + * The order is keep. + * + * @param index the index of the element to removed. + * @throws IndexOutOfBoundsException if index out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final void removeOrdered(int index) { + size--; + if (index < size) { + System.arraycopy(elementData, index+1, + elementData, index, size-index); + + } + // gc for last element + elementData[size] = null; + isDirty = true; + } + + + /** + * Removes the element at the last position in this list. + * @return The element remove + * @throws IndexOutOfBoundsException if array is empty + */ + synchronized final Object removeLastElement() { + Object elm = elementData[--size]; + elementData[size] = null; + isDirty = true; + /* + if ((cloneData != null) && (size < cloneData.length)) { + cloneData[size] = null; // for gc + } + */ + return elm; + } + + + // Shift element of array from positin idx to position 0 + // Note that idx < size, otherwise ArrayIndexOutOfBoundsException + // throws. The element remove are copy to objs. + synchronized final void shift(Object objs[], int idx) { + int oldsize = size; + + System.arraycopy(elementData, 0, objs, 0, idx); + size -= idx; + if (size > 0) { + System.arraycopy(elementData, idx, elementData, 0, size); + } + Arrays.fill(elementData, size, oldsize, null); + } + + /** + * Removes the specified element in this list. + * Replace the removed element by the last one. + * + * @param o the element to removed. + * @return true if object remove + * @throws IndexOutOfBoundsException if index out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final boolean remove(Object o) { + size--; + if (o != null) { + for (int i=size; i >= 0; i--) { + if (o.equals(elementData[i])) { + elementData[i] = elementData[size]; + elementData[size] = null; + /* + if ((cloneData != null) && (i < cloneData.length)) { + cloneData[i] = null; // for gc + } + */ + isDirty = true; + return true; + } + } + } else { + for (int i=size; i >= 0; i--) + if (elementData[i]==null) { + elementData[i] = elementData[size]; + elementData[size] = null; + /* + if ((cloneData != null) && (i < cloneData.length)) { + cloneData[i] = null; // for gc + } + */ + isDirty = true; + return true; + } + } + size++; // fail to remove + return false; + } + + + /** + * Removes all of the elements from this list. The list will + * be empty after this call returns. + */ + synchronized final void clear() { + if (size > 0) { + Arrays.fill(elementData, 0, size, null); + size = 0; + isDirty = true; + } + } + + synchronized final void clearMirror() { + if (cloneData != null) { + Arrays.fill(cloneData, 0, cloneData.length, null); + } + cloneSize = 0; + isDirty = true; + } + + final Class getComponentType() { + return componentType; + } + + synchronized public String toString() { + StringBuffer sb = new StringBuffer("Size = " + size + "\n["); + int len = size-1; + Object obj; + + for (int i=0; i < size; i++) { + obj = elementData[i]; + if (obj != null) { + sb.append(elementData[i].toString()); + } else { + sb.append("NULL"); + } + if (i != len) { + sb.append(", "); + } + } + sb.append("]\n"); + return sb.toString(); + } + + /** + * Save the state of the <tt>ArrayList</tt> instance to a stream (that + * is, serialize it). + * + * @serialData The length of the array backing the <tt>ArrayList</tt> + * instance is emitted (int), followed by all of its elements + * (each an <tt>Object</tt>) in the proper order. + */ + private synchronized void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException{ + // Write out element count, and any hidden stuff + s.defaultWriteObject(); + + // Write out array length + s.writeInt(elementData.length); + + // Write out all elements in the proper order. + for (int i=0; i<size; i++) + s.writeObject(elementData[i]); + + } + + /** + * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, + * deserialize it). + */ + private synchronized void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // Read in size, and any hidden stuff + s.defaultReadObject(); + + // Read in array length and allocate array + int arrayLength = s.readInt(); + elementData = (Object[])java.lang.reflect.Array.newInstance( + componentType, arrayLength); + + // Read in all elements in the proper order. + for (int i=0; i<size; i++) + elementData[i] = s.readObject(); + } +} diff --git a/src/classes/share/javax/media/j3d/UpdateTargets.java b/src/classes/share/javax/media/j3d/UpdateTargets.java new file mode 100644 index 0000000..4328453 --- /dev/null +++ b/src/classes/share/javax/media/j3d/UpdateTargets.java @@ -0,0 +1,99 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +class UpdateTargets { + + static int updateSwitchThreads[] = { + // GEO + J3dThread.UPDATE_RENDER | J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_GEOMETRY, + + // ENV + J3dThread.UPDATE_RENDER | J3dThread.UPDATE_RENDERING_ENVIRONMENT, + + // BEH + J3dThread.UPDATE_BEHAVIOR, + + // SND + J3dThread.UPDATE_SOUND | J3dThread.SOUND_SCHEDULER, + + // VPF + J3dThread.UPDATE_RENDER | J3dThread.UPDATE_BEHAVIOR | + J3dThread.UPDATE_SOUND | J3dThread.SOUND_SCHEDULER, + + // BLN + J3dThread.UPDATE_RENDER | J3dThread.UPDATE_RENDERING_ENVIRONMENT | + J3dThread.UPDATE_BEHAVIOR | J3dThread.UPDATE_SOUND, + + // GRP + 0 + }; + + + UnorderList[] targetList = new UnorderList[Targets.MAX_NODELIST]; + + int computeSwitchThreads() { + int switchThreads = 0; + + for (int i=0; i < Targets.MAX_NODELIST; i++) { + if (targetList[i] != null) { + switchThreads |= updateSwitchThreads[i]; + } + } + return switchThreads | J3dThread.UPDATE_TRANSFORM; + } + + void addNode(Object node, int targetType) { + if(targetList[targetType] == null) + targetList[targetType] = new UnorderList(1); + + targetList[targetType].add(node); + } + + + void addNodeArray(Object[] nodeArr, int targetType) { + if(targetList[targetType] == null) + targetList[targetType] = new UnorderList(1); + + targetList[targetType].add(nodeArr); + } + + + void clearNodes() { + for(int i=0; i<Targets.MAX_NODELIST; i++) { + if (targetList[i] != null) { + targetList[i].clear(); + } + } + } + + void addCachedTargets(CachedTargets cachedTargets) { + for(int i=0; i<Targets.MAX_NODELIST; i++) { + if (cachedTargets.targetArr[i] != null ) { + addNodeArray(cachedTargets.targetArr[i], i); + } + } + } + + void dump() { + for(int i=0; i<Targets.MAX_NODELIST; i++) { + if (targetList[i] != null) { + System.out.println(" " + CachedTargets.typeString[i]); + for(int j=0; j<targetList[i].size(); j++) { + System.out.println(" " + targetList[i].get(j)); + } + } + } + } +} diff --git a/src/classes/share/javax/media/j3d/VersionInfo.java b/src/classes/share/javax/media/j3d/VersionInfo.java new file mode 100644 index 0000000..3e52f01 --- /dev/null +++ b/src/classes/share/javax/media/j3d/VersionInfo.java @@ -0,0 +1,209 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The VersionInfo class contains strings that describe the implementation + * and specification version of Java 3D. These strings are made available + * as properties obtained from the VirtualUniverse class. + * + * <h4>NOTE TO DEVELOPERS:</h4> + * + * <p> + * Developers are required to do the following whenever they modify + * Java 3D: + * + * <ol> + * <li>The VENDOR_DEVELOPER string must be modified to + * indicate the name of the individuals or organizations who have + * modified the source code.</li> + * + * <li>The VERSION_DEV_STRING may be modified to indicate + * additional information about the particular build, but this is + * not required.</li> + * + * <li>The strings denoted as being unmodifiable must <i>not</i> be + * modified.</li> + * </ol> + * + * <p> + * Additionally, developers are required to comply with the terms + * of the Java 3D API specification, which prohibits releasing an + * implementation of the Java 3D API without first licensing and + * passing the TCK tests. + * + * @see VirtualUniverse#getProperties + */ +class VersionInfo extends Object { + /** + * Developer who has modified Java 3D. + * This string <i>must</i> be modified to indicate the name of the + * individual(s) or organization(s) who modified the code. + */ + private static final String VENDOR_DEVELOPER = null; + + /** + * String identifying the particular build of Java 3D, for + * example, beta1, build47, rc1, etc. This string may only + * contain letters, numbers, periods, dashes, or underscores. It + * must not contain any other characters or spaces. + * + * This will typically by null for final, released builds, but + * should be non-null for all other builds. + */ + private static final String VERSION_BUILD = "build2"; + + /** + * Time and date stamp appended to the end of the version string. + * This is appended to the version string + * after the build identifier (and after the first space, which + * will automatically be added) and before the optional dev + * string. This string should be null if no time stamp is desired + * (it will be null for production builds). + */ + private static final String VERSION_TIME_STAMP = J3dBuildInfo.getBuildTimeStamp(); + + /** + * An optional string appended to the end of the version string, + * after the time stamp. A space will be automatically prepended + * to this string. This string should be null if no dev string is + * desired. + */ + private static final String VERSION_DEV_STRING = null; + + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + // END OF DEVELOPER-MODIFIABLE PARAMETERS + // ------------------------------------------------------------------- + // ------------------------------------------------------------------- + + + // ------------------------------------------------------------------- + // The following set of constants must not be modified by developers. + // + // Only qualified licensees of the Java 3D API specification and + // TCK tests, who are releasing their own implementation of Java 3D + // are permitted to change these constants. + // ------------------------------------------------------------------- + + /** + * Specification version (major and minor version only). This + * string must not be modified by developers. + */ + private static final String SPECIFICATION_VERSION = "1.3"; + + /** + * Specification vendor. This should never change and must not + * be modified by developers. + */ + private static final String SPECIFICATION_VENDOR = "Sun Microsystems, Inc."; + + /** + * Primary implementation vendor. This should only be changed by a + * platform vendor who has licensed the TCK tests and who is + * releasing their own implementation of Java 3D. + */ + private static final String VENDOR_PRIMARY = "Sun Microsystems, Inc."; + + /** + * Base version number. This is the major.minor.subminor version + * number. Version qualifiers are specified separately. The + * major and minor version <i>must</i> be the same as the specification + * version. + */ + private static final String VERSION_BASE = "1.3.2"; + + /** + * Qualifier indicating that the version of Java 3D is + * experimental. This must <i>not</i> be modified by deverlopers. + * All non-official builds <i>must</i> contain the string + * <code>"experimental"</code> as part of the release name that + * appears before the optional first space. + */ + private static final String VERSION_SUFFIX = "experimental"; + + /** + * The composite version string. This is composed in the static + * initializer for this class. + */ + private static final String VERSION; + + /** + * The composite vendor string. This is composed in the static + * initializer for this class. + */ + private static final String VENDOR; + + // The static initializer composes the version and vendor strings + static { + // Assign the vendor by concatenating primary and developer + // vendor strings + String tmpVendor = VENDOR_PRIMARY; + if (VENDOR_DEVELOPER != null) { + tmpVendor += " & " + VENDOR_DEVELOPER; + } + + String tmpVersion = VERSION_BASE; + if (VERSION_BUILD != null) { + tmpVersion += "-" + VERSION_BUILD; + } + + if (VERSION_SUFFIX != null) { + tmpVersion += "-" + VERSION_SUFFIX; + } + + if (VERSION_TIME_STAMP != null) { + tmpVersion += " " + VERSION_TIME_STAMP; + } + + if (VERSION_DEV_STRING != null) { + tmpVersion += " " + VERSION_DEV_STRING; + } + + VERSION = tmpVersion; + VENDOR = tmpVendor; + } + + /** + * Returns the specification version string. + * @return the specification version string + */ + static String getSpecificationVersion() { + return SPECIFICATION_VERSION; + } + + /** + * Returns the specification vendor string. + * @return the specification vendor string + */ + static String getSpecificationVendor() { + return SPECIFICATION_VENDOR; + } + + + /** + * Returns the implementation version string. + * @return the implementation version string + */ + static String getVersion() { + return VERSION; + } + + /** + * Returns the implementation vendor string. + * @return the implementation vendor string + */ + static String getVendor() { + return VENDOR; + } +} diff --git a/src/classes/share/javax/media/j3d/VertexArrayRenderMethod.java b/src/classes/share/javax/media/j3d/VertexArrayRenderMethod.java new file mode 100644 index 0000000..759b6fd --- /dev/null +++ b/src/classes/share/javax/media/j3d/VertexArrayRenderMethod.java @@ -0,0 +1,88 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * The RenderMethod interface is used to create various ways to render + * different geometries. + */ + +class VertexArrayRenderMethod implements RenderMethod { + + + public boolean render(RenderMolecule rm, Canvas3D cv, int pass, + RenderAtomListInfo ra, int dirtyBits) { + + GeometryArrayRetained geo = (GeometryArrayRetained)ra.geometry(); + geo.setVertexFormat((rm.useAlpha && ((geo.vertexFormat & + GeometryArray.COLOR) != 0)), + rm.textureBin.attributeBin.ignoreVertexColors, cv.ctx); + + if (rm.doInfinite) { + cv.updateState(pass, dirtyBits); + while (ra != null) { + renderGeo(ra, rm, pass, cv); + ra = ra.next; + } + return true; + } + + boolean isVisible = false; // True if any of the RAs is visible. + while (ra != null) { + if (cv.ra == ra.renderAtom) { + if (cv.raIsVisible) { + cv.updateState(pass, dirtyBits); + renderGeo(ra, rm, pass, cv); + isVisible = true; + } + } + else { + if (ra.renderAtom.localeVwcBounds.intersect(cv.viewFrustum)) { + cv.updateState(pass, dirtyBits); + cv.raIsVisible = true; + renderGeo(ra, rm, pass, cv); + isVisible = true; + } + else { + cv.raIsVisible = false; + } + cv.ra = ra.renderAtom; + } + + ra = ra.next; + } + geo.disableGlobalAlpha(cv.ctx, + (rm.useAlpha && ((geo.vertexFormat & + GeometryArray.COLOR) != 0)), + rm.textureBin.attributeBin.ignoreVertexColors); + return isVisible; + } + + void renderGeo(RenderAtomListInfo ra, RenderMolecule rm, int pass, Canvas3D cv) { + GeometryArrayRetained geo; + boolean useAlpha; + + useAlpha = rm.useAlpha; + + geo = (GeometryArrayRetained)ra.geometry(); + + + geo.execute(cv, ra.renderAtom, rm.isNonUniformScale, + (useAlpha && ((geo.vertexFormat & GeometryArray.COLOR) != 0)) , + rm.alpha, + rm.renderBin.multiScreen, + cv.screen.screen, + rm.textureBin.attributeBin.ignoreVertexColors, + pass); + } +} diff --git a/src/classes/share/javax/media/j3d/View.java b/src/classes/share/javax/media/j3d/View.java new file mode 100644 index 0000000..51ed5e2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/View.java @@ -0,0 +1,3360 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.lang.Math; +import java.util.Vector; +import java.util.ArrayList; +import java.util.Enumeration; +import java.awt.*; +import java.awt.event.*; +import com.sun.j3d.utils.universe.*; // Needed for Support of DVR. + +/** + * The View object contains all parameters needed in rendering a + * three dimensional scene from one viewpoint. A view contains a list + * of Canvas3D objects that the view is rendered into. It exists outside + * of the scene graph, but attaches to a ViewPlatform leaf node object + * in the scene graph. It also contains a reference to a PhysicalBody + * and a PhysicalEnvironment object. + * <P> + * The View object is the main Java 3D object for controlling the + * Java 3D viewing model. All of the components that specify the + * view transform used to render to the 3D canvases are either contained + * in the View object or in objects that are referenced by the View + * object. + * <P> + * Java 3D allows applications to specify multiple simultaneously active + * View objects, each controlling its own set of canvases. + * <P> + * The Java 3D View object has several instance variables and methods, + * but most are calibration variables or user-helping functions. The + * viewing policies defined by the View object are described below. + * <P> + * <b>Policies</b><P> + * + * The View object defines the following policies:<P> + * <UL> + * <LI>View policy - informs Java 3D whether it should generate + * the view using the head-tracked system of transformations or the + * head-mounted system of transformations. These policies are attached + * to the Java 3D View object. There are two view policies:</LI><P> + * <UL> + * <LI>SCREEN_VIEW - specifies that Java 3D should compute a new + * viewpoint using the sequence of transforms appropriate to screen-based + * head-tracked display environments (fish-tank VR/portals/VR-desks). + * This is the default setting.</LI><P> + * <LI>HMD_VIEW - specifies that Java 3D should compute a new viewpoint + * using the sequence of transforms appropriate to head mounted display + * environments. This policy is not available in compatibility mode + * (see the setCompatibilityModeEnable method description).</LI><P> + * </UL> + * <LI>Projection policy - specifies whether Java 3D should generate + * a parallel projection or a perspective projection. This policy + * is attached to the Java 3D View object. There are two projection + * policies:</LI><P> + * <UL> + * <LI>PARALLEL_PROJECTION - specifies that a parallel projection + * transform is computed.</LI><P> + * <LI>PERSPECTIVE_PROJECTION - specifies that a perspective projection + * transform is computed. This is the default policy.</LI><P> + * </UL> + * <LI>Screen scale policy - specifies where the screen scale comes from. + * There are two screen scale policies:</LI><P> + * <UL> + * <LI>SCALE_SCREEN_SIZE - specifies that the scale is derived from the + * physical screen according to the following formula (this is the + * default mode):</LI> + * <UL> + * <code>screenScale = physicalScreenWidth / 2.0</code><P> + * </UL> + * <LI>SCALE_EXPLICIT - pecifies that the scale is taken directly from + * the user-provided <code>screenScale</code> attribute (see the + * setScreenScale method description).</LI><P> + * </UL> + * <LI>Window resize policy - specifies how Java 3D modifies the view + * when users resize windows. When users resize or move windows, + * Java 3D can choose to think of the window as attached either to + * the physical world or to the virtual world. The window resize + * policy allows an application to specify how the + * view model will handle resizing requests. + * There are two window resize policies:</LI><P> + * <UL> + * <LI>VIRTUAL_WORLD - implies that the original image remains the + * same size on the screen but the user sees more or less of the + * virtual world depending on whether the window grew or shrank + * in size.</LI><P> + * <LI>PHYSICAL_WORLD - implies that the original image continues + * to fill the window in the same way using more or less pixels + * depending on whether the window grew or shrank in size.</LI><P> + * </UL> + * <LI>Window movement policy - specifies what part of the virtual + * world Java 3D draws as a function of window placement on the screen. + * There are two window movement policies:</LI><P> + * <UL> + * <LI>VIRTUAL_WORLD - implies that the image seen in the window + * changes as the position of the window shifts on the screen. + * (This mode acts as if the window were a window into the virtual + * world.)</LI><P> + * <LI>PHYSICAL_WORLD - implies that the image seen in the window + * remains the same no matter where the user positions the window + * on the screen.</LI><P> + * </UL> + * <LI>Window eyepoint policy - comes into effect in a non-head-tracked + * environment. The policy tells Java 3D how to construct a new view + * frustum based on changes in the field of view and in the Canvas3D's + * location on the screen. The policy only comes into effect when the + * application changes a parameter that can change the placement of the + * eyepoint relative to the view frustum. + * There are three window eyepoint policies:</LI><P> + * <UL> + * <LI>RELATIVE_TO_SCREEN - tells Java 3D to interpret the eye's position + * relative to the entire screen. No matter where an end user moves a + * window (a Canvas3D), Java 3D continues to interpret the eye's position + * relative to the screen. This implies that the view frustum changes shape + * whenever an end user moves the location of a window on the screen. + * In this mode, the field of view is read-only.</LI><P> + * <LI>RELATIVE_TO_WINDOW - specifies that Java 3D should interpret the + * eye's position information relative to the window (Canvas3D). No matter + * where an end user moves a window (a Canvas3D), Java 3D continues to + * interpret the eye's position relative to that window. This implies + * that the frustum remains the same no matter where the end user + * moves the window on the screen. In this mode, the field of view + * is read-only.</LI><P> + * <LI>RELATIVE_TO_FIELD_OF_VIEW - tells Java 3D that it should + * modify the eyepoint position so it is located at the appropriate + * place relative to the window to match the specified field of view. + * This implies that the view frustum will change whenever the + * application changes the field of view. In this mode, the eye + * position is read-only. This is the default setting.</LI><P> + * <LI>RELATIVE_TO_COEXISTENCE - tells Java 3D to interpret the eye's + * position in coexistence coordinates. In this mode, the eye position + * is taken from the view (rather than the Canvas3D) and transformed from + * coexistence coordinates to image plate coordinates for each + * Canvas3D. The resulting eye position is relative to the screen. As + * in RELATIVE_TO_SCREEN mode, this implies that the view frustum + * changes shape whenever an end user moves the location of a window + * on the screen. In this mode, the field of view is + * read-only.</LI><P> + * </UL> + * <LI>Front and back clip policies - specifies how Java 3D + * interprets clipping distances to both the near and far clip + * planes. The policies can contain one of four values specifying + * whether a distance measurement should be interpreted in + * the physical or the virtual world and whether that distance + * measurement should be interpreted relative to the physical + * eyepoint or the physical screen. + * The front and back clip policies + * are specified separately. The front clip policy determines + * where Java 3D places the front clipping plane. The back clip + * policy determines where Java 3D places the back clipping plane. + * The values for both front and back clipping planes are:</LI><P> + * <UL> + * <LI>VIRTUAL_EYE - specifies that the associated distance is from + * the eye and in units of virtual distance.</LI><P> + * <LI>PHYSICAL_EYE - specifies that the associated distance is from + * the eye and in units of physical distance (in meters).</LI><P> + * <LI>VIRTUAL_SCREEN - specifies that the associated distance is + * from the screen and in units of virtual distance. </LI><P> + * <LI>PHYSICAL_SCREEN - specifies that the associated distance is + * from the screen and in units of physical distance (in meters). + * This is the default policy for both front and back clipping.</LI><P> + * </UL> + * <LI>Visibility policy - specifies how visible and invisible objects + * are drawn. There are three visibility policies:</LI><P> + * <UL> + * <LI>VISIBILITY_DRAW_VISIBLE - only visible objects are drawn + * (this is the default).</LI><P> + * <LI>VISIBILITY_DRAW_INVISIBLE - only invisible objects are drawn.</LI><P> + * <LI>VISIBILITY_DRAW_ALL - both visible and invisible + * objects are drawn. </LI><P> + * </UL> + * <LI>Transparency sorting policy - specifies whether and how + * transparent objects are sorted. Sorting multiple transparent + * objects is necessary to avoid artifacts caused by overlapping + * transparent objects. There are two transparency sorting + * policies:</LI><P> + * <UL> + * <LI>TRANSPARENCY_SORT_NONE - no depth sorting of transparent + * objects is performed (this is the default). Transparent objects + * are drawn after opaque objects, but are not sorted from back to + * front.</LI><P> + * <LI>TRANSPARENCY_SORT_GEOMETRY - transparent objects are + * depth-sorted on a per-geometry basis. Each geometry object of each + * transparent Shape3D node is drawn from back to front. Note that + * this policy will not split geometry into smaller pieces, so + * intersecting or intertwined objects may not be sorted + * correctly. The method used for determining which geometry is closer + * is implementation dependent.</LI><P> + * </UL> + * </UL> + * <b>Projection and Clip Parameters</b><P> + * The projection and clip parameters determine the view model's + * field of view and the front and back clipping distances.<P> + * <UL> + * <LI>Field of view - specifies the view model's horizontal + * field of view in radians, when in the default non-head-tracked + * mode. This value is ignored when the view model is operating + * in head-tracked mode, or when the Canvas3D's window eyepoint + * policy is set to a value other than the default setting of + * RELATIVE_TO_FIELD_OF_VIEW.</LI><P> + * <LI>Front clip distance - specifies the distance away from the + * clip origin, specified by the front clip policy variable, in + * the direction of gaze where objects stop disappearing. Objects + * closer than the clip origin (eye or screen) + * plus the front clip distance are not drawn. Measurements are + * done in the space (physical or virtual) that is specified by + * the associated front clip policy parameter.</LI><P> + * <LI>Back clip distance - specifies the distance away from the + * clip origin (specified by the back clip policy variable) in the + * direction of gaze where objects begin disappearing. Objects + * farther away from the clip origin (eye or + * screen) plus the back clip distance are not drawn. + * Measurements are done in the space (physical or virtual) that + * is specified by the associated back clip policy + * parameter. The View object's back clip distance is ignored + * if the scene graph contains an active Clip leaf node.</LI><P> + * There are several considerations to take into account when + * choosing values for the front and back clip distances.<P> + * <UL> + * <LI>The front clip distance must be greater than 0.0 in physical + * eye coordinates.</LI><P> + * <LI>The front clipping plane must be in front of the back clipping + * plane, that is, the front clip distance must be less than the + * back clip distance in physical eye coordinates.</LI><P> + * <LI>The front and back clip distances, in physical eye coordinates, + * must be less than the largest positive single-precision floating + * point value, Float.MAX_VALUE. In practice, since these physical + * eye coordinate distances are in meters, the values + * should be much less than that.</LI><P> + * <LI>The ratio of the back distance divided by the front distance, + * in physical eye coordinates, affects Z-buffer precision. This ratio + * should be less than about 3000 to accommodate 16-bit Z-buffers. + * Values of 100 to less than 1000 will produce better results.</LI><P> + * </UL> + * Violating any of the above rules will result in undefined behavior. + * In many cases, no picture will be drawn.<P> + * </UL> + * <b>Frame Start Time, Duration, and Number</b><P> + * + * There are five methods used to get information about system + * execution and performance:<P> + * <UL> + * <code>getCurrentFrameStartTime</code> returns the time at which + * the most recent rendering frame started.<P> + * <code>getLastFrameDuration</code> returns the duration, in milliseconds, of + * the most recently completed rendering frame.<P> + * <code>getFrameNumber</code> returns the frame number for this view.<P> + * <code>getMaxFrameStartTimes</code> retrieves the implementation-dependent + * maximum number of frames whose start times will be recorded by + * the system.<P> + * <code>getFrameStartTimes</code> copies the last k frame start time values + * into the user-specified array.<P> + * </UL> + * <b>View Traversal and Behavior Scheduling</b><P> + * The following methods control the traversal, the rendering, and + * the execution of the behavior scheduler for this view:<P> + * <UL> + * <code>startBehaviorScheduler</code> starts the behavior scheduler + * running after it has been stopped.<P> + * <code>stopBehaviorScheduler</code> stops the behavior scheduler after all + * currently-scheduled behaviors are executed.<P> + * <code>isBehaviorSchedulerRunning</code> retrieves a flag that indicates + * whether the behavior scheduler is currently running.<P> + * <code>startView</code> starts traversing this view and starts the renderers + * associated with all canvases attached to this view.<P> + * <code>stopView</code> stops traversing this view after the current state of + * the scene graph is reflected on all canvases attached to this + * view.<P> + * <code>isViewRunning</code> returns a flag indicating whether the traverser + * is currently running on this view.<P> + * </UL> + * Note: The above six methods are heavy-weight methods intended + * for verification and image capture (recording). They are not + * intended to be used for flow control.<P> + * + * <b>Scene Antialiasing</b><P> + * + * The following methods set and retrieve the scene antialiasing + * flag. Scene antialiasing is either enabled or disabled for this + * view. If enabled, the entire scene will be antialiased on each + * canvas in which scene antialiasing is available. Scene + * antialiasing is disabled by default.<P> + * <UL> + * <code>setSceneAntialiasingEnable</code> sets the scene antialiasing flag.<P> + * <code>getSceneAntialiasingEnable</code> returns the scene antialiasing + * flag.<P> + * </UL> + * Note that line and point antialiasing are independent of scene + * antialiasing. If antialiasing is enabled for lines and points, + * the lines and points will be antialiased prior to scene antialiasing. + * If scene antialiasing is disabled, antialiased lines and points will + * still be antialiased. + * <p> + * <b>Note:</b> Scene antialiasing is ignored in pure immediate mode, + * but is supported in mixed-immediate mode. + * <p> + * <b>Depth Buffer</b><P> + * + * The following two methods enable and disable automatic freezing + * of the depth buffer for objects rendered during the transparent + * rendering pass (that is, objects rendered using alpha blending) + * for this view. If enabled, depth buffer writes are disabled + * during the transparent rendering pass regardless of the value + * of the depth-buffer-write-enable flag in the RenderingAttributes + * object for a particular node. This flag is enabled by default.<P> + * <UL> + * <code>setDepthBufferFreezeTransparent</code> enables depth buffer freezing.<P> + * <code>getDepthBufferFreezeTransparent</code> retrieves the depth buffer + * flag.<P> + * </UL> + * Transparent objects include BLENDED transparent primitives + * and antialiased lines + * and points. Transparent objects do not include opaque objects + * or primitives rendered with SCREEN_DOOR transparency.<p> + * + * <b>Sensors</b><P> + * + * The following methods retrieve the sensor's location in the + * virtual world:<P> + * <UL> + * <code>getSensorToVworld</code> takes the sensor's last reading and + * generates a sensor-to-vworld coordinate system transform. This + * Transform3D object takes points in that sensor's local coordinate + * system and transforms them into virtual world coordinates.<P> + * + * <code>getSensorHotSpotInVworld</code> retrieves the specified sensor's + * last hotspot location in virtual world coordinates.<P> + * </UL> + * + * <b>Compatibility Mode</b><P> + * + * A camera-based view model allows application programmers to think + * about the images displayed on the computer screen as if a virtual + * camera took those images. Such a view model allows application + * programmers to position and orient a virtual camera within a + * virtual scene, to manipulate some parameters of the virtual + * camera's lens (specify its field of view), and to specify the + * locations of the near and far clipping planes.<P> + * Java 3D allows applications to enable compatibility mode for + * room-mounted, non-head-tracked display environments, or to disable + * compatibility mode using the following methods. Camera-based + * viewing functions are only available in compatibility mode.<P> + * <UL> + * <code>setCompatibilityModeEnable</code> turns compatibility mode on or off. + * Compatibility mode is disabled by default.<P> + * <code>getCompatabilityModeEnable</code> returns the compatibility mode + * enable flag.<P> + * </UL> + * Use of these view-compatibility functions will disable some of + * Java 3D's view model features and limit the portability of Java + * 3D programs. These methods are primarily intended to help + * jump-start porting of existing applications.<P> + * + * Setting the Viewing Transform<P> + * + * The View object provides the following compatibility-mode + * methods that operate on the viewing transform.<P> + * <UL> + * <code>setVpcToEc</code> a compatibility mode method that + * specifies the ViewPlatform + * coordinates (VPC) to eye coordinates viewing transform.<P> + * <code>getVpcToEc</code> returns the VPC.<P> + * </UL> + * Setting the Projection Transform + * <p> + * The View object provides the following compatibility-mode + * methods that operate on the projection transform:<P> + * <UL> + * The <code>setLeftProjection</code> and <code>setRightProjection</code> + * methods specify + * a viewing frustum for the left and right eye that transforms + * points in eye coordinates to clipping coordinates.<P> + * + * The <code>getLeftProjection</code> and <code>getRightProjection</code> + * methods return + * the viewing frustum for the left and right eye.<P> + * </UL> + * + * @see Canvas3D + * @see PhysicalBody + * @see PhysicalEnvironment + * @see ViewPlatform + * @see TransparencyAttributes + */ + +public class View extends Object { + /** + * Specifies a policy whereby the origin of physical or virtual + * coordinates is relative to the position of the nominal head. + * When used as a view attach policy, this sets the origin of view + * platform coordinates to be at the eyepoint. + * @see ViewPlatform#setViewAttachPolicy + * @see PhysicalEnvironment#setCoexistenceCenterInPworldPolicy + */ + public static final int NOMINAL_HEAD = 0; + + /** + * Specifies a policy whereby the origin of physical or virtual + * coordinates is relative to the position of the nominal feet. + * When used as a view attach policy, this sets the origin of view + * platform coordinates to be at the ground plane. + * @see ViewPlatform#setViewAttachPolicy + * @see PhysicalEnvironment#setCoexistenceCenterInPworldPolicy + */ + public static final int NOMINAL_FEET = 1; + + /** + * Specifies a policy whereby the origin of physical or virtual + * coordinates is relative to the screen. + * When used as a view attach policy, this sets the origin of view + * platform coordinates to be at the center of the window or screen, + * in effect, allowing the user to view objects from an optimal viewpoint. + * @see ViewPlatform#setViewAttachPolicy + * @see PhysicalEnvironment#setCoexistenceCenterInPworldPolicy + */ + public static final int NOMINAL_SCREEN = 2; + + /** + * Specifies that the screen scale for this view is derived from + * the physical screen size. This scale factor is computed as follows: + * <ul> + * physical_screen_width / 2.0 + * </ul> + * This allows an application to define a world in a normalized + * [-1,1] space and view it on a screen of any size. + * @see #setScreenScalePolicy + */ + public static final int SCALE_SCREEN_SIZE = 0; + + /** + * Specifies that the screen scale for this view is taken directly + * from the user-provided screenScale parameter. + * @see #setScreenScalePolicy + * @see #setScreenScale + */ + public static final int SCALE_EXPLICIT = 1; + + /** + * Specifies that the associated distance is measured + * from the screen in virtual world coordinates. + * Policy for interpreting clip plane distances. + * Used in specifying the policy in frontClipPolicy and backClipPolicy. + * @see #setFrontClipPolicy + * @see #setBackClipPolicy + */ + public static final int VIRTUAL_SCREEN = 0; + + /** + * Specifies that the associated distance is measured + * from the screen in meters. + * Policy for interpreting clip plane distances. + * Used in specifying the policy in frontClipPolicy and backClipPolicy. + * @see #setFrontClipPolicy + * @see #setBackClipPolicy + */ + public static final int PHYSICAL_SCREEN = 1; + + /** + * Specifies that the associated distance is measured + * from the eye in virtual world coordinates. + * Policy for interpreting clip plane distances. + * Used in specifying the policy in frontClipPolicy and backClipPolicy. + * @see #setFrontClipPolicy + * @see #setBackClipPolicy + */ + public static final int VIRTUAL_EYE = 2; + + /** + * Specifies that the associated distance is measured + * from the eye in meters. + * Policy for interpreting clip plane distances. + * Used in specifying the policy in frontClipPolicy and backClipPolicy. + * @see #setFrontClipPolicy + * @see #setBackClipPolicy + */ + public static final int PHYSICAL_EYE = 3; + + /** + * Policy for resizing and moving windows. + * Used in specifying windowResizePolicy and windowMovementPolicy. + * VIRTUAL_WORLD specifies that the associated action takes place + * in the virtual world as well as in the physical world. + * @see #setWindowResizePolicy + * @see #setWindowMovementPolicy + */ + public static final int VIRTUAL_WORLD = 0; + + /** + * Policy for resizing and moving windows. + * Used in specifying windowResizePolicy and windowMovementPolicy. + * PHYSICAL_WORLD specifies that the specified action takes place + * only in the physical world. + * @see #setWindowResizePolicy + * @see #setWindowMovementPolicy + */ + public static final int PHYSICAL_WORLD = 1; + + /** + * Policy for placing the eyepoint in non-head-tracked modes. + * Specifies that Java 3D should interpret the + * given fixed eyepoint position as relative to the entire screen. + * This implies + * that the view frustum shape will change whenever a + * user moves the location of a window on the screen. + * @see #setWindowEyepointPolicy + */ + public static final int RELATIVE_TO_SCREEN = 0; + + /** + * Policy for placing the eyepoint in non-head-tracked modes. + * Specifies that Java 3D should interpret the + * given fixed-eyepoint position as relative to the window. + * @see #setWindowEyepointPolicy + */ + public static final int RELATIVE_TO_WINDOW = 1; + + /** + * Policy for placing the eyepoint in non-head-tracked modes. + * Specifies that Java 3D should + * modify the position of the eyepoint to match any changes in field + * of view; the view frustum will change whenever the application + * program changes the field of view. + * <p> + * NOTE: when this policy is specified, the Z coordinate of + * the derived eyepoint is used in place of + * nominalEyeOffsetFromNominalScreen. + * @see #setWindowEyepointPolicy + */ + public static final int RELATIVE_TO_FIELD_OF_VIEW = 2; + + /** + * Policy for placing the eyepoint in non-head-tracked modes. + * Specifies that Java 3D should interpret the fixed eyepoint + * position in the view as relative to the origin + * of coexistence coordinates. This eyepoint is transformed from + * coexistence coordinates to image plate coordinates for each + * Canvas3D. + * As in RELATIVE_TO_SCREEN mode, this implies + * that the view frustum shape will change whenever a + * user moves the location of a window on the screen. + * @see #setWindowEyepointPolicy + * + * @since Java 3D 1.2 + */ + public static final int RELATIVE_TO_COEXISTENCE = 3; + + /** + * Specifies that monoscopic view generated should be the view as seen + * from the left eye. + * @see Canvas3D#setMonoscopicViewPolicy + */ + public static final int LEFT_EYE_VIEW = 0; + + /** + * Specifies that monoscopic view generated should be the view as seen + * from the right eye. + * @see Canvas3D#setMonoscopicViewPolicy + */ + public static final int RIGHT_EYE_VIEW = 1; + + /** + * Specifies that monoscopic view generated should be the view as seen + * from the 'center eye', the fictional eye half-way between the left and + * right eye. + * @see Canvas3D#setMonoscopicViewPolicy + */ + public static final int CYCLOPEAN_EYE_VIEW = 2; + + /** + * Specifies that the viewing environment for this view is a + * standard screen-based display environment. + * In this mode, Java 3D will compute new viewpoints + * using that sequence of transforms appropriate to screen-based, + * display environments, that may or may not include head tracking + * (e.g., a monoscopic screen, fish-tank VR, portals, VR-desks). + * This is the default mode. + * @see #setViewPolicy + */ + public static final int SCREEN_VIEW = 0; + + /** + * Specifies that the viewing environment for this view is a + * head-mounted display environment. + * In this mode, Java 3D will compute new viewpoints + * using that sequence of transforms appropriate to head-mounted display + * environments. These environments are generally head-tracked. + * @see #setViewPolicy + */ + public static final int HMD_VIEW = 1; + + /** + * Specifies that Java 3D should generate a parallel projection matrix + * for this View. + * @see #setProjectionPolicy + */ + public static final int PARALLEL_PROJECTION = 0; + + /** + * Specifies that Java 3D should generate a perspective projection matrix + * for this View. + * This is the default mode. + * @see #setProjectionPolicy + */ + public static final int PERSPECTIVE_PROJECTION = 1; + + /** + * Policy that specifies that only visible objects should be drawn. + * This is the default mode. + * @see #setVisibilityPolicy + * + * @since Java 3D 1.2 + */ + public static final int VISIBILITY_DRAW_VISIBLE = 0; + + /** + * Policy that specifies that only invisible objects should be drawn. + * @see #setVisibilityPolicy + * + * @since Java 3D 1.2 + */ + public static final int VISIBILITY_DRAW_INVISIBLE = 1; + + /** + * Policy that specifies that both visible and invisible objects + * should be drawn. + * @see #setVisibilityPolicy + * + * @since Java 3D 1.2 + */ + public static final int VISIBILITY_DRAW_ALL = 2; + + /** + * Policy that specifies that no sorting of transparent objects + * is done. + * This is the default mode. + * @see #setTransparencySortingPolicy + * + * @since Java 3D 1.3 + */ + public static final int TRANSPARENCY_SORT_NONE = 0; + + /** + * Policy that specifies that transparent objects + * are sorted from back to front on a per-geometry basis. + * @see #setTransparencySortingPolicy + * + * @since Java 3D 1.3 + */ + public static final int TRANSPARENCY_SORT_GEOMETRY = 1; + + + // + // The AWT window for display. + // + // This object can be queried to obtain: + // screen width in pixels + // screen height in pixels + // window width in pixels + // window height in pixels + // window upper left corner location in pixels relative to screen + // + // Use getCanvases() to access this + private Vector canvases = new Vector(3); + + // + // The current universe associated with this view + // + VirtualUniverse universe = null; + + // + // The RenderBin associated with this view. + // + RenderBin renderBin = null; + + // This is the SoundScheduler associated with this view. + SoundScheduler soundScheduler = null; + + // This is the thread associated with this view. + SoundRenderer soundRenderer = new SoundRenderer(); + + // AudioDevice enumerator current position + // AudioDeviceEnumerator allAudioEnumerator = null; + + // These are used for tracking the frame times + static final int NUMBER_FRAME_START_TIMES = 10; + + long[] frameStartTimes = new long[NUMBER_FRAME_START_TIMES]; + long[] frameNumbers = new long[NUMBER_FRAME_START_TIMES]; + int currentFrameIndex = 0; + + // These are the values that are set at the end of each frame + long currentFrameStartTime = 0; + long currentFrameDuration = 0; + long currentFrameNumber = 0; + + // These are the ones that get updated directly by MC + long frameNumber = 0; + long startTime = 0; + long stopTime = 0; + + // Support dynamic video resize -- DVR. + Viewer viewer = null; // Cached the associate viewer of this view. + boolean firstTime = true; + float dvrFactor = 1.0f; + boolean dvrResizeCompensation = true; + + // User adjustable minimum frame cycle time + long minFrameCycleTime; + + // True when stopBehaviorScheduler invoke + boolean stopBehavior; + + // + // View cache for this view. + // + ViewCache viewCache = null; + + // Compatibility mode related field has changed. + // { compatibilityModeEnable, compatVpcToEc, compatLeftProjection, + // compatRightProjection } + static final int COMPATIBILITY_MODE_DIRTY = 0x01; + // ScreenScalePolicy field has changed. + static final int SCREEN_SCALE_POLICY_DIRTY = 0x02; + // Screen scale field has changed. + static final int SCREEN_SCALE_DIRTY = 0x04; + // Window Resize Policy field has changed. + static final int WINDOW_RESIZE_POLICY_DIRTY = 0x08; + // View Policy eye in image plate field has changed. + static final int VIEW_POLICY_DIRTY = 0x10; + // Clip related field has changed. + // { frontClipDistance, backClipDistance, frontClipPolicy, backClipPolicy } + static final int CLIP_DIRTY = 0x20; + // Projection Policy field has changed. + static final int PROJECTION_POLICY_DIRTY = 0x40; + // Window Movement Policy field has changed. + static final int WINDOW_MOVEMENT_POLICY_DIRTY = 0x80; + // Window Eye Point Policy field has changed. + static final int WINDOW_EYE_POINT_POLICY_DIRTY = 0x100; + // Monoscopic View Policy field has changed. + static final int MONOSCOPIC_VIEW_POLICY_DIRTY = 0x200; + // Field Of View field has changed. + static final int FIELD_OF_VIEW_DIRTY = 0x400; + // Tracking Enable field has changed. + static final int TRACKING_ENABLE_DIRTY = 0x800; + // User Head To Vworld Enable field has changed. + static final int USER_HEAD_TO_VWORLD_ENABLE_DIRTY = 0x1000; + // coexistenceCenteringEnable flag has changed. + static final int COEXISTENCE_CENTERING_ENABLE_DIRTY = 0x2000; + // leftManualEyeInCoexistence has changed. + static final int LEFT_MANUAL_EYE_IN_COEXISTENCE_DIRTY = 0x4000; + // rightManualEyeInCoexistence has changed. + static final int RIGHT_MANUAL_EYE_IN_COEXISTENCE_DIRTY = 0x8000; + // visibilityPolicy has changed. + static final int VISIBILITY_POLICY_DIRTY = 0x10000; + + // This is not from View object. It is here for the purpose + // keeping all ViewCache's dirty mask bit declaration in one place. + // ViewPlatformRetained viewAttach Policy field has changed. + static final int VPR_VIEW_ATTACH_POLICY_DIRTY = 0x10000; + static final int VPR_VIEWPLATFORM_DIRTY = 0x20000; + + // PhysicalEnvironment fields has changed. + static final int PE_COE_TO_TRACKER_BASE_DIRTY = 0x100000; + static final int PE_TRACKING_AVAILABLE_DIRTY = 0x200000; + static final int PE_COE_CENTER_IN_PWORLD_POLICY_DIRTY = 0x400000; + + // PhysicalBody fields has changed. + static final int PB_EYE_POSITION_DIRTY = 0x1000000; + static final int PB_EAR_POSITION_DIRTY = 0x2000000; + static final int PB_NOMINAL_EYE_HEIGHT_FROM_GROUND_DIRTY = 0x4000000; + static final int PB_NOMINAL_EYE_OFFSET_FROM_NOMINAL_SCREEN_DIRTY = 0x8000000; + + + // Mask that indicates this View's view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + int vDirtyMask = (COMPATIBILITY_MODE_DIRTY | SCREEN_SCALE_POLICY_DIRTY + | SCREEN_SCALE_DIRTY | WINDOW_RESIZE_POLICY_DIRTY + | VIEW_POLICY_DIRTY | CLIP_DIRTY + | PROJECTION_POLICY_DIRTY | WINDOW_MOVEMENT_POLICY_DIRTY + | WINDOW_EYE_POINT_POLICY_DIRTY | MONOSCOPIC_VIEW_POLICY_DIRTY + | FIELD_OF_VIEW_DIRTY | TRACKING_ENABLE_DIRTY + | USER_HEAD_TO_VWORLD_ENABLE_DIRTY + | COEXISTENCE_CENTERING_ENABLE_DIRTY + | LEFT_MANUAL_EYE_IN_COEXISTENCE_DIRTY + | RIGHT_MANUAL_EYE_IN_COEXISTENCE_DIRTY + | VISIBILITY_POLICY_DIRTY); + + + // + // This object contains a specification of the user's physical body. + // + // Attributes of this object are defined in head coordinates and + // include information such as the location of the user's eyes and + // ears. + // The origin is defined to be halfway between the left and right eye + // in the plane of the face. + // The x-axis extends to the right (of the head looking out from the head). + // The y-axis extends up. The z-axis extends to the rear of the head. + // + PhysicalBody physicalBody; + + // This object contains a specification of the physical environment. + PhysicalEnvironment physicalEnvironment; + + // View model compatibility mode flag + boolean compatibilityModeEnable = false; + + // View model coexistenceCenteringEnable flag + boolean coexistenceCenteringEnable = true; + + Point3d leftManualEyeInCoexistence = new Point3d(); + Point3d rightManualEyeInCoexistence = new Point3d(); + + // + // Indicates which major mode of view computation to use: + // HMD mode or screen/fish-tank-VR mode. + // + int viewPolicy = SCREEN_VIEW; + + // The current projection policy (parallel versus perspective) + int projectionPolicy = PERSPECTIVE_PROJECTION; + + // + // The view model's field of view. + // + double fieldOfView = 45.0 * Math.PI / 180.0; + + // + // The distance away from the clip origin + // in the direction of gaze for the front and back clip planes. + // The default values are in meters. + // + double frontClipDistance = 0.1; + double backClipDistance = 10.0; + + // This variable specifies where the screen scale comes from + int screenScalePolicy = SCALE_SCREEN_SIZE; + + // The screen scale value used when the screen scale policy is + // SCALE_EXPLICIT + double screenScale = 1.0; + + // + // This variable specifies how Java 3D modifies the view when + // the window is resized (VIRTUAL_WORLD or PHYSICAL_WORLD). + // + int windowResizePolicy = PHYSICAL_WORLD; + + // + // This variable specifies how Java 3D modifies the view when + // the window is moved (VIRTUAL_WORLD or PHYSICAL_WORLD). + // + int windowMovementPolicy = PHYSICAL_WORLD; + + // + // Specifies how Java 3D handles the predefined eyepoint in + // non-head-tracked environment (RELATIVE_TO_SCREEN, + // RELATIVE_TO_WINDOW, RELATIVE_TO_FIELD_OF_VIEW, or + // RELATIVE_TO_COEXISTENCE) + // + int windowEyepointPolicy = RELATIVE_TO_FIELD_OF_VIEW; + + // + // Specifies how Java 3D generates monoscopic view + // (LEFT_EYE_VIEW, RIGHT_EYE_VIEW, or CYCLOPEAN_EYE_VIEW). + // + int monoscopicViewPolicy = CYCLOPEAN_EYE_VIEW; + + /** + * Defines the policy for placing the front clipping plane. + * Legal values include PHYSICAL_EYE, PHYSICAL_SCREEN, + * VIRTUAL_EYE, and VIRTUAL_SCREEN. + */ + int frontClipPolicy = PHYSICAL_EYE; + + /** + * Defines the policy for placing the back clipping plane. + */ + int backClipPolicy = PHYSICAL_EYE; + + /** + * Defines the visibility policy. + */ + int visibilityPolicy = VISIBILITY_DRAW_VISIBLE; + + /** + * Defines the transparency sorting policy. + */ + int transparencySortingPolicy = TRANSPARENCY_SORT_NONE; + + /** + * Flag to enable tracking, if so allowed by the trackingAvailable flag. + */ + boolean trackingEnable = false; + + /** + * This setting enables the continuous updating by Java 3D of the + * userHeadToVworld transform. + */ + boolean userHeadToVworldEnable = false; + + /** + * The view platform currently associated with this view. + */ + private ViewPlatform viewPlatform = null; + + // The current compatibility mode view transform + Transform3D compatVpcToEc = new Transform3D(); + + // The current compatibility mode projection transforms + Transform3D compatLeftProjection = new Transform3D(); + Transform3D compatRightProjection = new Transform3D(); + + // The long id of this view - used for dirty bit evaluation in the scene graph + Integer viewId = null; + int viewIndex = -1; + + // A boolean that indicates whether or not this is the primary view + boolean primaryView = false; + + // A boolean that indicates whether or not this view is active as + // seen by MasterControl + boolean active = false; + + // A boolean that indicates whether or not this view is active as + // seen by this view. There is a delay before MasterControl set + // active flag, so a local activeStatus is used. Otherwise + // activate event may lost if it proceed by deactivate event + // but MC not yet set active to false. This happens in + // viewplatform detach and attach. + boolean activeStatus = false; + + // This boolean indicates whether or not the view is running. It + // is used for startView/stopView + volatile boolean isRunning = true; + + // A flag to indicate that we are in a canvas callback routine + boolean inCanvasCallback = false; + + // + // Flag to enable depth buffer freeze during trasparent rendering pass + // + boolean depthBufferFreezeTransparent = true; + + // + // Flag to enable scene antialiasing + // + boolean sceneAntialiasingEnable = false; + + // + // Flag to enable local eye lighting + // + boolean localEyeLightingEnable = false; + + // Array Lists to track the screens and canvases associated with this View. + // use getScreens() to access this + private ArrayList screenList = new ArrayList(); + + // use getCanvasList() to access this + private ArrayList canvasList = new ArrayList(); + + private Canvas3D[][] cachedCanvasList; + private Canvas3D[] cachedCanvases; + private Canvas3D[] cachedOffScreenCanvases; + private Screen3D[] cachedScreens; + private int longestScreenList = 0; + private boolean canvasesDirty = true; + + // Flag to notify user thread when renderOnce is finished + volatile boolean renderOnceFinish = true; + + // Lock to synchronize start/stop/renderOnce call + private Object startStopViewLock = new Object(); + + // Lock for evaluateActive() only. This is used to prevent + // using lock this which will cause deadlock when MC call + // snapshot which waitForMC() in user thread. + private Object evaluateLock = new Object(); + + /** + * use for stop view, when stopview, set to count -1, + * when reach 1, call stopView() in MC and reset to -1. + */ + int stopViewCount = -1; + + /** + * False if current frame cycle time less than minimum frame cycle time + */ + boolean isMinCycleTimeAchieve = true; + + // Time to sleep if minimum frame cycle time not achieve + long sleepTime = 0; + + // use in pure immediate mode to tell whether this view rendering + // thread is added in MC renderThreadData + volatile boolean inRenderThreadData = false; + + // use to notify MC that render bin has run once, and is ready for + // renderer to render + boolean renderBinReady = false; + + // No of time setUniverse() is invoke + long universeCount = 0; + + // The universe count when UNREGISTER_VIEW request is post, + // this is used to distingish whether new setUniverse() is + // invoked after UNREGISTER_VIEW request post to avoid + // resetting the newly set universe. + long resetUnivCount = 0; + + // This notify user thread waitForMC() to continue when + // MC finish unregisterView + volatile boolean doneUnregister = false; + + static final int TRANSP_SORT_POLICY_CHANGED = 0x0001; + static final int OTHER_ATTRS_CHANGED = 0x0002; + + /** + * Constructs a View object with default parameters. The default + * values are as follows: + * <ul> + * view policy : SCREEN_VIEW<br> + * projection policy : PERSPECTIVE_PROJECTION<br> + * screen scale policy : SCALE_SCREEN_SIZE<br> + * window resize policy : PHYSICAL_WORLD<br> + * window movement policy : PHYSICAL_WORLD<br> + * window eyepoint policy : RELATIVE_TO_FIELD_OF_VIEW<br> + * monoscopic view policy : CYCLOPEAN_EYE_VIEW<br> + * front clip policy : PHYSICAL_EYE<br> + * back clip policy : PHYSICAL_EYE<br> + * visibility policy : VISIBILITY_DRAW_VISIBLE<br> + * transparency sorting policy : TRANSPARENCY_SORT_NONE<br> + * coexistenceCentering flag : true<br> + * compatibility mode : false<br> + * left projection : identity<br> + * right projection : identity<br> + * vpc to ec transform : identity<br> + * physical body : null<br> + * physical environment : null<br> + * screen scale : 1.0<br> + * field of view : PI/4<br> + * left manual eye in coexistence : (-0.033, 0.0, 0.4572)<br> + * right manual eye in coexistence : (0.033, 0.0, 0.4572)<br> + * front clip distance : 0.1<br> + * back clip distance : 10.0<br> + * tracking enable : false<br> + * user head to vworld enable : false<br> + * list of Canvas3D objects : empty<br> + * depth buffer freeze transparent : true<br> + * scene antialiasing : false<br> + * local eye lighting : false<br> + * view platform : null<br> + * behavior scheduler running : true<br> + * view running : true<br> + * minimum frame cycle time : 0<br> + * </ul> + */ + public View() { + viewCache = new ViewCache(this); + } + + /** + * Sets the policy for view computation. + * This variable specifies how Java 3D uses its transforms in + * computing new viewpoints. + * <UL> + * <LI>SCREEN_VIEW specifies that Java 3D should compute a new viewpoint + * using the sequence of transforms appropriate to screen-based + * head-tracked display environments (fish-tank VR/portals/VR-desks). + * </LI> + * <LI>HMD_VIEW specifies that Java 3D should compute a new viewpoint + * using the sequence of transforms appropriate to head mounted + * display environments. + * </LI> + * </UL> + * The default view policy is SCREEN_VIEW. + * @param policy the new policy, one of SCREEN_VIEW or HMD_VIEW + * @exception IllegalArgumentException if policy is a value other than + * SCREEN_VIEW or HMD_VIEW + * @exception IllegalStateException if the specified policy + * is HMD_VIEW and if any canvas associated with this view is + * a stereo canvas with a monoscopicEyePolicy of CYCLOPEAN_EYE_VIEW + */ + public void setViewPolicy(int policy) { + if (policy != HMD_VIEW && + policy != SCREEN_VIEW) { + + throw new IllegalArgumentException(J3dI18N.getString("View0")); + } + if(policy == HMD_VIEW) { + // Check the following : + // 1) If the view is in HMD mode and there exists a canvas in + // CYCLOPEAN_EYE_VIEW mode then throw exception. + synchronized (canvasList) { + for (int i=canvases.size()-1; i>=0; i--) { + Canvas3D c3d = (Canvas3D)canvases.elementAt(i); + + if ((c3d.monoscopicViewPolicy == View.CYCLOPEAN_EYE_VIEW) && + (!c3d.useStereo)){ + throw new + IllegalStateException(J3dI18N.getString("View31")); + } + } + } + } + synchronized(this) { + this.viewPolicy = policy; + vDirtyMask |= View.VIEW_POLICY_DIRTY; + } + repaint(); + } + + /** + * Retrieves the current view computation policy for this View. + * @return one of: SCREEN_VIEW or HMD_VIEW. + */ + public int getViewPolicy() { + return this.viewPolicy; + } + + /** + * Sets the projection policy for this View. + * This variable specifies the type of projection transform that + * will be generated. A value of PARALLEL_PROJECTION specifies that + * a parallel projection transform is generated. A value of + * PERSPECTIVE_PROJECTION specifies that + * a perspective projection transform is generated. + * The default projection policy is PERSPECTIVE. + * @param policy the new policy, one of PARALLEL_PROJECTION or + * PERSPECTIVE_PROJECTION + * @exception IllegalArgumentException if policy is a value other than + * PARALLEL_PROJECTION or PERSPECTIVE_PROJECTION + */ + public void setProjectionPolicy(int policy) { + if (policy != PERSPECTIVE_PROJECTION && + policy != PARALLEL_PROJECTION) { + + throw new IllegalArgumentException(J3dI18N.getString("View1")); + } + synchronized(this) { + this.projectionPolicy = policy; + vDirtyMask |= View.PROJECTION_POLICY_DIRTY; + } + + repaint(); + } + + /** + * Retrieves the current projection policy for this View. + * @return one of: PARALLEL_PROJECTION or PERSPECTIVE_PROJECTION. + */ + public int getProjectionPolicy() { + return this.projectionPolicy; + } + + /** + * Sets the screen scale policy for this view. + * This policy specifies how the screen scale is derived. + * The value is either SCALE_SCREEN_SIZE or SCALE_EXPLICIT. + * A value of SCALE_SCREEN_SIZE specifies that the scale is derived + * from the size of the physical screen. A value of SCALE_EXPLICIT + * specifies that the scale is taken directly from the screenScale + * parameter. + * The default screen scale policy is SCALE_SCREEN_SIZE. + * @param policy the new policy, one of SCALE_SCREEN_SIZE or + * SCALE_EXPLICIT. + */ + public void setScreenScalePolicy(int policy) { + + synchronized(this) { + this.screenScalePolicy = policy; + vDirtyMask |= View.SCREEN_SCALE_POLICY_DIRTY; + } + + repaint(); + } + + /** + * Returns the current screen scale policy, one of: + * SCALE_SCREEN_SIZE or SCALE_EXPLICIT. + * @return the current screen scale policy + */ + public int getScreenScalePolicy() { + return this.screenScalePolicy; + } + + /** + * Sets the window resize policy. + * This variable specifies how Java 3D modifies the view when + * users resize windows. The variable can contain one of + * VIRTUAL_WORLD or PHYSICAL_WORLD. + * A value of VIRTUAL_WORLD implies that the original image + * remains the same size on the screen but the user sees more + * or less of the virtual world depending on whether the window + * grew or shrank in size. + * A value of PHYSICAL_WORLD implies that the original image + * continues to fill the window in the same way using more or + * less pixels depending on whether the window grew or shrank + * in size. + * The default window resize policy is PHYSICAL_WORLD. + * @param policy the new policy, one of VIRTUAL_WORLD or PHYSICAL_WORLD + */ + public void setWindowResizePolicy(int policy) { + + synchronized(this) { + this.windowResizePolicy = policy; + vDirtyMask |= View.WINDOW_RESIZE_POLICY_DIRTY; + } + repaint(); + } + + /** + * Returns the current window resize policy, one of: + * VIRTUAL_WORLD or PHYSICAL_WORLD. + * @return the current window resize policy + */ + public int getWindowResizePolicy() { + return this.windowResizePolicy; + } + + /** + * Sets the window movement policy. + * This variable specifies what part of the virtual world Java 3D + * draws as a function of window placement on the screen. The + * variable can contain one of VIRTUAL_WORLD or PHYSICAL_WORLD. + * A value of VIRTUAL_WORLD implies that the image seen in the + * window changes as the position of the window shifts on the + * screen. (This mode acts as if the window were a window into + * the virtual world.) + * A value of PHYSICAL_WORLD implies that the image seen in the + * window remains the same no matter where the user positions + * the window on the screen. + * The default window movement policy is PHYSICAL_WORLD. + * @param policy the new policy, one of VIRTUAL_WORLD or PHYSICAL_WORLD + */ + public void setWindowMovementPolicy(int policy) { + + synchronized(this) { + this.windowMovementPolicy = policy; + vDirtyMask |= View.WINDOW_MOVEMENT_POLICY_DIRTY; + } + repaint(); + } + + /** + * Returns the current window movement policy, + * one of: VIRTUAL_WORLD or PHYSICAL_WORLD. + * @return the current window movement policy + */ + public int getWindowMovementPolicy() { + return this.windowMovementPolicy; + } + + /** + * Sets the view model's window eyepoint policy. + * This variable specifies how Java 3D handles the predefined eye + * point in a non-head-tracked environment. The variable can contain + * one of: + * <UL> + * <LI>RELATIVE_TO_SCREEN, Java 3D should interpret the + * given fixed-eyepoint position as relative to the screen (this + * implies that the view frustum shape will change whenever a + * user moves the location of a window on the screen). + * </LI> + * <LI>RELATIVE_TO_WINDOW, Java 3D should interpret the + * given fixed-eyepoint position as relative to the window. In this + * mode, the X and Y values are taken as the center of the window and + * the Z value is taken from the canvas eyepoint position. + * </LI> + * <LI>RELATIVE_TO_FIELD_OF_VIEW, Java 3D should + * modify the position of the eyepoint to match any changes in field + * of view (the view frustum will change whenever the application + * program changes the field of view). + * </LI> + * <LI>RELATIVE_TO_COEXISTENCE, Java 3D should interpret the eye's + * position in coexistence coordinates. In this mode, the eye position + * is taken from the view (rather than the Canvas3D) and transformed from + * coexistence coordinates to image plate coordinates for each + * Canvas3D. The resulting eye position is relative to the screen (this + * implies that the view frustum shape will change whenever a + * user moves the location of a window on the screen). + * </LI> + * </UL> + * The default window eyepoint policy is RELATIVE_TO_FIELD_OF_VIEW. + * @param policy the new policy, one of RELATIVE_TO_SCREEN, + * RELATIVE_TO_WINDOW, RELATIVE_TO_FIELD_OF_VIEW, or + * RELATIVE_TO_COEXISTENCE + */ + public void setWindowEyepointPolicy(int policy) { + synchronized(this) { + this.windowEyepointPolicy = policy; + vDirtyMask |= View.WINDOW_EYE_POINT_POLICY_DIRTY; + } + + repaint(); + } + + /** + * Returns the current window eyepoint policy, one of: + * RELATIVE_TO_SCREEN, RELATIVE_TO_WINDOW, RELATIVE_TO_FIELD_OF_VIEW or + * RELATIVE_TO_COEXISTENCE. + * @return the current window eyepoint policy + */ + public int getWindowEyepointPolicy() { + return this.windowEyepointPolicy; + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>Canvas3D.setMonoscopicViewPolicy</code> + */ + public void setMonoscopicViewPolicy(int policy) { + synchronized(this) { + this.monoscopicViewPolicy = policy; + vDirtyMask |= View.MONOSCOPIC_VIEW_POLICY_DIRTY; + } + repaint(); + } + + /** + * @deprecated As of Java 3D version 1.2, replaced by + * <code>Canvas3D.getMonoscopicViewPolicy</code> + */ + public int getMonoscopicViewPolicy() { + return this.monoscopicViewPolicy; + } + + /** + * Sets the coexistenceCentering enable flag to true or false. + * If the coexistenceCentering flag is true, the center of + * coexistence in image plate coordinates, as specified by the + * trackerBaseToImagePlate transform, is translated to the center + * of either the window or the screen in image plate coordinates, + * according to the value of windowMovementPolicy. + * + * <p> + * By default, coexistenceCentering is enabled. It should be + * disabled if the trackerBaseToImagePlate calibration transform + * is set to a value other than the identity (for example, when + * rendering to multiple screens or when head tracking is + * enabled). This flag is ignored for HMD mode, or when the + * coexistenceCenterInPworldPolicy is <i>not</i> + * NOMINAL_SCREEN. + * + * @param flag the new coexistenceCentering enable flag + * + * @since Java 3D 1.2 + */ + public void setCoexistenceCenteringEnable(boolean flag) { + synchronized(this) { + this.coexistenceCenteringEnable = flag; + vDirtyMask |= View.COEXISTENCE_CENTERING_ENABLE_DIRTY; + } + repaint(); + } + + /** + * Retrieves the coexistenceCentering enable flag. + * + * @return the current coexistenceCentering enable flag + * + * @since Java 3D 1.2 + */ + public boolean getCoexistenceCenteringEnable() { + return this.coexistenceCenteringEnable; + } + + /** + * Sets the compatibility mode enable flag to true or false. + * Compatibility mode is disabled by default. + * @param flag the new compatibility mode enable flag + */ + public void setCompatibilityModeEnable(boolean flag) { + synchronized(this) { + this.compatibilityModeEnable = flag; + vDirtyMask |= View.COMPATIBILITY_MODE_DIRTY; + } + repaint(); + } + + /** + * Retrieves the compatibility mode enable flag. + * @return the current compatibility mode enable flag + */ + public boolean getCompatibilityModeEnable() { + return this.compatibilityModeEnable; + } + + /** + * Compatibility mode method that specifies a viewing frustum for + * the left eye that transforms points in Eye Coordinates (EC) to + * Clipping Coordinates (CC). + * If compatibility mode is disabled, then this transform is not used; + * the actual projection is derived from other values. + * In monoscopic mode, only the left eye projection matrix is used. + * @param projection the new left eye projection transform + * @exception RestrictedAccessException if compatibility mode is disabled. + */ + public void setLeftProjection(Transform3D projection) { + if (!compatibilityModeEnable) { + throw new RestrictedAccessException(J3dI18N.getString("View2")); + } + + synchronized(this) { + compatLeftProjection.setWithLock(projection); + vDirtyMask |= View.COMPATIBILITY_MODE_DIRTY; + } + repaint(); + } + + /** + * Compatibility mode method that specifies a viewing frustum for + * the right eye that transforms points in Eye Coordinates (EC) to + * Clipping Coordinates (CC). + * If compatibility mode is disabled, then this transform is not used; + * the actual projection is derived from other values. + * In monoscopic mode, the right eye projection matrix is ignored. + * @param projection the new right eye projection transform + * @exception RestrictedAccessException if compatibility mode is disabled. + */ + public void setRightProjection(Transform3D projection) { + if (!compatibilityModeEnable) { + throw new RestrictedAccessException(J3dI18N.getString("View2")); + } + + synchronized(this) { + compatRightProjection.setWithLock(projection); + vDirtyMask |= View.COMPATIBILITY_MODE_DIRTY; + } + + repaint(); + } + + /** + * Compatibility mode method that retrieves the current + * compatibility mode projection transform for the left eye and + * places it into the specified object. + * @param projection the Transform3D object that will receive the + * projection + * @exception RestrictedAccessException if compatibility mode is disabled. + */ + public void getLeftProjection(Transform3D projection) { + if (!compatibilityModeEnable) { + throw new RestrictedAccessException(J3dI18N.getString("View4")); + } + + projection.set(compatLeftProjection); + } + + /** + * Compatibility mode method that retrieves the current + * compatibility mode projection transform for the right eye and + * places it into the specified object. + * @param projection the Transform3D object that will receive the + * projection + * @exception RestrictedAccessException if compatibility mode is disabled. + */ + public void getRightProjection(Transform3D projection) { + if (!compatibilityModeEnable) { + throw new RestrictedAccessException(J3dI18N.getString("View4")); + } + + projection.set(compatRightProjection); + } + + /** + * Compatibility mode method that specifies the ViewPlatform + * Coordinates (VPC) to Eye Coordinates (EC) transform. + * If compatibility mode is disabled, then this transform + * is derived from other values and is read-only. + * @param vpcToEc the new VPC to EC transform + * @exception RestrictedAccessException if compatibility mode is disabled. + * @exception BadTransformException if the transform is not affine. + */ + public void setVpcToEc(Transform3D vpcToEc) { + if (!compatibilityModeEnable) { + throw new RestrictedAccessException(J3dI18N.getString("View6")); + } + + if (!vpcToEc.isAffine()) { + throw new BadTransformException(J3dI18N.getString("View7")); + } + + synchronized(this) { + compatVpcToEc.setWithLock(vpcToEc); + vDirtyMask |= View.COMPATIBILITY_MODE_DIRTY; + } + + repaint(); + } + + /** + * Compatibility mode method that retrieves the current + * ViewPlatform Coordinates (VPC) system to + * Eye Coordinates (EC) transform and copies it into the specified + * object. + * @param vpcToEc the object that will receive the vpcToEc transform. + * @exception RestrictedAccessException if compatibility mode is disabled. + */ + public void getVpcToEc(Transform3D vpcToEc) { + if (!compatibilityModeEnable) { + throw new RestrictedAccessException(J3dI18N.getString("View8")); + } + + vpcToEc.set(compatVpcToEc); + } + + /** + * Sets the view model's physical body to the PhysicalBody object provided. + * Java 3D uses the parameters in the PhysicalBody to ensure accurate + * image and sound generation when in head-tracked mode. + * @param physicalBody the new PhysicalBody object + */ + public void setPhysicalBody(PhysicalBody physicalBody) { + // need to synchronize variable activateStatus + synchronized (canvasList) { + if (activeStatus) { + if (this.physicalBody != null) { + this.physicalBody.removeUser(this); + } + physicalBody.addUser(this); + } + } + this.physicalBody = physicalBody; + repaint(); + } + + /** + * Returns a reference to the view model's PhysicalBody object. + * @return the view object's PhysicalBody object + */ + public PhysicalBody getPhysicalBody() { + return this.physicalBody; + } + + /** + * Sets the view model's physical environment to the PhysicalEnvironment + * object provided. + * @param physicalEnvironment the new PhysicalEnvironment object + */ + public void setPhysicalEnvironment(PhysicalEnvironment physicalEnvironment) { + synchronized (canvasList) { + if (activeStatus) { + if (this.physicalEnvironment != null) { + this.physicalEnvironment.removeUser(this); + } + physicalEnvironment.addUser(this); + } + } + this.physicalEnvironment = physicalEnvironment; + + + if ((viewPlatform != null) && viewPlatform.isLive()) { + VirtualUniverse.mc.postRequest(MasterControl.PHYSICAL_ENV_CHANGE, this); + } + repaint(); + } + + /** + * Returns a reference to the view model's PhysicalEnvironment object. + * @return the view object's PhysicalEnvironment object + */ + public PhysicalEnvironment getPhysicalEnvironment() { + return this.physicalEnvironment; + } + + /** + * Sets the screen scale value for this view. + * This is used when the screen scale policy is SCALE_EXPLICIT. + * The default value is 1.0 (i.e., unscaled). + * @param scale the new screen scale + */ + public void setScreenScale(double scale) { + synchronized(this) { + this.screenScale = scale; + vDirtyMask |= View.SCREEN_SCALE_DIRTY; + } + repaint(); + } + + /** + * Returns the current screen scale value + * @return the current screen scale value + */ + public double getScreenScale() { + return this.screenScale; + } + + /** + * Sets the field of view used to compute the projection transform. + * This is used when head tracking is disabled and when the Canvas3D's + * windowEyepointPolicy is RELATIVE_TO_FIELD_OF_VIEW. + * @param fieldOfView the new field of view in radians + */ + public void setFieldOfView(double fieldOfView) { + synchronized(this) { + this.fieldOfView = fieldOfView; + vDirtyMask |= View.FIELD_OF_VIEW_DIRTY; + } + repaint(); + + } + + /** + * Returns the current field of view. + * @return the current field of view in radians + */ + public double getFieldOfView() { + return this.fieldOfView; + } + + + /** + * Sets the position of the manual left eye in coexistence + * coordinates. This value determines eye placement when a head + * tracker is not in use and the application is directly controlling + * the eye position in coexistence coordinates. This value is + * ignored when in head-tracked mode or when the + * windowEyePointPolicy is <i>not</i> RELATIVE_TO_COEXISTENCE. + * + * @param position the new manual left eye position + * + * @since Java 3D 1.2 + */ + public void setLeftManualEyeInCoexistence(Point3d position) { + synchronized(this) { + leftManualEyeInCoexistence.set(position); + vDirtyMask |= View.LEFT_MANUAL_EYE_IN_COEXISTENCE_DIRTY; + } + repaint(); + } + + /** + * Sets the position of the manual right eye in coexistence + * coordinates. This value determines eye placement when a head + * tracker is not in use and the application is directly controlling + * the eye position in coexistence coordinates. This value is + * ignored when in head-tracked mode or when the + * windowEyePointPolicy is <i>not</i> RELATIVE_TO_COEXISTENCE. + * + * @param position the new manual right eye position + * + * @since Java 3D 1.2 + */ + public void setRightManualEyeInCoexistence(Point3d position) { + synchronized(this) { + rightManualEyeInCoexistence.set(position); + vDirtyMask |= View.RIGHT_MANUAL_EYE_IN_COEXISTENCE_DIRTY; + } + repaint(); + } + + /** + * Retrieves the position of the user-specified, manual left eye + * in coexistence + * coordinates and copies that value into the object provided. + * @param position the object that will receive the position + * + * @since Java 3D 1.2 + */ + public void getLeftManualEyeInCoexistence(Point3d position) { + position.set(leftManualEyeInCoexistence); + } + + /** + * Retrieves the position of the user-specified, manual right eye + * in coexistence + * coordinates and copies that value into the object provided. + * @param position the object that will receive the position + * + * @since Java 3D 1.2 + */ + public void getRightManualEyeInCoexistence(Point3d position) { + position.set(rightManualEyeInCoexistence); + } + + + /** + * Sets the view model's front clip distance. + * This value specifies the distance away from the eyepoint + * in the direction of gaze where objects stop disappearing. + * Objects closer to the eye than the front clip + * distance are not drawn. The default value is 0.1 meters. + * <p> + * There are several considerations that need to be taken into + * account when choosing values for the front and back clip + * distances. + * <ul> + * <li>The front clip distance must be greater than + * 0.0 in physical eye coordinates.</li> + * <li>The front clipping plane must be in front of the + * back clipping plane, that is, the front clip distance + * must be less than the back clip distance in physical eye + * coordinates.</li> + * <li>The front and back clip distances, in physical + * eye coordinates, must be less than the largest positive + * single-precision floating point value, <code>Float.MAX_VALUE</code>. + * In practice, since these physical eye coordinate distances are in + * meters, the values should be <i>much</i> less than that. + * <li>The ratio of the back distance divided by the front distance, + * in physical eye coordinates, affects Z-buffer precision. This + * ratio should be less than about 3000 in order to accommodate 16-bit + * Z-buffers. Values of 100 to less than 1000 will produce better + * results.</li> + * </ul> + * Violating any of the above rules will result in undefined + * behavior. In many cases, no picture will be drawn. + * + * @param distance the new front clip distance + * @see #setBackClipDistance + */ + public void setFrontClipDistance(double distance) { + synchronized(this) { + this.frontClipDistance = distance; + vDirtyMask |= View.CLIP_DIRTY; + } + repaint(); + } + + /** + * Returns the view model's front clip distance. + * @return the current front clip distance + */ + public double getFrontClipDistance() { + return this.frontClipDistance; + } + + /** + * Sets the view model's back clip distance. + * The parameter specifies the distance from the eyepoint + * in the direction of gaze to where objects begin disappearing. + * Objects farther away from the eye than the + * back clip distance are not drawn. + * The default value is 10.0 meters. + * <p> + * There are several considerations that need to be taken into + * account when choosing values for the front and back clip + * distances. These are enumerated in the description of + * <a href=#setFrontClipDistance(double)>setFrontClipDistance</a>. + * <p> + * Note that this attribute is only used if there is no Clip node + * that is in scope of the view platform associated with this view. + * @param distance the new back clip distance + * @see #setFrontClipDistance + * @see Clip#setBackDistance + */ + public void setBackClipDistance(double distance) { + synchronized(this) { + this.backClipDistance = distance; + vDirtyMask |= View.CLIP_DIRTY; + } + repaint(); + } + + /** + * Returns the view model's back clip distance. + * @return the current back clip distance + */ + public double getBackClipDistance() { + return this.backClipDistance; + } + + /** + * Retrieves the user-head to virtual-world transform + * and copies that value into the transform provided. + * @param t the Transform3D object that will receive the transform + */ + public void getUserHeadToVworld(Transform3D t) { + + if( userHeadToVworldEnable ) { + + // get the calculated userHeadToVworld transform + // from the view cache. + // grab the first canvas -- not sure for multiple canvases + Canvas3D canvas = (Canvas3D) this.canvases.firstElement(); + synchronized(canvas.canvasViewCache) { + t.set(canvas.canvasViewCache.getHeadToVworld()); + } + }else { + throw new RestrictedAccessException(J3dI18N.getString("View9")); + } + } + + /** + * Sets the view model's front clip policy, the policy Java 3D uses + * in computing where to place the front clip plane. The variable + * can contain one of: + * <UL> + * <LI>VIRTUAL_EYE, to specify that the associated distance is + * from the eye and in units of virtual distance + * </LI> + * <LI>PHYSICAL_EYE, to specify that the associated distance is + * from the eye and in units of physical distance (meters) + * </LI> + * <LI>VIRTUAL_SCREEN, to specify that the associated distance is + * from the screen and in units of virtual distance + * </LI> + * <LI>PHYSICAL_SCREEN, to specify that the associated distance is + * from the screen and in units of physical distance (meters) + * </LI> + * </UL> + * The default front clip policy is PHYSICAL_EYE. + * @param policy the new policy, one of PHYSICAL_EYE, PHYSICAL_SCREEN, + * VIRTUAL_EYE, or VIRTUAL_SCREEN + */ + public void setFrontClipPolicy(int policy) { + synchronized(this) { + this.frontClipPolicy = policy; + vDirtyMask |= View.CLIP_DIRTY; + } + repaint(); + } + + /** + * Returns the view model's current front clip policy. + * @return one of: + * VIRTUAL_EYE, PHYSICAL_EYE, VIRTUAL_SCREEN, or PHYSICAL_SCREEN + */ + public int getFrontClipPolicy() { + return this.frontClipPolicy; + } + + /** + * Sets the view model's back clip policy, the policy Java 3D uses + * in computing where to place the back clip plane. The variable + * can contain one of: + * <UL> + * <LI>VIRTUAL_EYE, to specify that the associated distance is + * from the eye and in units of virtual distance + * </LI> + * <LI>PHYSICAL_EYE, to specify that the associated distance is + * from the eye and in units of physical distance (meters) + * </LI> + * <LI>VIRTUAL_SCREEN, to specify that the associated distance is + * from the screen and in units of virtual distance + * </LI> + * <LI>PHYSICAL_SCREEN, to specify that the associated distance is + * from the screen and in units of physical distance (meters) + * </LI> + * </UL> + * The default back clip policy is PHYSICAL_EYE. + * @param policy the new policy, one of PHYSICAL_EYE, PHYSICAL_SCREEN, + * VIRTUAL_EYE, or VIRTUAL_SCREEN + */ + public void setBackClipPolicy(int policy) { + synchronized(this) { + this.backClipPolicy = policy; + vDirtyMask |= View.CLIP_DIRTY; + } + repaint(); + } + + /** + * Returns the view model's current back clip policy. + * @return one of: + * VIRTUAL_EYE, PHYSICAL_EYE, VIRTUAL_SCREEN, or PHYSICAL_SCREEN + */ + public int getBackClipPolicy() { + return this.backClipPolicy; + } + + /** + * Sets the visibility policy for this view. This attribute + * is one of: + * <UL> + * <LI>VISIBILITY_DRAW_VISIBLE, to specify that only visible objects + * are drawn. + * </LI> + * <LI>VISIBILITY_DRAW_INVISIBLE, to specify that only invisible objects + * are drawn. + * </LI> + * <LI>VISIBILITY_DRAW_ALL, to specify that both visible and + * invisible objects are drawn. + * </LI> + * </UL> + * The default visibility policy is VISIBILITY_DRAW_VISIBLE. + * + * @param policy the new policy, one of VISIBILITY_DRAW_VISIBLE, + * VISIBILITY_DRAW_INVISIBLE, or VISIBILITY_DRAW_ALL. + * + * @see RenderingAttributes#setVisible + * + * @since Java 3D 1.2 + */ + public void setVisibilityPolicy(int policy) { + + synchronized(this) { + this.visibilityPolicy = policy; + vDirtyMask |= View.VISIBILITY_POLICY_DIRTY; + } + + if (activeStatus && isRunning) { + + J3dMessage vpMessage = VirtualUniverse.mc.getMessage(); + vpMessage.universe = universe; + vpMessage.view = this; + vpMessage.type = J3dMessage.UPDATE_VIEW; + vpMessage.threads = J3dThread.UPDATE_RENDER; + vpMessage.args[0] = this; + synchronized(((ViewPlatformRetained)viewPlatform.retained).sphere) { + vpMessage.args[1] = new Float(((ViewPlatformRetained)viewPlatform. + retained).sphere.radius); + } + vpMessage.args[2] = new Integer(OTHER_ATTRS_CHANGED); + vpMessage.args[3] = new Integer(transparencySortingPolicy); + VirtualUniverse.mc.processMessage(vpMessage); + } + } + + /** + * Retrieves the current visibility policy. + * @return one of: + * VISIBILITY_DRAW_VISIBLE, + * VISIBILITY_DRAW_INVISIBLE, or VISIBILITY_DRAW_ALL. + * + * @since Java 3D 1.2 + */ + public int getVisibilityPolicy() { + return this.visibilityPolicy; + } + + /** + * Sets the transparency sorting policy for this view. This attribute + * is one of: + * + * <UL> + * <LI>TRANSPARENCY_SORT_NONE, to specify that no depth sorting of + * transparent objects is performed. Transparent objects are + * drawn after opaque objects, but are not sorted from back to + * front.</LI> + * + * <LI>TRANSPARENCY_SORT_GEOMETRY, to specify that transparent + * objects are depth-sorted on a per-geometry basis. Each + * geometry object of each transparent Shape3D node is drawn from + * back to front. Note that this policy will not split geometry + * into smaller pieces, so intersecting or intertwined objects may + * not be sorted correctly.</LI> + * </UL> + * + * The default policy is TRANSPARENCY_SORT_NONE. + * + * @param policy the new policy, one of TRANSPARENCY_SORT_NONE + * or TRANSPARENCY_SORT_GEOMETRY. + * + * @since Java 3D 1.3 + */ + public void setTransparencySortingPolicy(int policy) { + if (policy == transparencySortingPolicy) { + return; + } + + transparencySortingPolicy = policy; + if (activeStatus && isRunning) { + + J3dMessage vpMessage = VirtualUniverse.mc.getMessage(); + vpMessage.universe = universe; + vpMessage.view = this; + vpMessage.type = J3dMessage.UPDATE_VIEW; + vpMessage.threads = J3dThread.UPDATE_RENDER; + vpMessage.args[0] = this; + vpMessage.args[1] = null; + vpMessage.args[2] = new Integer(TRANSP_SORT_POLICY_CHANGED); + vpMessage.args[3] = new Integer(policy); + VirtualUniverse.mc.processMessage(vpMessage); + } + } + + /** + * Retrieves the current transparency sorting policy. + * @return one of: + * TRANSPARENCY_SORT_NONE or TRANSPARENCY_SORT_GEOMETRY. + * + * @since Java 3D 1.3 + */ + public int getTransparencySortingPolicy() { + return this.transparencySortingPolicy; + } + + /** + * Turns head tracking on or off for this view. + * @param flag specifies whether head tracking is enabled or + * disabled for this view + */ + public void setTrackingEnable(boolean flag) { + + synchronized(this) { + this.trackingEnable = flag; + vDirtyMask |= View.TRACKING_ENABLE_DIRTY; + } + + repaint(); + } + + /** + * Returns a status flag indicating whether or not head tracking + * is enabled. + * @return a flag telling whether head tracking is enabled + */ + public boolean getTrackingEnable() { + return this.trackingEnable; + } + + /** + * Turns on or off the continuous + * updating of the userHeadToVworld transform. + * @param flag enables or disables continuous updating + */ + public void setUserHeadToVworldEnable(boolean flag) { + + synchronized(this) { + userHeadToVworldEnable = flag; + vDirtyMask |= View.USER_HEAD_TO_VWORLD_ENABLE_DIRTY; + } + repaint(); + } + + /** + * Returns a status flag indicating whether or not + * Java 3D is continuously updating the userHeadToVworldEnable transform. + * @return a flag indicating if continuously updating userHeadToVworld + */ + public boolean getUserHeadToVworldEnable() { + return userHeadToVworldEnable; + } + + /** + * Computes the sensor to virtual-world transform + * and copies that value into the transform provided. + * The computed transforms takes points in the sensor's coordinate + * system and produces the point's corresponding value in + * virtual-world coordinates. + * @param sensor the sensor in question + * @param t the object that will receive the transform + */ + public void getSensorToVworld(Sensor sensor, Transform3D t) { + // grab the first canvas -- not sure for multiple canvases + Canvas3D canvas = (Canvas3D) this.canvases.firstElement(); + Transform3D localTrans = new Transform3D(); + synchronized(canvas.canvasViewCache) { + t.set(canvas.canvasViewCache.getVworldToTrackerBase()); + } + t.invert(); + sensor.getRead(localTrans); + t.mul(localTrans); + } + + /** + * Retrieves the position of the specified Sensor's + * hotspot in virtual-world coordinates + * and copies that value into the position provided. + * This value is derived from other values and is read-only. + * @param sensor the sensor in question + * @param position the variable that will receive the position + */ + public void getSensorHotspotInVworld(Sensor sensor, + Point3f position) { + + Canvas3D canvas = (Canvas3D) this.canvases.firstElement(); + Transform3D sensorToVworld = new Transform3D(); + Point3d hotspot3d = new Point3d(); + + getSensorToVworld(sensor, sensorToVworld); + sensor.getHotspot(hotspot3d); + position.set(hotspot3d); + sensorToVworld.transform(position); + } + + /** + * Retrieves the position of the specified Sensor's + * hotspot in virtual-world coordinates + * and copies that value into the position provided. + * This value is derived from other values and is read-only. + * @param sensor the sensor in question + * @param position the variable that will receive the position + */ + public void getSensorHotspotInVworld(Sensor sensor, + Point3d position) { + + Canvas3D canvas = (Canvas3D) this.canvases.firstElement(); + Transform3D sensorToVworld = new Transform3D(); + + getSensorToVworld(sensor, sensorToVworld); + sensor.getHotspot(position); + sensorToVworld.transform(position); + } + + /** + * Sets given Canvas3D at the given index position. + * @param canvas3D the given Canvas3D to be set + * @param index the position to be set + * @exception IllegalStateException if the specified canvas is + * a stereo canvas with a monoscopicEyePolicy of CYCLOPEAN_EYE_VIEW, + * and the viewPolicy for this view is HMD_VIEW + * @exception IllegalSharingException if the specified canvas is + * associated with another view + */ + public void setCanvas3D(Canvas3D canvas3D, int index) { + + if((viewPolicy == HMD_VIEW) && + (canvas3D.monoscopicViewPolicy == View.CYCLOPEAN_EYE_VIEW) && + (!canvas3D.useStereo)){ + + throw new + IllegalStateException(J3dI18N.getString("View31")); + } + + Canvas3D cv; + + synchronized(canvasList) { + if (canvas3D.getView() != null) + throw new IllegalSharingException(J3dI18N.getString("View10")); + cv = (Canvas3D) canvases.elementAt(index); + canvases.setElementAt(canvas3D, index); + removeFromCanvasList(cv); + addToCanvasList(canvas3D); + canvasesDirty = true; + } + + canvas3D.setView(this); + cv.setView(null); + + if (canvas3D.added) { + evaluateActive(); + } + if (cv.added) { + evaluateActive(); + } + + } + + /** + * Gets the Canvas3D at the specified index position. + * @param index the position from which to get Canvas3D object + * @return the Canvas3D at the sprcified index position + */ + public Canvas3D getCanvas3D(int index){ + return (Canvas3D) this.canvases.elementAt(index); + } + + /** + * Gets the enumeration object of all the Canvas3Ds. + * @return the enumeration object of all the Canvas3Ds. + */ + public Enumeration getAllCanvas3Ds(){ + return canvases.elements(); + } + + /** + * Returns the number of Canvas3Ds in this View. + * @return the number of Canvas3Ds in this View + * + * @since Java 3D 1.2 + */ + public int numCanvas3Ds() { + return canvases.size(); + } + + /** + * Adds the given Canvas3D at the end of the list. + * @param canvas3D the Canvas3D to be added + * @exception IllegalStateException if the specified canvas is + * a stereo canvas with a monoscopicEyePolicy of CYCLOPEAN_EYE_VIEW, + * and the viewPolicy for this view is HMD_VIEW + * @exception IllegalSharingException if the specified canvas is + * associated with another view + */ + public void addCanvas3D(Canvas3D canvas3D){ + + if((viewPolicy == HMD_VIEW) && + (canvas3D.monoscopicViewPolicy == View.CYCLOPEAN_EYE_VIEW) && + (!canvas3D.useStereo)) { + throw new + IllegalStateException(J3dI18N.getString("View31")); + } + + synchronized(canvasList) { + if (canvas3D.getView() != null) + throw new IllegalSharingException(J3dI18N.getString("View10")); + canvases.addElement(canvas3D); + addToCanvasList(canvas3D); + canvasesDirty = true; + } + + canvas3D.setView(this); + + if (canvas3D.added) { + if ((canvas3D.visible || canvas3D.offScreen) && + canvas3D.firstPaintCalled) { + canvas3D.active = true; + } + evaluateActive(); + } + } + + /** + * Inserts the Canvas3D at the given index position. + * @param canvas3D the Canvas3D to be inserted + * @param index the position to be inserted at + * @exception IllegalStateException if the specified canvas is + * a stereo canvas with a monoscopicEyePolicy of CYCLOPEAN_EYE_VIEW, + * and the viewPolicy for this view is HMD_VIEW + * @exception IllegalSharingException if the specified canvas is + * associated with another view + */ + public void insertCanvas3D(Canvas3D canvas3D, int index){ + + if((viewPolicy == HMD_VIEW) && + (canvas3D.monoscopicViewPolicy == View.CYCLOPEAN_EYE_VIEW) && + (!canvas3D.useStereo)) { + throw new + IllegalStateException(J3dI18N.getString("View31")); + } + + synchronized(canvasList) { + if (canvas3D.getView() != null) + throw new IllegalSharingException(J3dI18N.getString("View10")); + this.canvases.insertElementAt(canvas3D, index); + addToCanvasList(canvas3D); + canvasesDirty = true; + } + + canvas3D.setView(this); + + if (canvas3D.added) { + if ((canvas3D.visible || canvas3D.offScreen) && + canvas3D.firstPaintCalled) { + canvas3D.active = true; + } + evaluateActive(); + } + } + + /** + * Removes the Canvas3D from the given index position. + * @param index the position of Canvas3D object to be removed + */ + public void removeCanvas3D(int index) { + // index -1 is possible if the view is unregistered first + // because viewPlatform is clearLived, + // and then removeCanvas from the view + if (index == -1) + return; + + Canvas3D cv; + + synchronized(canvasList) { + cv = (Canvas3D) canvases.elementAt(index); + + canvases.removeElementAt(index); + removeFromCanvasList(cv); + canvasesDirty = true; + } + + // reset canvas will set view to null also + VirtualUniverse.mc.postRequest(MasterControl.RESET_CANVAS, + cv); + cv.pendingView = null; + + computeCanvasesCached(); + + if (cv.added) { + cv.active = false; + evaluateActive(); + } + if (universe != null) { + universe.waitForMC(); + } + } + + + /** + * Retrieves the index of the specified Canvas3D in + * this View's list of Canvas3Ds + * + * @param canvas3D the Canvas3D to be looked up. + * @return the index of the specified Canvas3D; + * returns -1 if the object is not in the list. + * + * @since Java 3D 1.3 + */ + public int indexOfCanvas3D(Canvas3D canvas3D) { + return canvases.indexOf(canvas3D); + } + + + /** + * Removes the specified Canvas3D from this View's + * list of Canvas3Ds. + * If the specified object is not in the list, the list is not modified. + * + * @param canvas3D the Canvas3D to be removed. + */ + public void removeCanvas3D(Canvas3D canvas3D) { + removeCanvas3D(canvases.indexOf(canvas3D)); + } + + + /** + * Removes all Canvas3Ds from this View. + * + * @since Java 3D 1.3 + */ + public void removeAllCanvas3Ds() { + synchronized(canvasList) { + int numCanvases = canvases.size(); + + // Remove in reverse order to ensure valid indices + for (int index = numCanvases - 1; index >= 0; index--) { + Canvas3D cv; + + cv = (Canvas3D) canvases.elementAt(index); + + canvases.removeElementAt(index); + removeFromCanvasList(cv); + canvasesDirty = true; + + // reset canvas will set view to null also + VirtualUniverse.mc.postRequest(MasterControl.RESET_CANVAS, + cv); + cv.pendingView = null; + + if (cv.added) { + cv.active = false; + } + } + + computeCanvasesCached(); + } + + evaluateActive(); + + if (universe != null) { + universe.waitForMC(); + } + } + + + // This adds this canvas and its screen to the screen list. + // Locks are already acquired before this is called. + private void addToCanvasList(Canvas3D c) { + + for (int i=screenList.size()-1; i>=0; i--) { + if ((Screen3D)screenList.get(i) == c.screen) { + // This is the right screen slot + ((ArrayList)canvasList.get(i)).add(c); + canvasesDirty = true; + return; + } + } + + // Add a screen slot + screenList.add(c.screen); + ArrayList clist = new ArrayList(); + canvasList.add(clist); + clist.add(c); + canvasesDirty = true; + } + + // This removes this canvas and its screen from the screen list + // Locks are already acquired before this is called. + private void removeFromCanvasList(Canvas3D c) { + + for (int i=screenList.size()-1; i>=0; i--) { + if ((Screen3D) screenList.get(i) == c.screen) { + // This is the right screen slot + ArrayList clist = (ArrayList)canvasList.get(i); + clist.remove(clist.indexOf(c)); + + if (clist.size() == 0) { + canvasList.remove(i); + screenList.remove(i); + canvasesDirty = true; + } + return; + } + } + } + + // Locks are already acquired before this is called. + void computeCanvasesCached() { + + synchronized (canvasList) { + ArrayList cv; + int len = canvases.size(); + int numOffScreenCanvases = 0; + + Canvas3D newCachedCanvases[] = new Canvas3D[len]; + for (int i=0; i < len; i++) { + newCachedCanvases[i] = (Canvas3D) canvases.get(i); + if (newCachedCanvases[i].offScreen) + numOffScreenCanvases++; + } + // Do this in one instruction so there is no need to + // synchronized getCanvases() + + if (numOffScreenCanvases > 0) { + cachedOffScreenCanvases = new Canvas3D[numOffScreenCanvases]; + numOffScreenCanvases = 0; + } + + cachedCanvases = newCachedCanvases; + len = 0; + longestScreenList = 0; + cachedCanvasList = new Canvas3D[canvasList.size()][0]; + for (int i=0; i < cachedCanvasList.length; i++) { + cv = (ArrayList) canvasList.get(i); + len = cv.size(); + cachedCanvasList[i] = new Canvas3D[len]; + for (int j=0; j < len; j++) { + cachedCanvasList[i][j] = (Canvas3D) cv.get(j); + } + + if (cachedCanvasList[i][0].offScreen) { + for (int j = 0; j < len; j++) { + cachedOffScreenCanvases[numOffScreenCanvases++]= + cachedCanvasList[i][j]; + } + } + + if (len > longestScreenList) { + longestScreenList = len; + } + } + len = screenList.size(); + Screen3D newCachedScreens[] = new Screen3D[len]; + + for (int i=0; i < len; i++) { + newCachedScreens[i] = (Screen3D) screenList.get(i); + } + // Do this in one instruction so there is no need to + // synchronized getScreens() + cachedScreens = newCachedScreens; + canvasesDirty = false; + } + } + + // This creates a 2 dimentional list of canvases + // ONLY MC can call this procedure with canCompute=true, + // since MC want the result return by + // evaluateCanvases and updateWorkThreads agree to each other, + // so only evaluateCanvases can compute a new list. + // Other threads should use getCanvasList(false). + Canvas3D[][] getCanvasList(boolean canCompute) { + if (canvasesDirty && canCompute) { + computeCanvasesCached(); + } + return cachedCanvasList; + } + + // assume getCanvasList is called before + int getLongestScreenList() { + return longestScreenList; + } + + // assume getCanvasList is called before + Canvas3D[] getCanvases() { + return cachedCanvases; + } + + Canvas3D[] getOffScreenCanvases() { + return cachedOffScreenCanvases; + } + + // assume getCanvasList is called before + Screen3D[] getScreens() { + return cachedScreens; + } + + Canvas3D getFirstCanvas() { + synchronized (canvasList) { + if (canvases.size() > 0) { + return (Canvas3D) canvases.elementAt(0); + } + return null; + } + } + + /** + * This method returns the time at which the most recent rendering + * frame started. It is defined as the number of milliseconds + * since January 1, 1970 00:00:00 GMT. + * Since multiple canvases might be attached to this View, + * the start of a frame is defined as the point in time just prior + * to clearing any canvas attached to this view. + * @return the time at which the most recent rendering frame started + */ + public long getCurrentFrameStartTime() { + synchronized (frameStartTimes) { + return currentFrameStartTime; + } + } + + /** + * This method returns the duration, in milliseconds, of the most + * recently completed rendering frame. The time taken to render + * all canvases attached to this view is measured. This duration + * is computed as the difference between the start of the most recently + * completed frame and the end of that frame. + * Since multiple canvases might be attached to this View, + * the start of a frame is defined as the point in time just prior + * to clearing any canvas attached to this view--before preRender + * is called for any canvas. Similarly, the end of a frame is + * defined as the point in time just after swapping the buffer for + * all canvases--after postSwap is called for all canvases. + * Note that since the frame duration is measured from start to stop + * for this view only, the value returned is not the same as + * frame rate; it measures only the rendering time for this view. + * + * @return the duration, in milliseconds, of the most recently + * completed rendering frame + */ + public long getLastFrameDuration() { + synchronized (frameStartTimes) { + return currentFrameDuration; + } + } + + /** + * This method returns the frame number for this view. The frame + * number starts at 0 and is incremented at the start of each + * frame--prior to clearing all the canvases attached to this + * view. + * + * @return the current frame number for this view + */ + public long getFrameNumber() { + synchronized (frameStartTimes) { + return currentFrameNumber; + } + } + + /** + * Retrieves the implementation-dependent maximum number of + * frames whose start times will be recorded by the system. This + * value is guaranteed to be at least 10 for all implementations + * of the Java 3D API. + * @return the maximum number of frame start times recorded + */ + public static int getMaxFrameStartTimes() { + return (NUMBER_FRAME_START_TIMES); + } + + /** + * Copies the last <i>k</i> frame start time values into + * the user-specified array. The most recent frame start time is + * copied to location 0 of the array, the next most recent frame + * start time is copied into location 1 of the array, and so forth. + * If times.length is smaller than + * maxFrameStartTimes, then only the last times.length values are + * copied. If times.length is greater than maxFrameStartTimes, + * then all array elements after index maxFrameStartTimes-1 are + * set to 0. + * + * @return the frame number of the most recent frame in the array + * + * @see #setMinimumFrameCycleTime + */ + public long getFrameStartTimes(long[] times) { + int index, i, loopCount; + long lastFrameNumber; + + synchronized (frameStartTimes) { + index = currentFrameIndex - 1; + if (index < 0) { + index = NUMBER_FRAME_START_TIMES - 1; + } + lastFrameNumber = frameNumbers[index]; + + if (times.length <= NUMBER_FRAME_START_TIMES) { + loopCount = times.length; + } else { + loopCount = NUMBER_FRAME_START_TIMES; + } + + for (i=0; i<loopCount; i++) { + times[i] = frameStartTimes[index]; + index--; + if (index < 0) { + index = NUMBER_FRAME_START_TIMES - 1; + } + } + + if (times.length > NUMBER_FRAME_START_TIMES) { + for (; i<times.length; i++) { + times[i] = 0; + } + } + } + + return (lastFrameNumber); + } + + /** + * Sets the minimum frame cycle time, in milliseconds, for this + * view. The Java 3D renderer will ensure that the time between + * the start of each successive frame is at least the specified + * number of milliseconds. The default value is 0. + * + * @param minimumTime the minimum number of milliseconds between + * successive frames + * + * @exception IllegalArgumentException if <code>minimumTime < 0</code> + * + * @see #getFrameStartTimes + * + * @since Java 3D 1.2 + */ + public void setMinimumFrameCycleTime(long minimumTime) { + if (minimumTime < 0L) + throw new IllegalArgumentException(J3dI18N.getString("View27")); + + minFrameCycleTime = minimumTime; + VirtualUniverse.mc.setWork(); + } + + /** + * Retrieves the minimum frame cycle time, in milliseconds, for this view. + * @return the minimum frame cycle time for this view. + * + * @see #getFrameStartTimes + * + * @since Java 3D 1.2 + */ + public long getMinimumFrameCycleTime() { + return minFrameCycleTime; + } + + + /** + * This adds a frame time to the this of frame times + */ + void setFrameTimingValues() { + + synchronized (frameStartTimes) { + if (currentFrameIndex == NUMBER_FRAME_START_TIMES) { + currentFrameIndex = 0; + } + + frameNumbers[currentFrameIndex] = frameNumber; + + frameStartTimes[currentFrameIndex++] = startTime; + currentFrameStartTime = startTime; + currentFrameDuration = stopTime - startTime; + currentFrameNumber = frameNumber; + } + } + + /** + * Return true if maximum fps impose by user reach + */ + void computeCycleTime() { + if (minFrameCycleTime == 0) { + isMinCycleTimeAchieve = true; + sleepTime = 0; + } else { + sleepTime = minFrameCycleTime - + (System.currentTimeMillis() - startTime); + isMinCycleTimeAchieve = (sleepTime <= 0); + } + } + + + /** + * Enables or disables automatic freezing of the depth buffer for + * objects rendered + * during the transparent rendering pass (i.e., objects rendered + * using alpha blending) for this view. + * If enabled, depth buffer writes will be disabled during the + * transparent rendering pass regardless of the value of + * the depth buffer write enable flag in the RenderingAttributes + * object for a particular node. + * This flag is enabled by default. + * @param flag indicates whether automatic freezing of the depth buffer + * for transparent/antialiased objects is enabled. + * @see RenderingAttributes#setDepthBufferWriteEnable + */ + public void setDepthBufferFreezeTransparent(boolean flag) { + depthBufferFreezeTransparent = flag; + repaint(); + } + + /** + * Retrieves the current value of the depth buffer freeze transparent + * flag for this view. + * @return a flag that indicates whether or not the depth + * buffer is automatically frozen during the transparent rendering pass. + */ + public boolean getDepthBufferFreezeTransparent() { + return depthBufferFreezeTransparent; + } + + /** + * Enables or disables scene antialiasing for this view. + * If enabled, the entire scene will be antialiased on + * each canvas in which scene antialiasing is available. + * Scene antialiasing is disabled by default. + * <p> + * NOTE: Scene antialiasing is ignored in pure immediate mode, + * but is supported in mixed-immediate mode. + * @param flag indicates whether scene antialiasing is enabled + * + * @see Canvas3D#queryProperties + */ + public void setSceneAntialiasingEnable(boolean flag) { + sceneAntialiasingEnable = flag; + repaint(); + } + + /** + * Returns a flag that indicates whether or not scene antialiasing + * is enabled for this view. + * @return a flag that indicates whether scene antialiasing is enabled + */ + public boolean getSceneAntialiasingEnable() { + return sceneAntialiasingEnable; + } + + /** + * Sets a flag that indicates whether the local eyepoint is used in + * lighting calculations for perspective projections. + * If this flag is set to true, the view vector is calculated per-vertex + * based on the direction from the actual eyepoint to the vertex. + * If this flag is set to false, a single view vector is computed from + * the eyepoint to the center of the view frustum. This is + * called infinite eye lighting. + * Local eye lighting is disabled by default, and is ignored for + * parallel projections. + * @param flag indicates whether local eye lighting is enabled + */ + public void setLocalEyeLightingEnable(boolean flag) { + localEyeLightingEnable = flag; + repaint(); + } + + /** + * Retrieves a flag that indicates whether or not local eye lighting + * is enabled for this view. + * @return a flag that indicates whether local eye lighting is enabled + */ + public boolean getLocalEyeLightingEnable() { + return localEyeLightingEnable; + } + + /** + * Attach viewPlatform structure to this view. + * @param vp the viewPlatform to be attached + */ + public void attachViewPlatform(ViewPlatform vp) { + + if ((vp != null) && (vp == viewPlatform)) { + return; + } + + if (viewPlatform != null) { + ((ViewPlatformRetained)viewPlatform.retained).removeView(this); + if (viewPlatform.isLive()) { + synchronized (evaluateLock) { + viewPlatform = null; + // cleanup View stuff for the old platform + evaluateActive(); + viewPlatform = vp; + } + if (universe != null) { + universe.waitForMC(); + } + } else { + viewPlatform = vp; + } + } else { + viewPlatform = vp; + } + if (vp != null) { + if (vp.isLive()) { + checkView(); + setUniverse(((ViewPlatformRetained)vp.retained).universe); + } + ((ViewPlatformRetained)vp.retained).setView(this); + } + + evaluateActive(); + if ((vp == null) && (universe != null)) { + universe.waitForMC(); + } + } + + /** + * Retrieves the currently attached ViewPlatform object + * @return the currently attached ViewPlatform + */ + public ViewPlatform getViewPlatform() { + return viewPlatform; + } + + /** + * Checks view parameters for consistency + */ + void checkView() { + if (physicalBody == null) + throw new IllegalStateException(J3dI18N.getString("View13")); + + if (physicalEnvironment == null) + throw new IllegalStateException(J3dI18N.getString("View14")); + } + + + /** + * Stops the behavior scheduler after all + * currently scheduled behaviors are executed. Any frame-based + * behaviors scheduled to wake up on the next frame will be + * executed at least once before the behavior scheduler is + * stopped. + * <p> + * NOTE: This is a heavy-weight method + * intended for verification and image capture (recording); it + * is <i>not</i> intended to be used for flow control. + * @return a pair of integers that specify the beginning and ending + * time (in milliseconds since January 1, 1970 00:00:00 GMT) + * of the behavior scheduler's last pass + * @exception IllegalStateException if this method is called + * from a Behavior method or from any Canvas3D render callback + * method + */ + public final long[] stopBehaviorScheduler() { + long[] intervalTime = new long[2]; + + if (checkBehaviorSchedulerState("View15", "View16")) { + if (activeStatus && isRunning && + (universe.behaviorScheduler != null)) { + // view is active + universe.behaviorScheduler.stopBehaviorScheduler(intervalTime); + } else { + if ((universe != null) && + (universe.behaviorScheduler != null)) { + universe.behaviorScheduler.userStop = true; + } + } + } + stopBehavior = true; + return intervalTime; + } + + /** + * Starts the behavior scheduler running after it has been stopped. + * @exception IllegalStateException if this method is called + * from a Behavior method or from any Canvas3D render callback + * method + */ + public final void startBehaviorScheduler() { + if (checkBehaviorSchedulerState("View17", "View18")) { + if (activeStatus && isRunning && + (universe.behaviorScheduler != null)) { + universe.behaviorScheduler.startBehaviorScheduler(); + + } else { + if ((universe != null) && + (universe.behaviorScheduler != null)) { + universe.behaviorScheduler.userStop = false; + } + } + } + + stopBehavior = false; + } + + /** + * Check if BehaviorScheduler is in valid state to start/stop + * itself. + * @param s1 Exception String if method is called from a Canvas3D + * @param s2 Exception String if method is called from a Behavior method + * @return true if viewPlatform is live + * @exception IllegalStateException if this method is called + * from a Behavior method or from any Canvas3D render callback + * method + * + */ + boolean checkBehaviorSchedulerState(String s1, String s2) { + Thread me = Thread.currentThread(); + + if (inCanvasCallback) { + synchronized (canvasList) { + for (int i=canvases.size()-1; i>=0; i--) { + if (((Canvas3D)canvases.elementAt(i)).screen.renderer == me) { + throw new IllegalStateException(J3dI18N.getString(s1)); + } + } + } + } + + if ((viewPlatform != null) && viewPlatform.isLive()) { + if (universe.inBehavior && (universe.behaviorScheduler == me)) { + throw new IllegalStateException(J3dI18N.getString(s2)); + } + return true; + } + return false; + } + + /** + * Retrieves a flag that indicates whether the behavior scheduler is + * currently running. + * @return true if the behavior scheduler is running, false otherwise + * @exception IllegalStateException if this method is called + * from a Behavior method or from any Canvas3D render callback + * method + */ + public final boolean isBehaviorSchedulerRunning() { + return (((universe != null) && !stopBehavior && + (universe.behaviorScheduler != null)) ? + !universe.behaviorScheduler.userStop : false); + } + + /** + * Stops traversing the scene graph for this + * view after the current state of the scene graph is reflected on + * all canvases attached to this view. The renderers associated + * with these canvases are also stopped. + * <p> + * NOTE: This is a heavy-weight method + * intended for verification and image capture (recording); it + * is <i>not</i> intended to be used for flow control. + * @exception IllegalStateException if this method is called + * from a Behavior method or from any Canvas3D render callback + * method + */ + public final void stopView() { + checkViewState("View19", "View20"); + synchronized (startStopViewLock) { + if (activeStatus && isRunning) { + VirtualUniverse.mc.postRequest(MasterControl.STOP_VIEW, this); + while (isRunning) { + MasterControl.threadYield(); + } + } else { + isRunning = false; + } + } + } + + /** + * Starts + * traversing this view, and starts the renderers associated + * with all canvases attached to this view. + * @exception IllegalStateException if this method is called + * from a Behavior method or from any Canvas3D render callback + * method + */ + public final void startView() { + + checkViewState("View21", "View22"); + synchronized (startStopViewLock) { + if (activeStatus && !isRunning) { + VirtualUniverse.mc.postRequest(MasterControl.START_VIEW, this); + while (!isRunning) { + MasterControl.threadYield(); + } + VirtualUniverse.mc.sendRunMessage(this, + J3dThread.RENDER_THREAD); + } else { + isRunning = true; + } + } + + } + + /** + * This will throw IllegalStateException if not in valid state + * for start/stop request. + */ + void checkViewState(String s1, String s2) throws IllegalStateException { + if (inCanvasCallback) { + Thread me = Thread.currentThread(); + synchronized (canvasList) { + for (int i= canvases.size()-1; i>=0; i--) { + Canvas3D cv = (Canvas3D)canvases.elementAt(i); + if (cv.screen.renderer == me) { + throw new + IllegalStateException(J3dI18N.getString(s1)); + } + } + } + } + + if ((viewPlatform != null) && viewPlatform.isLive()) { + if (universe.inBehavior && + (Thread.currentThread() == universe.behaviorScheduler)) { + throw new IllegalStateException(J3dI18N.getString(s2)); + } + } + } + + /** + * Retrieves a flag that indicates whether the traverser is + * currently running on this view. + * @return true if the traverser is running, false otherwise + * @exception IllegalStateException if this method is called + * from a Behavior method or from any Canvas3D render callback + * method + */ + public final boolean isViewRunning() { + return isRunning; + } + + /** + * Renders one frame for a stopped View. Functionally, this + * method is equivalent to <code>startView()</code> followed by + * <code>stopview()</code>, except that it is atomic, which + * guarantees that only one frame is rendered. + * + * @exception IllegalStateException if this method is called from + * a Behavior method or from any Canvas3D render callback, or if + * the view is currently running. + * + * @since Java 3D 1.2 + */ + public void renderOnce() { + checkViewState("View28", "View29"); + synchronized (startStopViewLock) { + if (isRunning) { + throw new IllegalStateException(J3dI18N.getString("View30")); + } + renderOnceFinish = false; + VirtualUniverse.mc.postRequest(MasterControl.RENDER_ONCE, this); + while (!renderOnceFinish) { + MasterControl.threadYield(); + } + renderOnceFinish = true; + } + } + + /** + * Requests that this View be scheduled for rendering as soon as + * possible. The repaint method may return before the frame has + * been rendered. If the view is stopped, or if the view is + * continuously running (for example, due to a free-running + * interpolator), this method will have no effect. Most + * applications will not need to call this method, since any + * update to the scene graph or to viewing parameters will + * automatically cause all affected views to be rendered. + * + * @since Java 3D 1.2 + */ + public void repaint() { + if (activeStatus && isRunning) { + VirtualUniverse.mc.sendRunMessage(this, + J3dThread.RENDER_THREAD); + } + } + + + /** + * Update the view cache associated with this view. Also, shapshot + * the per-screen parameters associated with all screens attached + * to this view. + */ + final void updateViewCache() { + + + // DVR support + // This is a back door in j3d to provide DVR support. + // A better place to put this code segment is in + // ViewCache.snapshot(). Since it consists of some + // back door code, I've decided to put it here to isolate + // it from the rest of view snapshot code. + if(firstTime) { + // System.out.println("View : First Time is " + firstTime); + // viewer = Viewer.getViewer(this); + // Since we've the handler to the viewer, we can remove the entry + // now to avoid confusion and prevent memory leak problem. + viewer = Viewer.removeViewerMapEntry(this); + firstTime = false; + } + + if(viewer != null) { + if(viewer.isDvrEnabled()) { + dvrFactor = viewer.getDvrFactor(); + dvrResizeCompensation = + viewer.getDvrResizeCompensationEnable(); + /* + System.out.println("View : dvrFactor is " + dvrFactor); + System.out.println("View : dvrResizeCompensation is " + + dvrResizeCompensation); + */ + } + else { + // Reset back to default. + dvrFactor = 1.0f; + dvrResizeCompensation = true; + + } + } + // End of back door -- DVR. + + synchronized(this) { + viewCache.snapshot(); + viewCache.computeDerivedData(); + } + + // Just take the brute force approach and snapshot the + // parameters for each screen attached to each canvas. We won't + // worry about whether a screen is cached more than once. + // Eventually, dirty bits will take care of this. + + synchronized (canvasList) { + int i = canvases.size()-1; + while (i>=0) { + Screen3D scr = + ((Canvas3D)canvases.elementAt(i--)).getScreen3D(); + if (scr != null) + scr.updateViewCache(); + } + } + } + + + /** + * This routine activates or deactivates a view based on various information + */ + void evaluateActive() { + + synchronized (evaluateLock) { + if (universe == null) { + return; + } + + if ((viewPlatform == null) || + !viewPlatform.isLive() || + !((ViewPlatformRetained)viewPlatform.retained).switchState.currentSwitchOn) { + if (activeStatus) { + deactivate(); + activeStatus = false; + } + // Destroy threads from MC + if (VirtualUniverse.mc.isRegistered(this) && + (universe.isEmpty() || + (canvases.isEmpty() && + ((viewPlatform == null) || + !viewPlatform.isLive())))) { + // We can't wait until MC finish unregister view + // here because user thread may + // holds the universe.sceneGraphLock if branch + // or locale remove in clearLive(). In this way + // There is deadlock since MC also need need + // sceneGraphLock in some threads + // (e.g. TransformStructure update thread) + universe.unRegViewWaiting = this; + resetUnivCount = universeCount; + VirtualUniverse.mc.postRequest( + MasterControl.UNREGISTER_VIEW, this); + } + } else { + + // We're on a live view platform. See what the canvases say + // If view not register, MC will register it automatically + + int i; + VirtualUniverse u = null; + synchronized (canvasList) { + + for (i=canvases.size()-1; i>=0; i--) { + Canvas3D cv = (Canvas3D)canvases.elementAt(i); + if (cv.active) { + + if (!activeStatus && (universeCount > resetUnivCount)) { + u = universe; + } + break; + } + } + } + + // We should do this outside canvasList lock, + // otherwise it may cause deadlock with MC + if (u != null) { + activate(u); + activeStatus = true; + return; + } + + + if ((i < 0) && activeStatus) { + deactivate(); + activeStatus = false; + return; + } + + if (VirtualUniverse.mc.isRegistered(this)) { + // notify MC that canvases state for this view changed + VirtualUniverse.mc.postRequest( + MasterControl.REEVALUATE_CANVAS, this); + } + } + } + } + + void setUniverse(VirtualUniverse universe) { + + synchronized (VirtualUniverse.mc.requestObjList) { + if ((renderBin == null) || + (renderBin.universe != universe)) { + if (renderBin != null) { + renderBin.cleanup(); + } + renderBin = new RenderBin(universe, this); + renderBin.universe = universe; + } + + + if ((soundScheduler == null) || + (soundScheduler.universe != universe)) { + // create a sound scheduler for this view, with this universe + if (soundScheduler != null) { + soundScheduler.cleanup(); + } + soundScheduler = new SoundScheduler(universe, this); + } + + + // This has to be the last call before + // RenderBin and SoundScheduler construct. Since it is + // possible that canvas receive paint call and invoked + // evaluateActive in another thread - which check for + // universe == null and may let it pass before soundScheduler + // and renderBin initialize. + universeCount++; + this.universe = universe; + } + evaluateActive(); + } + + /** + * This activates all traversers and renderers associated with this view. + */ + void activate(VirtualUniverse universe) { + + universe.checkForEnableEvents(); + + if (physicalBody != null) { + physicalBody.addUser(this); + } + + if (!VirtualUniverse.mc.isRegistered(this)) { + universe.regViewWaiting = this; + } + + VirtualUniverse.mc.postRequest(MasterControl.ACTIVATE_VIEW, + this); + + if (!universe.isSceneGraphLock) { + universe.waitForMC(); + } + if (soundScheduler != null) { + soundScheduler.reset(this); + } + + J3dMessage vpMessage = VirtualUniverse.mc.getMessage(); + vpMessage.universe = universe; + vpMessage.view = this; + vpMessage.type = J3dMessage.UPDATE_VIEW; + vpMessage.threads = + J3dThread.SOUND_SCHEDULER | + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_BEHAVIOR; + vpMessage.args[0] = this; + synchronized(((ViewPlatformRetained)viewPlatform.retained).sphere) { + vpMessage.args[1] = new Float(((ViewPlatformRetained)viewPlatform.retained).sphere.radius); + } + vpMessage.args[2] = new Integer(OTHER_ATTRS_CHANGED); + vpMessage.args[3] = new Integer(transparencySortingPolicy); + VirtualUniverse.mc.processMessage(vpMessage); + } + + /** + * This deactivates all traversers and renderers associated with this view. + */ + void deactivate() { + VirtualUniverse.mc.postRequest(MasterControl.DEACTIVATE_VIEW, this); + if (physicalBody != null) { + physicalBody.removeUser(this); + } + + // This is a temporary fix for bug 4267395 + // TODO:cleanup in RenderBin after View detach + // universe.addViewIdToFreeList(viewId); + + J3dMessage vpMessage = VirtualUniverse.mc.getMessage(); + vpMessage.universe = universe; + vpMessage.view = this; + vpMessage.type = J3dMessage.UPDATE_VIEW; + vpMessage.threads = + J3dThread.SOUND_SCHEDULER | + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_BEHAVIOR; + vpMessage.args[0] = this; + if (viewPlatform != null) { + synchronized(((ViewPlatformRetained)viewPlatform.retained).sphere) { + vpMessage.args[1] = new Float(((ViewPlatformRetained)viewPlatform.retained).sphere.radius); + } + } else { + vpMessage.args[1] = new Float(0); + } + vpMessage.args[2] = new Integer(OTHER_ATTRS_CHANGED); + vpMessage.args[3] = new Integer(transparencySortingPolicy); + VirtualUniverse.mc.processMessage(vpMessage); + + } + + void cleanupViewId() { + universe.addViewIdToFreeList(viewId); + viewId = null; + } + + + void assignViewId () { + if (viewId == null) { + viewId = universe.getViewId(); + viewIndex = viewId.intValue(); + } + } + + /** + * This method passes window event to SoundScheduler + */ + void sendEventToSoundScheduler(AWTEvent evt) { + if (soundScheduler != null) { + soundScheduler.receiveAWTEvent(evt); + } + } + + void reset() { + + for (int i=0; i < canvases.size(); i++) { + ((Canvas3D) canvases.get(i)).reset(); + } + + // reset the renderBinReady flag + renderBinReady = false; + + soundScheduler.cleanup(); + soundScheduler = null; + soundRenderer = new SoundRenderer(); + + viewCache = new ViewCache(this); + getCanvasList(true); + cleanupViewId(); + renderBin.cleanup(); + renderBin = null; + universe = null; + } +} diff --git a/src/classes/share/javax/media/j3d/ViewCache.java b/src/classes/share/javax/media/j3d/ViewCache.java new file mode 100644 index 0000000..c04883c --- /dev/null +++ b/src/classes/share/javax/media/j3d/ViewCache.java @@ -0,0 +1,343 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; + +/** + * The ViewCache class is used to cache all data, both API data and derived + * data, that is independent of the Canvas3D and Screen3D. + */ +class ViewCache extends Object { + // The view associated with this view cache + View view; + + // + // API/INPUT DATA + // + + // ********************* + // * From ViewPlatform * + // ********************* + int viewAttachPolicy; + + // ********************* + // * From PhysicalBody * + // ********************* + + /** + * The user's left eye's position in head coordinates. + */ + Point3d leftEyePosInHead = new Point3d(); + + /** + * The user's right eye's position in head coordinates. + */ + Point3d rightEyePosInHead = new Point3d(); + + /** + * The user's left ear's position in head coordinates. + */ + Point3d leftEarPosInHead = new Point3d(); + + /** + * The user's right ear's position in head coordinates. + */ + Point3d rightEarPosInHead = new Point3d(); + + /** + * The user's nominal eye height as measured + * from the ground plane. + */ + double nominalEyeHeightFromGround; + + /** + * The amount to offset the system's + * viewpoint from the user's current eye-point. This offset + * distance allows an "Over the shoulder" view of the scene + * as seen by the user. + */ + double nominalEyeOffsetFromNominalScreen; + + // Head to head-tracker coordinate system transform. + // If head tracking is enabled, this transform is a calibration + // constant. If head tracking is not enabled, this transform is + // not used. + // This is only used in SCREEN_VIEW mode. + Transform3D headToHeadTracker = new Transform3D(); + + // **************************** + // * From PhysicalEnvironment * + // **************************** + + // Coexistence coordinate system to tracker-base coordinate + // system transform. If head tracking is enabled, this transform + // is a calibration constant. If head tracking is not enabled, + // this transform is not used. + // This is used in both SCREEN_VIEW and HMD_VIEW modes. + Transform3D coexistenceToTrackerBase = new Transform3D(); + + // Transform generated by the head tracker to transform + // from tracker base to head tracker coordinates (and the inverse). + Transform3D headTrackerToTrackerBase = new Transform3D(); + Transform3D trackerBaseToHeadTracker = new Transform3D(); + + // + // Indicates whether the underlying hardware implementation + // supports tracking. + // + boolean trackingAvailable; + + // Sensor index for head tracker + int headIndex; + + // + // This variable specifies the policy Java 3D will use in placing + // the user's eye position relative to the user's head position + // (NOMINAL_SCREEN, NOMINAL_HEAD, or NOMINAL_FEET). + // It is used in the calibration process. + // + int coexistenceCenterInPworldPolicy; + + + // ************* + // * From View * + // ************* + + // View model compatibility mode flag + boolean compatibilityModeEnable; + + // coexistenceCenteringEnable flag + boolean coexistenceCenteringEnable; + + Point3d leftManualEyeInCoexistence = new Point3d(); + Point3d rightManualEyeInCoexistence = new Point3d(); + + // Indicates which major mode of view computation to use: + // HMD mode or screen/fish-tank-VR mode. + int viewPolicy; + + // The current projection policy (parallel versus perspective) + int projectionPolicy; + + // The current screen scale policy and scale value + int screenScalePolicy; + double screenScale; + + // The current window resize, movement and eyepoint policies + int windowResizePolicy; + int windowMovementPolicy; + int windowEyepointPolicy; + + // The current monoscopic view policy + int monoscopicViewPolicy; + + // The view model's field of view. + double fieldOfView; + + // The distance away from the clip origin + // in the direction of gaze for the front and back clip planes. + double frontClipDistance; + double backClipDistance; + + // Front and back clip policies + int frontClipPolicy; + int backClipPolicy; + + // ViewPlatform of this view + ViewPlatformRetained vpRetained; + + /** + * Defines the visibility policy. + */ + int visibilityPolicy; + + // Flag to enable tracking, if so allowed by the trackingAvailable flag. + boolean trackingEnable; + + // This setting enables the continuous updating by Java 3D of the + // userHeadToVworld transform. + boolean userHeadToVworldEnable; + + // The current compatibility mode view transform + Transform3D compatVpcToEc = new Transform3D(); + + // The current compatibility mode projection transforms + Transform3D compatLeftProjection = new Transform3D(); + Transform3D compatRightProjection = new Transform3D(); + + // Mask that indicates ViewCache's view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + int vcDirtyMask = 0; + + // + // DERIVED DATA + // + + // Flag indicating that head tracking will be used + private boolean doHeadTracking; + + // + // Matrix to transform from user-head to + // virtual-world coordinates. This matrix is a read-only + // value that Java 3D generates continuously, but only if enabled + // by userHeadToVworldEnableFlag. + // + Transform3D userHeadToVworld = new Transform3D(); + + + /** + * Take snapshot of all per-view API parameters and input values. + */ + synchronized void snapshot() { + + // View parameters + vcDirtyMask = view.vDirtyMask; + view.vDirtyMask = 0; + compatibilityModeEnable = view.compatibilityModeEnable; + coexistenceCenteringEnable = view.coexistenceCenteringEnable; + leftManualEyeInCoexistence.set(view.leftManualEyeInCoexistence); + rightManualEyeInCoexistence.set(view.rightManualEyeInCoexistence); + viewPolicy = view.viewPolicy; + projectionPolicy = view.projectionPolicy; + screenScalePolicy = view.screenScalePolicy; + windowResizePolicy = view.windowResizePolicy; + windowMovementPolicy = view.windowMovementPolicy; + windowEyepointPolicy = view.windowEyepointPolicy; + monoscopicViewPolicy = view.monoscopicViewPolicy; + + fieldOfView = view.fieldOfView; + screenScale = view.screenScale; + + frontClipDistance = view.frontClipDistance; + backClipDistance = view.backClipDistance; + frontClipPolicy = view.frontClipPolicy; + backClipPolicy = view.backClipPolicy; + + visibilityPolicy = view.visibilityPolicy; + + trackingEnable = view.trackingEnable; + userHeadToVworldEnable = view.userHeadToVworldEnable; + + view.compatVpcToEc.getWithLock(compatVpcToEc); + view.compatLeftProjection.getWithLock(compatLeftProjection); + view.compatRightProjection.getWithLock(compatRightProjection); + + // ViewPlatform parameters + ViewPlatform vpp = view.getViewPlatform(); + + if (vpp == null) { + // This happens when user attach a null viewplatform + // and MC still call updateViewCache() in run before + // the viewDeactivate request get. + return; + } + + vpRetained = (ViewPlatformRetained) vpp.retained; + + synchronized(vpRetained) { + vcDirtyMask |= vpRetained.vprDirtyMask; + vpRetained.vprDirtyMask = 0; + viewAttachPolicy = vpRetained.viewAttachPolicy; + // System.out.println("ViewCache snapshot vcDirtyMask " + vcDirtyMask ); + } + + // PhysicalEnvironment parameters + PhysicalEnvironment env = view.getPhysicalEnvironment(); + + synchronized(env) { + vcDirtyMask |= env.peDirtyMask; + env.peDirtyMask = 0; + + env.coexistenceToTrackerBase.getWithLock(coexistenceToTrackerBase); + trackingAvailable = env.trackingAvailable; + coexistenceCenterInPworldPolicy = env.coexistenceCenterInPworldPolicy; + + // NOTE: this is really derived data, but we need it here in order + // to avoid reading head tracked data when no tracker is available + // and enabled. + doHeadTracking = trackingEnable && trackingAvailable; + + if (doHeadTracking) { + headIndex = env.getHeadIndex(); + env.getSensor(headIndex).getRead(headTrackerToTrackerBase); + vcDirtyMask |= View.TRACKING_ENABLE_DIRTY; + } + else { + headTrackerToTrackerBase.setIdentity(); + } + } + + // PhysicalBody parameters + PhysicalBody body = view.getPhysicalBody(); + + synchronized(body) { + vcDirtyMask |= body.pbDirtyMask; + body.pbDirtyMask = 0; + + leftEyePosInHead.set(body.leftEyePosition); + rightEyePosInHead.set(body.rightEyePosition); + leftEarPosInHead.set(body.leftEarPosition); + rightEarPosInHead.set(body.rightEarPosition); + + nominalEyeHeightFromGround = body.nominalEyeHeightFromGround; + nominalEyeOffsetFromNominalScreen = + body.nominalEyeOffsetFromNominalScreen; + } + + body.headToHeadTracker.getWithLock(headToHeadTracker); + } + + + /** + * Compute derived data using the snapshot of the per-view data. + */ + synchronized void computeDerivedData() { + if (doHeadTracking) { + trackerBaseToHeadTracker.invert(headTrackerToTrackerBase); + //System.out.println("trackerBaseToHeadTracker: "); + //System.out.println(trackerBaseToHeadTracker); + } + else { + trackerBaseToHeadTracker.setIdentity(); + } + + // TODO: implement head to vworld tracking if userHeadToVworldEnable + // is set + userHeadToVworld.setIdentity(); + } + + + // Get methods for returning derived data values. + // Eventually, these get functions will cause some of the parameters + // to be lazily evaluated. + // + // NOTE that in the case of Transform3D, and Tuple objects, a reference + // to the actual derived data is returned. In these cases, the caller + // must ensure that the returned data is not modified. + + boolean getDoHeadTracking() { + return doHeadTracking; + } + + /** + * Constructs and initializes a ViewCache object. + */ + ViewCache(View view) { + this.view = view; + + if (false) + System.out.println("Constructed a ViewCache"); + } + +} diff --git a/src/classes/share/javax/media/j3d/ViewPlatform.java b/src/classes/share/javax/media/j3d/ViewPlatform.java new file mode 100644 index 0000000..6436b42 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ViewPlatform.java @@ -0,0 +1,265 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + + +/** + * The ViewPlatform leaf node object controls the position, orientation + * and scale of the viewer. It is the node in the scene graph that a + * View object connects to. A viewer navigates through the virtual + * universe by changing the transform in the scene graph hierarchy above + * the ViewPlatform. + * <p> + * <b>The View Attach Policy</b> + * <p> + * The actual view that Java 3D's renderer draws depends on the view + * attach policy specified within the currently attached ViewPlatform. + * The view attach policy, set by the setViewAttachPolicy + * method, is one of the following: + * <p> + * <UL> + * <LI>View.NOMINAL_HEAD - ensures that the end-user's nominal eye + * position in the physical world corresponds to the virtual eye's + * nominal eye position in the virtual world (the ViewPlatform's origin). + * In essence, this policy tells Java 3D to position the virtual eyepoint + * relative to the ViewPlatform origin in the same way as the physical + * eyepoint is positioned relative to its nominal physical-world + * origin. Deviations in the physical eye's position and orientation from + * nominal in the physical world generate corresponding deviations of the + * virtual eye's position and orientation in the virtual world. This + * is the default view attach policy.</LI> + * <p> + * <LI>View.NOMINAL_FEET - ensures that the end-user's virtual feet + * always touch the virtual ground. This policy tells Java 3D to compute + * the physical-to-virtual-world correspondence in a way that enforces + * this constraint. Java 3D does so by appropriately offsetting the + * physical eye's position by the end-user's physical height. Java 3D + * uses the nominalEyeHeightFromGround parameter found in the + * PhysicalBody object to perform this computation.</LI> + * <p> + * <LI>View.NOMINAL_SCREEN - allows an application to always have + * the virtual eyepoint appear at some "viewable" distance from a point + * of interest. This policy tells Java 3D to compute the + * physical-to-virtual-world correspondence in a way + * that ensures that the renderer moves the nominal virtual eyepoint + * away from the point of interest by the amount specified by the + * nominalEyeOffsetFromNominalScreen parameter found in the + * PhysicalBody object.</LI></UL> + * <p> + * <b>Activation Radius</b> + * <p> + * The ViewPlatform's activation radius defines an activation + * volume surrounding the center of the ViewPlatform. This activation + * volume is a spherical region that intersects with the scheduling regions + * and application regions + * of other leaf node objects to determine which of those objects may + * affect rendering. Only active view platforms--that is, view platforms + * attached to a View--will be used to schedule or select other leaf nodes. + * <p> + * Different leaf objects interact with the ViewPlatform's activation + * volume differently. The Background, Clip, and Soundscape leaf objects + * each define a set of attributes and an application region in which + * those attributes are applied. If more than one node of a given type + * (Background, Clip, or Soundscape) intersects an active ViewPlatform's + * activation volume, the "most appropriate" node is selected for that View. + * Sound leaf objects and Behavior objects become active when + * their scheduling region intersects an active ViewPlatform's activation + * volume. + * <p> + * The activation radius is in view platform coordinates. For the + * default screen scale policy of SCALE_SCREEN_SIZE, the + * activationRadius parameter value is multiplied by half the + * monitor screen size to derive the actual activation radius. For example, + * for the default screen size of 0.35 meters, and the default activation + * radius value of 62, the actual activation radius would be 10.85 + * meters. + * <p> + * <UL> + * <code>62 * 0.35 / 2 = 10.85</code> + * </UL> + * <p> + * + * @see View + */ + +public class ViewPlatform extends Leaf { + + /** + * Specifies that the ViewPlatform allows read access to its view + * attach policy information at runtime. + */ + public static final int + ALLOW_POLICY_READ = CapabilityBits.VIEW_PLATFORM_ALLOW_POLICY_READ; + + /** + * Specifies that the ViewPlatform allows write access to its view + * attach policy information at runtime. + */ + public static final int + ALLOW_POLICY_WRITE = CapabilityBits.VIEW_PLATFORM_ALLOW_POLICY_WRITE; + + /** + * Constructs a ViewPlatform object with default parameters. + * The default values are as follows: + * <ul> + * view attach policy : View.NOMINAL_HEAD<br> + * activation radius : 62<br> + * </ul> + */ + public ViewPlatform() { + } + + /** + * Creates the retained mode ViewPlatformRetained object that this + * ViewPlatform component object will point to. + */ + void createRetained() { + this.retained = new ViewPlatformRetained(); + this.retained.setSource(this); + } + + + /** + * Sets the view attach policy that determines the coexistence center + * in the virtual world. This policy determines how Java 3D places the + * view platform relative to the position of the user's head, one of + * View.NOMINAL_SCREEN, View.NOMINAL_HEAD, or View.NOMINAL_FEET. + * The default policy is View.NOMINAL_HEAD. + * @param policy the new policy + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * @see View#NOMINAL_SCREEN + * @see View#NOMINAL_HEAD + * @see View#NOMINAL_FEET + */ + public void setViewAttachPolicy(int policy) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POLICY_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewPlatform0")); + + switch (policy) { + case View.NOMINAL_SCREEN: + case View.NOMINAL_HEAD: + case View.NOMINAL_FEET: + break; + + default: + throw new IllegalArgumentException(J3dI18N.getString("ViewPlatform1")); + } + + ((ViewPlatformRetained)this.retained).setViewAttachPolicy(policy); + } + + /** + * Returns the current coexistence center in virtual-world policy. + * @return one of: View.NOMINAL_SCREEN, View.NOMINAL_HEAD, or + * View.NOMINAL_FEET + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int getViewAttachPolicy() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_POLICY_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewPlatform2")); + + return ((ViewPlatformRetained)this.retained).getViewAttachPolicy(); + } + + /** + * Set the ViewPlatform's activation radius which defines an activation + * volume around the view platform. + * @param activationRadius the new activation radius + */ + public void setActivationRadius(float activationRadius) { + ((ViewPlatformRetained)this.retained).setActivationRadius(activationRadius); + } + + /** + * Get the ViewPlatform's activation radius. + * @return the ViewPlatform activation radius + */ + public float getActivationRadius() { + return ((ViewPlatformRetained)this.retained).getActivationRadius(); + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * <code>cloneNode</code> should be overridden by any user subclassed + * objects. All subclasses must have their <code>cloneNode</code> + * method consist of the following lines: + * <P><blockquote><pre> + * public Node cloneNode(boolean forceDuplicate) { + * UserSubClass usc = new UserSubClass(); + * usc.duplicateNode(this, forceDuplicate); + * return usc; + * } + * </pre></blockquote> + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + ViewPlatform v = new ViewPlatform(); + v.duplicateNode(this, forceDuplicate); + return v; + } + + + /** + * Copies all ViewPlatform information from <code>originalNode</code> into + * the current node. This method is called from the + * <code>duplicateNode</code> method. This routine does + * the actual duplication of all "local data" (any data defined in + * this object). It then will duplicate the retained side of the + * tree if this method was called from its own 2 parameter + * <code>duplicateNode</code> method. This is designate by setting the + * <code>duplicateRetained</code> flag to <code>true</code>. + * Without this flag a <code>duplicateNode</code> method would not + * whether or not to duplicate the retained side of the object. + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * @param duplicateRetained set to <code>true</code> when this + * method is should initiate the duplicateRetained call. This + * call walks up a nodes superclasses so it should only be called + * once from the class of the original node. + * + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + super.duplicateAttributes(originalNode, forceDuplicate); + + ViewPlatformRetained attr = + (ViewPlatformRetained) originalNode.retained; + ViewPlatformRetained rt = (ViewPlatformRetained) retained; + + rt.setActivationRadius(attr.getActivationRadius()); + rt.setViewAttachPolicy(attr.getViewAttachPolicy()); + } + +} diff --git a/src/classes/share/javax/media/j3d/ViewPlatformRetained.java b/src/classes/share/javax/media/j3d/ViewPlatformRetained.java new file mode 100644 index 0000000..8d7c031 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ViewPlatformRetained.java @@ -0,0 +1,410 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import javax.vecmath.*; +import java.util.ArrayList; + +/** + * ViewPlatform object (retained side) + */ + +class ViewPlatformRetained extends LeafRetained { + + // different types of IndexedUnorderedSet that use in BehaviorStructure + static final int VP_IN_BS_LIST = 0; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 1; + + /** + * This variable specifies the policy Java 3D will use in placing the + * user's eye point as a function of head position. The variable can + * contain one of NOMINAL_SCREEN, NOMINAL_HEAD, or NOMINAL_FEET. + */ + int viewAttachPolicy = View.NOMINAL_HEAD; + + /** + * The list of views associated with this view platform. + * Use getViewList() to access this variable. + */ + private ArrayList viewList = new ArrayList(); + + /** + * Cached list of viewList for synchronization + */ + private View views[] = null; + + // The locale that this node is decended from + Locale locale = null; + + // dirty flag for viewList + boolean viewListDirty = true; + + /** + * The current cached view platform transform (vworldToVpc) and + * its inverse (vpcToVworld). + */ + Transform3D vworldToVpc = null; + Transform3D vpcToVworld = new Transform3D(); + + + /** + * The activation radius. The value is chosen so that when using a + * default screen scale and field of view, the entire view frustum + * is enclosed. + */ + + /** + * Position used for placing this view platform. + */ + BoundingSphere sphere = + new BoundingSphere(new Point3d(0.0,0.0,0.0), 62.0f); + + /** + * This is the cached bounding sphere used for the activation volume. + */ + BoundingSphere schedSphere; + Point3d center = new Point3d(); + final static Point3d zeroPoint = new Point3d(); + + // Mask that indicates this ViewPlatformRetained's view dependence info. has changed, + // and CanvasViewCache may need to recompute the final view matries. + int vprDirtyMask = (View.VPR_VIEW_ATTACH_POLICY_DIRTY + | View.VPR_VIEWPLATFORM_DIRTY); + + static final Object emptyObj[] = new Object[0]; + static final Transform3D identity = new Transform3D(); + + ViewPlatformRetained() { + this.nodeType = NodeRetained.VIEWPLATFORM; + localBounds = new BoundingBox(); + ((BoundingBox)localBounds).setLower( 1.0, 1.0, 1.0); + ((BoundingBox)localBounds).setUpper(-1.0,-1.0,-1.0); + IndexedUnorderSet.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + schedSphere = (BoundingSphere) sphere.clone(); + } + + /** + * Sets the coexistence center in virtual world policy. + * This setting determines how Java 3D places the + * user's eye point as a function of head position. The variable can + * contain one of NOMINAL_SCREEN, NOMINAL_HEAD, or NOMINAL_FEET. + * @param policy the new policy, one of NOMINAL_SCREEN, NOMINAL_HEAD, + * or NOMINAL_FEET + */ + void setViewAttachPolicy(int policy) { + synchronized(this) { + this.viewAttachPolicy = policy; + vprDirtyMask |= View.VPR_VIEW_ATTACH_POLICY_DIRTY; + } + + if (source != null && source.isLive()) { + repaint(); + } + } + + + void repaint() { + View views[] = getViewList(); + for (int i=views.length-1; i >=0; i--) { + views[i].repaint(); + } + } + + /** + * Returns the current coexistence center in virtual-world policy. + * @return one of: NOMINAL_SCREEN, NOMINAL_HEAD, or NOMINAL_FEET + */ + int getViewAttachPolicy() { + return this.viewAttachPolicy; + } + + /** + * Set the ViewPlatform's activation radius + */ + void setActivationRadius(float activationRadius) { + sphere.setRadius(activationRadius); + + if (source != null && source.isLive()) { + repaint(); + } + // Notify behavior scheduler & RenderBin + if (source.isLive()) { + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.UPDATE_VIEWPLATFORM; + message.threads = J3dThread.UPDATE_RENDER|J3dThread.UPDATE_BEHAVIOR; + message.universe = universe; + message.args[0] = this; + message.args[1] = new Float(activationRadius); + VirtualUniverse.mc.processMessage(message); + } else { + schedSphere.setRadius(activationRadius); + } + + } + + /** + * Get the ViewPlatform's activation radius + */ + float getActivationRadius() { + return (float) sphere.getRadius(); + } + + /** + * This sets the view that is associated with this view platform. + */ + // TODO: This must be changed to a list of views! + void setView(View v) { + synchronized (viewList) { + if (!viewList.contains(v)) { + viewList.add(v); + } + viewListDirty = true; + } + } + + void removeView(View v) { + synchronized (viewList) { + if (viewList.contains(v)) { + viewList.remove(viewList.indexOf(v)); + } + viewListDirty = true; + } + } + + Transform3D getVworldToVpc() { + if (vworldToVpc == null) + vworldToVpc = VirtualUniverse.mc.getTransform3D(null); + vworldToVpc.set(getCurrentLocalToVworld(null)); + vworldToVpc.invert(); + return vworldToVpc; + } + + Transform3D getVpcToVworld() { + vpcToVworld .set(getCurrentLocalToVworld(null)); + return vpcToVworld; + } + + + void evaluateViewPlatformTransform() { + if (vworldToVpc != null) { + FreeListManager.freeObject(FreeListManager.TRANSFORM3D, + vworldToVpc); + } + // clear cache so that next time getVworldToVpc() can recompute + vworldToVpc = null; + } + + /** + * Evaluate the view platform transform by traversing *up* the tree from + * this ViewPlatform node, computing the composite model transform + * along the way. Because we are traversing bottom to top, we must + * multiply each TransformGroup's matrix on the left by the + * composite transform on the right (rather than the other way + * around as is usually done). Once we have the composite model + * transform for this ViewPlatform--the vpcToVworld transform--we + * simply invert it to get the vworldToVpc transform. + */ + void evaluateInitViewPlatformTransform(NodeRetained node, Transform3D trans) { + if (node instanceof TransformGroupRetained) { + Transform3D tmpTrans = new Transform3D(); + TransformGroupRetained tgr = (TransformGroupRetained)node; + tgr.transform.getWithLock(tmpTrans); + trans.mul(tmpTrans, trans); + } + + NodeRetained parent = node.getParent(); + if (parent != null) { + // Not at the top yet. + evaluateInitViewPlatformTransform(parent, trans); + } + } + + void evaluateInitViewPlatformTransform() { + + Transform3D lastLocalToVworld; + + synchronized (this) { + lastLocalToVworld = getLastLocalToVworld(); + + if (lastLocalToVworld.equals(identity)) { + // lastLocalToVworld not yet updated + // for Renderer viewCache when startup + evaluateInitViewPlatformTransform((NodeRetained)this, + lastLocalToVworld); + } + } + } + + + // This is invoke from BehaviorStructure + void updateActivationRadius(float radius) { + schedSphere.setCenter(zeroPoint); + schedSphere.setRadius(radius); + schedSphere.transform(getCurrentLocalToVworld(null)); + } + + // This is invoke from BehaviorStructure when TransformGroup + // above this viewplatform changed + void updateTransformRegion() { + Transform3D tr = getCurrentLocalToVworld(null); + schedSphere.setCenter(zeroPoint); + schedSphere.transform(tr); + tr.transform(zeroPoint, center); + } + + /** + * This setLive routine first calls the superclass's method, then + * it evaluates the view platform transform, and then it activates + * all canvases that are associated with the attached view. + */ + void setLive(SetLiveState s) { + View views[] = getViewList(); + + for (int i = views.length-1; i>=0; i--) { + views[i].checkView(); + } + + super.doSetLive(s); + + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("ViewPlatformRetained1")); + } + + if (inSharedGroup) { + throw new + IllegalSharingException(J3dI18N.getString("ViewPlatformRetained2")); + } + + if (s.viewLists != null) { + throw new + IllegalSceneGraphException(J3dI18N.getString("ViewPlatformRetained3")); + } + /* + if (false) { + System.out.println("setLive: vworldToVpc = "); + System.out.println(this.vworldToVpc); + System.out.println("setLive: vpcToVworld = "); + System.out.println(this.vpcToVworld); + } + */ + this.locale = s.locale; + + + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.VPF_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + } + // process switch leaf + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.VPF_TARGETS); + } + switchState = (SwitchState)s.switchStates.get(0); + s.nodeList.add(this); + s.notifyThreads |= (J3dThread.UPDATE_BEHAVIOR); + super.markAsLive(); + for (int i = views.length-1; i>=0; i--) { + views[i].setUniverse(s.universe); + views[i].evaluateActive(); + } + + universe.addViewPlatform(this); + s.traverseFlags |= NodeRetained.CONTAINS_VIEWPLATFORM; + } + + /** + * This clearLive routine first calls the superclass's method, then + * it deactivates all canvases that are associated with the attached + * view. + */ + void clearLive(SetLiveState s) { + super.clearLive(s); + if (s.switchTargets != null && + s.switchTargets[0] != null) { + s.switchTargets[0].addNode(this, Targets.VPF_TARGETS); + } + + View views[] = getViewList(); + for (int i = views.length-1; i>=0; i--) { + views[i].evaluateActive(); + } + s.nodeList.add(this); + if (s.transformTargets != null && s.transformTargets[0] != null) { + s.transformTargets[0].addNode(this, Targets.VPF_TARGETS); + s.notifyThreads |= J3dThread.UPDATE_TRANSFORM; + + } + s.notifyThreads |= (J3dThread.UPDATE_BEHAVIOR | + J3dThread.SOUND_SCHEDULER); + universe.removeViewPlatform(this); + } + + /** + * Re-evaluate all View active status reference to this view + * platform. This procedure is called from RenderBin when switch + * above a view platform changed. + */ + void reEvaluateView() { + View views[] = getViewList(); + + for (int i=views.length-1; i >=0; i--) { + views[i].evaluateActive(); + } + } + + /** + * Get a copy of cached view list + */ + View[] getViewList() { + synchronized (viewList) { + if (viewListDirty) { + views = (View []) viewList.toArray(new View[viewList.size()]); + viewListDirty = false; + } + return views; + } + } + + /** + * Use by BehaviorStructure to determine whether current + * ViewPlatform is active or not. + */ + boolean isActiveViewPlatform() { + View v[] = getViewList(); + if (v != null) { + for (int i=0; i < v.length; i++) { + if (v[i].active) { + return true; + } + } + } + return false; + } + + void processSwitchChanged() { + reEvaluateView(); + } + + + void compile(CompileState compState) { + + super.compile(compState); + + // keep the parent transform group. It's not worth + // to push the static transform here. + compState.keepTG = true; + } +} diff --git a/src/classes/share/javax/media/j3d/ViewSpecificGroup.java b/src/classes/share/javax/media/j3d/ViewSpecificGroup.java new file mode 100644 index 0000000..71dc123 --- /dev/null +++ b/src/classes/share/javax/media/j3d/ViewSpecificGroup.java @@ -0,0 +1,324 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; + +/** + * The ViewSpecificGroup node is a Group whose descendants are + * rendered only on a specified set of views. It + * contains a list of views on which its descendants are + * rendered. Methods are provided to add, remove, and enumerate the + * list of views. The list of views is initially empty, meaning that + * the descendants of this group will not be rendered on any view. At + * least one view must be added to the list of views for rendering to + * occur. + * + * <p> + * All nodes except ViewPlatform may appear as a descendant of + * ViewSpecificGroup (including another ViewSpecificGroup). Behavior + * nodes may appear under a ViewSpecificGroup, but are not affected by + * it--the Behavior scheduler is per-universe rather than per-View. + * BoundingLeaf nodes are similarly unaffected by being under a + * ViewSpecificGroup. A BoundingLeaf under a ViewSpecificGroup + * provides a valid bounds for any node that refers to it, + * irrespective of the view. + * The rest of the leaf nodes either: A) are only rendered within the + * specified view(s), for example, Shape3D, Morph, and Sound; or B) + * only affect other objects when they are rendered in the specified + * view(s), for example, AlternateAppearance, Clip, ModelClip, Fog, + * Light, Soundscape, Background. + * + * @since Java 3D 1.3 + */ + +public class ViewSpecificGroup extends Group { + /** + * Specifies that this ViewSpecificGroup node allows reading its + * view information at runtime. + */ + public static final int + ALLOW_VIEW_READ = CapabilityBits.VIEW_SPECIFIC_GROUP_ALLOW_VIEW_READ; + + /** + * Specifies that this ViewSpecificGroup node allows writing its + * view information at runtime. + */ + public static final int + ALLOW_VIEW_WRITE = CapabilityBits.VIEW_SPECIFIC_GROUP_ALLOW_VIEW_WRITE; + + + /** + * Constructs and initializes a new ViewSpecificGroup node object. + */ + public ViewSpecificGroup() { + } + + + /** + * Creates the retained mode ViewSpecificGroupRetained object that this + * ViewSpecificGroup component object will point to. + */ + void createRetained() { + this.retained = new ViewSpecificGroupRetained(); + this.retained.setSource(this); + } + + + /** + * Replaces the view at the specified index in this node's + * list of views with the specified View object. + * + * @param view the View object to be stored at the specified index. + * @param index the index of the View object to be replaced. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void setView(View view, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup1")); + + ((ViewSpecificGroupRetained)this.retained).setView(view, index); + } + + + /** + * Retrieves the View object at the specified index from this node's + * list of views. + * + * @param index the index of the View object to be returned. + * @return the View object at the specified index. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public View getView(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup2")); + + return ((ViewSpecificGroupRetained)this.retained).getView(index); + } + + + /** + * Inserts the specified View object into this node's + * list of views at the specified index. + * + * @param view the View object to be inserted at the specified index. + * @param index the index at which the View object is inserted. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void insertView(View view, int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup1")); + + ((ViewSpecificGroupRetained)this.retained).insertView(view, index); + } + + + /** + * Removes the View object at the specified index from this node's + * list of views. + * If this operation causes the list of views to become empty, + * then the descendants of this ViewSpecificGroup node will not be + * rendered. + * + * @param index the index of the View object to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void removeView(int index) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup1")); + ((ViewSpecificGroupRetained)this.retained).removeView(index); + + } + + + /** + * Returns an enumeration of this ViewSpecificGroup node's list + * of views. + * + * @return an Enumeration object containing all the views. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public Enumeration getAllViews() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup2")); + + return ((ViewSpecificGroupRetained)this.retained).getAllViews(); + } + + + /** + * Appends the specified View object to this node's list of views. + * + * @param view the View object to be appended. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public void addView(View view) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup1")); + + ((ViewSpecificGroupRetained)this.retained).addView(view); + } + + + /** + * Returns the number of View objects in this node's list of views. + * If this number is 0, then the list of views is empty and + * the descendants of this ViewSpecificGroup node will not be + * rendered. + * + * @return the number of views in this node's list of views. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + */ + public int numViews() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup2")); + + return ((ViewSpecificGroupRetained)this.retained).numViews(); + } + + + /** + * Retrieves the index of the specified View object in this + * node's list of views. + * + * @param view the View object to be looked up. + * @return the index of the specified View object; + * returns -1 if the object is not in the list. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public int indexOfView(View view) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_READ)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup2")); + + return ((ViewSpecificGroupRetained)this.retained).indexOfView(view); + } + + + /** + * Removes the specified View object from this + * node's list of views. If the specified object is not in the + * list, the list is not modified. + * If this operation causes the list of views to become empty, + * then the descendants of this ViewSpecificGroup node will not be + * rendered. + * + * @param view the View object to be removed. + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeView(View view) { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup1")); + + ((ViewSpecificGroupRetained)this.retained).removeView(view); + } + + + /** + * Removes all View objects from this node's + * list of views. + * Since this method clears the list of views, the descendants of + * this ViewSpecificGroup node will not be rendered. + * + * @exception CapabilityNotSetException if appropriate capability is + * not set and this object is part of live or compiled scene graph + * + * @since Java 3D 1.3 + */ + public void removeAllViews() { + if (isLiveOrCompiled()) + if(!this.getCapability(ALLOW_VIEW_WRITE)) + throw new CapabilityNotSetException(J3dI18N.getString("ViewSpecificGroup1")); + + ((ViewSpecificGroupRetained)this.retained).removeAllViews(); + } + + + /** + * Used to create a new instance of the node. This routine is called + * by <code>cloneTree</code> to duplicate the current node. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @see Node#cloneTree + * @see Node#cloneNode + * @see Node#duplicateNode + * @see NodeComponent#setDuplicateOnCloneTree + */ + public Node cloneNode(boolean forceDuplicate) { + ViewSpecificGroup vsg = new ViewSpecificGroup(); + vsg.duplicateNode(this, forceDuplicate); + return vsg; + } + + + /** + * Copies all ViewSpecificGroup information from + * <code>originalNode</code> into + * the current node. This method is called from the + * <code>cloneNode</code> method which is, in turn, called by the + * <code>cloneTree</code> method.<P> + * + * @param originalNode the original node to duplicate. + * @param forceDuplicate when set to <code>true</code>, causes the + * <code>duplicateOnCloneTree</code> flag to be ignored. When + * <code>false</code>, the value of each node's + * <code>duplicateOnCloneTree</code> variable determines whether + * NodeComponent data is duplicated or copied. + * + * @exception RestrictedAccessException if this object is part of a live + * or compiled scenegraph. + * + * @see Group#cloneNode + * @see Node#duplicateNode + * @see Node#cloneTree + * @see NodeComponent#setDuplicateOnCloneTree + */ + void duplicateAttributes(Node originalNode, boolean forceDuplicate) { + + // TODO: implement this + super.duplicateAttributes(originalNode, forceDuplicate); + + ViewSpecificGroupRetained attr = (ViewSpecificGroupRetained) originalNode.retained; + ViewSpecificGroupRetained rt = (ViewSpecificGroupRetained) retained; + + for (Enumeration e = attr.getAllViews(); e.hasMoreElements(); ) { + rt.addView((View)e.nextElement()); + } + } + +} diff --git a/src/classes/share/javax/media/j3d/ViewSpecificGroupRetained.java b/src/classes/share/javax/media/j3d/ViewSpecificGroupRetained.java new file mode 100644 index 0000000..da3bd8c --- /dev/null +++ b/src/classes/share/javax/media/j3d/ViewSpecificGroupRetained.java @@ -0,0 +1,747 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +/** + * The ViewSpecificGroup node retained object. + */ + +class ViewSpecificGroupRetained extends GroupRetained { + + ArrayList apiViewList = new ArrayList(); + + // Used by leaf objects particularly GAs + // Updated in a MT Safe manner and also used by RenderBin + ArrayList cachedViewList = new ArrayList(); + + // The object that contains the dynamic HashKey - a string type object + // Used in scoping + HashKey tempKey = new HashKey(250); + + // ArrayList of Integer indices + ArrayList parentLists = new ArrayList(); + + + static final int SET_VIEW = 0x1; + static final int ADD_VIEW = 0x2; + static final int REMOVE_VIEW = 0x4; + + // Construct retained object + ViewSpecificGroupRetained() { + this.nodeType = NodeRetained.VIEWSPECIFICGROUP; + viewLists = new ArrayList(); + } + + void addView(View view) { + int i; + Integer mtype = new Integer(ADD_VIEW); + + apiViewList.add(view); + if (source.isLive() && view != null) { + // Gather all affected leaf nodes and send a message to + // RenderingEnv and RenderBin + if (inSharedGroup) { + ArrayList parentList; + for (int k = 0; k < localToVworldKeys.length; k++) { + parentList = (ArrayList)parentLists.get(k); + // If the parentList contains this view or if this is the + // first VSG then .. + if (parentList == null || parentList.contains(view)) { + Object[] objAry = new Object[4]; + ArrayList addVsgList = new ArrayList(); + ArrayList addLeafList = new ArrayList(); + int[] addKeyList = new int[10]; + + HashKey key = localToVworldKeys[k]; + addVsgList.add(this); + addKeyList[0] = k; + objAry[0] = view; + objAry[1] = addVsgList; + objAry[2] = addLeafList; + /* + for (int n = 0; n < addLeafList.size(); n++) { + System.out.println("Shared:n = "+n+" addLeafList = "+addLeafList.get(n)); + } + */ + objAry[3] = super.processViewSpecificInfo(ADD_VIEW, + (HashKey)key, view, + addVsgList, addKeyList, addLeafList); + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.VIEWSPECIFICGROUP_CHANGED; + message.threads = (J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_SOUND| + J3dThread.SOUND_SCHEDULER); + message.universe = universe; + message.args[0] = mtype; + message.args[1] = objAry; + VirtualUniverse.mc.processMessage(message); + } + } + + } + else { + ArrayList parentList = (ArrayList)parentLists.get(0); + + // If the parentList contains this view or if this is the + // first VSG then .. + if (parentList == null || parentList.contains(view)) { + Object[] objAry = new Object[4]; + ArrayList addVsgList = new ArrayList(); + ArrayList addLeafList = new ArrayList(); + int[] addKeyList = new int[10]; + + objAry[0] = view; + objAry[1] = addVsgList; + objAry[2] = addLeafList; + + addVsgList.add(this); + addKeyList[0] = 0; + tempKey.reset(); + objAry[3] = super.processViewSpecificInfo(ADD_VIEW, + tempKey, view, + addVsgList, addKeyList, addLeafList); + + /* + for (int n = 0; n < addLeafList.size(); n++) { + System.out.println("n = "+n+" addLeafList = "+addLeafList.get(n)); + } + */ + + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.VIEWSPECIFICGROUP_CHANGED; + message.threads = (J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_SOUND| + J3dThread.SOUND_SCHEDULER); + message.universe = universe; + message.args[0] = mtype; + message.args[1] = objAry; + VirtualUniverse.mc.processMessage(message); + } + } + + + } + } + + + void setView(View view, int index) { + int i; + + View oldView = (View)apiViewList.get(index); + Integer mtype = new Integer(SET_VIEW); + + if (oldView == view) + return; + + apiViewList.set(index, view); + if (source.isLive()) { + // Gather all affected leaf nodes and send a message to + // RenderingEnv and RenderBin + if (inSharedGroup) { + ArrayList parentList; + for (int k = 0; k < localToVworldKeys.length; k++) { + parentList = (ArrayList)parentLists.get(k); + Object[] objAry = new Object[8]; + ArrayList addVsgList = new ArrayList(); + ArrayList removeVsgList = new ArrayList(); + ArrayList addLeafList = new ArrayList(); + ArrayList removeLeafList = new ArrayList(); + int[] addKeyList = new int[10]; + int[] removeKeyList = new int[10]; + + objAry[0] = view; + objAry[1] = addVsgList ; + objAry[2] = addLeafList; + objAry[4] = oldView; + objAry[5] = removeVsgList; + objAry[6] = removeLeafList; + + HashKey key = localToVworldKeys[k]; + if (oldView != null && (parentList == null || parentList.contains(oldView))) { + removeVsgList.add(this); + removeKeyList[0] = k; + objAry[7] = super.processViewSpecificInfo(REMOVE_VIEW, (HashKey)key, + oldView, removeVsgList, removeKeyList, removeLeafList); + } + + if (view != null && (parentList == null || parentList.contains(view))) { + addVsgList.add(this); + addKeyList[0] = k; + objAry[3] = super.processViewSpecificInfo(ADD_VIEW, (HashKey)key, + view, addVsgList, addKeyList, addLeafList); + } + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.VIEWSPECIFICGROUP_CHANGED; + message.threads = (J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_SOUND| + J3dThread.SOUND_SCHEDULER); + message.universe = universe; + message.args[0] = mtype; + message.args[1] = objAry; + VirtualUniverse.mc.processMessage(message); + } + + } + else { + ArrayList parentList = (ArrayList)parentLists.get(0); + Object[] objAry = new Object[8]; + ArrayList addVsgList = new ArrayList(); + ArrayList removeVsgList = new ArrayList(); + ArrayList addLeafList = new ArrayList(); + ArrayList removeLeafList = new ArrayList(); + int[] addKeyList = new int[10]; + int[] removeKeyList = new int[10]; + + objAry[0] = view; + objAry[1] = addVsgList ; + objAry[2] = addLeafList; + objAry[4] = oldView; + objAry[5] = removeVsgList; + objAry[6] = removeLeafList; + + + + tempKey.reset(); + if (oldView != null && (parentList == null || parentList.contains(oldView))) { + removeVsgList.add(this); + removeKeyList[0] = 0; + objAry[7] = super.processViewSpecificInfo(REMOVE_VIEW, (HashKey)tempKey, + oldView, removeVsgList, removeKeyList, removeLeafList); + } + if (view != null && (parentList == null || parentList.contains(view))) { + tempKey.reset(); + addVsgList.add(this); + addKeyList[0] = 0; + objAry[3] = super.processViewSpecificInfo(ADD_VIEW, (HashKey)tempKey, + view, addVsgList, addKeyList, addLeafList); + } + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.VIEWSPECIFICGROUP_CHANGED; + message.threads = (J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_SOUND| + J3dThread.SOUND_SCHEDULER); + + message.universe = universe; + message.args[0] = mtype; + message.args[1] = objAry; + VirtualUniverse.mc.processMessage(message); + } + + + } + + } + + int[] processViewSpecificInfo(int mode, HashKey key, View v, ArrayList vsgList, int[] keyList, ArrayList leaflist) { + int hkIndex = 0; + Integer hashInt = null; + int[] newKeyList = null; + // Get the intersection of the viewList with this view, + + if (source.isLive()) { + if (inSharedGroup) { + hkIndex = key.equals(localToVworldKeys, 0, localToVworldKeys.length); + } + + if (mode == ADD_VIEW) { + ArrayList parentList = (ArrayList)parentLists.get(hkIndex); + parentList.add(v); + } + else if (mode == REMOVE_VIEW) { + ArrayList parentList = (ArrayList)parentLists.get(hkIndex); + parentList.remove(v); + } + if(apiViewList.contains(v)) { + // System.out.println("processViewSpecificInfo, this = "+this+" key = "+key); + vsgList.add(this); + if (keyList.length< vsgList.size()) { + // System.out.println("====> allocating new array"); + newKeyList = new int[keyList.length+20]; + System.arraycopy(keyList, 0, newKeyList, 0, keyList.length); + keyList = newKeyList; + } + if (mode == ADD_VIEW) { + if (inSharedGroup) { + keyList[vsgList.size()-1] = hkIndex; + + } + else { + keyList[vsgList.size()-1] = 0; + } + } + else if (mode == REMOVE_VIEW) { + if (inSharedGroup) { + keyList[vsgList.size()-1] = hkIndex; + } + else { + keyList[vsgList.size()-1] = 0; + } + } + return super.processViewSpecificInfo(mode, key, v, vsgList, keyList, leaflist); + } + } + return keyList; + } + + View getView(int index) { + return (View)apiViewList.get(index); + } + + void insertView(View view, int index) { + int i; + Integer mtype = new Integer(ADD_VIEW); + + apiViewList.add(index, view); + if (source.isLive() && view != null) { + // Gather all affected leaf nodes and send a message to + // RenderingEnv and RenderBin + if (inSharedGroup) { + ArrayList parentList; + for (int k = 0; k < localToVworldKeys.length; k++) { + parentList = (ArrayList)parentLists.get(k); + // If the parentList contains this view or if this is the + // first VSG then .. + if (parentList == null || parentList.contains(view)) { + Object[] objAry = new Object[4]; + ArrayList addVsgList = new ArrayList(); + ArrayList addLeafList = new ArrayList(); + int[] addKeyList = new int[10]; + + HashKey key = localToVworldKeys[k]; + addVsgList.add(this); + addKeyList[0] = k; + objAry[0] = view; + objAry[1] = addVsgList; + objAry[2] = addLeafList; + /* + for (int n = 0; n < addLeafList.size(); n++) { + System.out.println("Shared:n = "+n+" addLeafList = "+addLeafList.get(n)); + } + */ + objAry[3] = super.processViewSpecificInfo(ADD_VIEW, + (HashKey)key, view, + addVsgList, addKeyList, addLeafList); + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.VIEWSPECIFICGROUP_CHANGED; + message.threads = (J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_SOUND| + J3dThread.SOUND_SCHEDULER); + message.universe = universe; + message.args[0] = mtype; + message.args[1] = objAry; + VirtualUniverse.mc.processMessage(message); + } + } + + } + else { + ArrayList parentList = (ArrayList)parentLists.get(0); + + // If the parentList contains this view or if this is the + // first VSG then .. + if (parentList == null || parentList.contains(view)) { + Object[] objAry = new Object[4]; + ArrayList addVsgList = new ArrayList(); + ArrayList addLeafList = new ArrayList(); + int[] addKeyList = new int[10]; + + objAry[0] = view; + objAry[1] = addVsgList; + objAry[2] = addLeafList; + + addVsgList.add(this); + addKeyList[0] = 0; + tempKey.reset(); + objAry[3] = super.processViewSpecificInfo(ADD_VIEW, + tempKey, view, + addVsgList, addKeyList, addLeafList); + + /* + for (int n = 0; n < addLeafList.size(); n++) { + System.out.println("n = "+n+" addLeafList = "+addLeafList.get(n)); + } + */ + + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.VIEWSPECIFICGROUP_CHANGED; + message.threads = (J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_SOUND| + J3dThread.SOUND_SCHEDULER); + message.universe = universe; + message.args[0] = mtype; + message.args[1] = objAry; + VirtualUniverse.mc.processMessage(message); + } + } + + + } + } + + void removeView(int index) { + int i; + View v = (View) apiViewList.remove(index); + if (source.isLive() && v != null) { + // Gather all affected leaf nodes and send a message to + // RenderingEnv and RenderBin + if (inSharedGroup) { + ArrayList parentList; + for (int k = 0; k < localToVworldKeys.length; k++) { + parentList = (ArrayList)parentLists.get(k); + // If the parentList contains this view or if this is the + // first VSG then .. + if (parentList == null || parentList.contains(v)) { + Object[] objAry = new Object[4]; + ArrayList removeVsgList = new ArrayList(); + ArrayList removeLeafList = new ArrayList(); + int[] removeKeyList = new int[10]; + + objAry[0] = v; + objAry[1] = removeVsgList; + objAry[2] = removeLeafList; + HashKey key = localToVworldKeys[k]; + + removeVsgList.add(this); + removeKeyList[0] = k; + + objAry[3] = super.processViewSpecificInfo(REMOVE_VIEW, + (HashKey)key,v, + removeVsgList, removeKeyList, removeLeafList); + + + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.VIEWSPECIFICGROUP_CHANGED; + message.threads = (J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_SOUND| + J3dThread.SOUND_SCHEDULER); + message.universe = universe; + message.args[0] = new Integer(REMOVE_VIEW); + message.args[1] = objAry; + VirtualUniverse.mc.processMessage(message); + } + } + + } + else { + ArrayList parentList = (ArrayList)parentLists.get(0); + + // If the parentList contains this view or if this is the + // first VSG then .. + if (parentList == null || parentList.contains(v)) { + Object[] objAry = new Object[4]; + ArrayList removeVsgList = new ArrayList(); + ArrayList removeLeafList = new ArrayList(); + int[] removeKeyList = new int[10]; + + objAry[0] = v; + objAry[1] = removeVsgList; + objAry[2] = removeLeafList; + removeVsgList.add(this); + removeKeyList[0] = 0; + + tempKey.reset(); + objAry[3] = super.processViewSpecificInfo(REMOVE_VIEW, + (HashKey)tempKey, v, + removeVsgList, removeKeyList, removeLeafList); + + /* + for (int n = 0; n < removeKeyList.size(); n++) { + System.out.println("n = "+n+" keyValue = "+removeKeyList.get(n)); + } + */ + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.VIEWSPECIFICGROUP_CHANGED; + message.threads = (J3dThread.UPDATE_RENDERING_ENVIRONMENT| + J3dThread.UPDATE_RENDER | + J3dThread.UPDATE_SOUND| + J3dThread.SOUND_SCHEDULER); + message.universe = universe; + message.args[0] = new Integer(REMOVE_VIEW); + message.args[1] = objAry; + VirtualUniverse.mc.processMessage(message); + } + } + + } + } + + Enumeration getAllViews() { + Vector viewList = new Vector(); + for (int i = 0; i < apiViewList.size(); i++) { + viewList.add(apiViewList.get(i)); + } + return viewList.elements(); + } + + int numViews() { + return apiViewList.size(); + } + + int indexOfView(View view) { + return apiViewList.indexOf(view); + } + + void removeView(View view) { + removeView(apiViewList.indexOf(view)); + } + + void removeAllViews() { + int size = apiViewList.size(); + for (int i = 0; i < size; i++) { + removeView(0); + } + } + + void compile(CompileState compState) { + super.compile(compState); + + // don't remove this group node + mergeFlag = SceneGraphObjectRetained.DONT_MERGE; + + // TODO: complete this + } + + void setLive(SetLiveState s) { + if (inBackgroundGroup) { + throw new + IllegalSceneGraphException(J3dI18N.getString("ViewSpecificGroup3")); + } + + s.inViewSpecificGroup = true; + ArrayList savedViewList = s.viewLists; + if (s.changedViewGroup == null) { + s.changedViewGroup = new ArrayList(); + s.changedViewList = new ArrayList(); + s.keyList = new int[10]; + s.viewScopedNodeList = new ArrayList(); + s.scopedNodesViewList = new ArrayList(); + } + super.setLive(s); + s.viewLists = savedViewList; + + } + + void clearLive(SetLiveState s) { + ArrayList savedViewList = s.viewLists; + if (s.changedViewGroup == null) { + s.changedViewGroup = new ArrayList(); + s.changedViewList = new ArrayList(); + s.keyList = new int[10]; + s.viewScopedNodeList = new ArrayList(); + s.scopedNodesViewList = new ArrayList(); + } + // TODO: This is a hack since removeNodeData is called before + // children are clearLives + int[] tempIndex = null; + // Don't keep the indices if everything will be cleared + if (inSharedGroup && (s.keys.length != localToVworld.length)) { + tempIndex = new int[s.keys.length]; + for (int i = 0; i < s.keys.length; i++) { + tempIndex[i] = s.keys[i].equals(localToVworldKeys, 0, localToVworldKeys.length); + } + } + super.clearLive(s); + // Do this after children clearlive since part of the viewLists may get cleared + // during removeNodeData + + // If the last SharedGroup is being cleared + if((!inSharedGroup) || (localToVworld == null)) { + viewLists.clear(); + } + else { + // Must be in reverse, to preserve right indexing. + for (int i = tempIndex.length-1; i >= 0 ; i--) { + if(tempIndex[i] >= 0) { + viewLists.remove(tempIndex[i]); + } + } + } + s.viewLists = savedViewList; + } + + + void removeNodeData(SetLiveState s) { + if((!inSharedGroup) || (s.keys.length == localToVworld.length)) { + s.changedViewGroup.add(this); + // Remove everything .. + int size = s.changedViewGroup.size(); + if (s.keyList.length < size) { + int[] newKeyList = new int[s.keyList.length+20]; + System.arraycopy(s.keyList, 0, newKeyList, 0, s.keyList.length); + s.keyList = newKeyList; + // System.out.println("====> RemovedNodeData: Allocating Non-shared"); + } + s.keyList[size -1] = -1; + parentLists.clear(); + } + // A path of the shared group is removed + else { + int i, index; + int size = s.changedViewGroup.size(); + if (s.keyList.length < size+1+s.keys.length) { + int[] newKeyList = new int[s.keyList.length+s.keys.length+20]; + System.arraycopy(s.keyList, 0, newKeyList, 0, s.keyList.length); + s.keyList = newKeyList; + // System.out.println("====> RemovedNodeData: Allocating Shared"); + } + // Must be in reverse, to preserve right indexing. + for (i = s.keys.length-1; i >= 0; i--) { + index = s.keys[i].equals(localToVworldKeys, 0, localToVworldKeys.length); + if(index >= 0) { + s.changedViewGroup.add(this); + s.keyList[s.changedViewGroup.size() -1] = index; + parentLists.remove(index); + } + } + } + s.viewLists =viewLists; + super.removeNodeData(s); + } + + void updateCachedInformation(int component, View view, int index ) { + ArrayList list = (ArrayList) cachedViewList.get(index); + + /* + System.out.println("updateCachedInformation v = "+this+" index = "+index+" list = "+list+" cachedViewList.size() = "+cachedViewList.size()); + for (int k = 0; k < cachedViewList.size(); k++) { + System.out.println("v = "+this+" k = "+k+" v.cachedViewList.get(k) = "+cachedViewList.get(k)); + } + */ + if ((component & ADD_VIEW) != 0) { + list.add(view); + } + else if ((component & REMOVE_VIEW) != 0) { + list.remove(view); + } + /* + System.out.println("After updateCachedInformation v = "+this+" index = "+index+" list = "+list+" cachedViewList.size() = "+cachedViewList.size()); + for (int k = 0; k < cachedViewList.size(); k++) { + System.out.println("v = "+this+" k = "+k+" v.cachedViewList.get(k) = "+cachedViewList.get(k)); + } + */ + + } + + void setNodeData(SetLiveState s) { + super.setNodeData(s); + if (!inSharedGroup) { + int size = s.changedViewGroup.size(); + if (s.keyList.length < size+1) { + int[] newKeyList = new int[s.keyList.length+20]; + System.arraycopy(s.keyList, 0, newKeyList, 0, s.keyList.length); + s.keyList = newKeyList; + // System.out.println("====> setNodeData: Allocating Non-shared"); + } + setAuxData(s, 0, 0); + } else { + // For inSharedGroup case. + int j, hkIndex; + + int size = s.changedViewGroup.size(); + if (s.keyList.length < size+1+s.keys.length) { + int[] newKeyList = new int[s.keyList.length+s.keys.length+20]; + System.arraycopy(s.keyList, 0, newKeyList, 0, s.keyList.length); + s.keyList = newKeyList; + // System.out.println("====> setNodeData: Allocating Shared"); + } + + for(j=0; j<s.keys.length; j++) { + hkIndex = s.keys[j].equals(localToVworldKeys, 0, + localToVworldKeys.length); + + if(hkIndex >= 0) { + setAuxData(s, j, hkIndex); + + } else { + System.out.println("Can't Find matching hashKey in setNodeData."); + System.out.println("We're in TROUBLE!!!"); + } + } + } + // Make the VSG's viewLists as the relavant one for its children + s.viewLists = viewLists; + + } + + void setAuxData(SetLiveState s, int index, int hkIndex) { + ArrayList vl; + ArrayList parentList = null; + int size = apiViewList.size(); + if (s.viewLists != null) { + // System.out.println("=====> VSG: = "+this+" hkIndex = "+hkIndex+" s.viewLists = "+s.viewLists); + parentList = (ArrayList) s.viewLists.get(hkIndex); + if (parentList != null) { + vl = new ArrayList(); + for (int i = 0; i < size; i++) { + Object obj1 = apiViewList.get(i); + // Get the intersection of the parentlist and this vsg's api list + for (int j = 0; j < parentList.size(); j++) { + if (obj1 == parentList.get(j)) { + vl.add(obj1); + break; + } + } + } + } + else { + vl = new ArrayList(); + // Only include the non null ones in the apiViewList + for (int i = 0; i < size; i++) { + Object obj = apiViewList.get(i); + if (obj != null) { + vl.add(obj); + } + } + } + } + else { + vl = new ArrayList(); + // Only include the non null ones in the apiViewList + for (int i = 0; i < size; i++) { + Object obj = apiViewList.get(i); + if (obj != null) { + vl.add(obj); + } + } + } + if (parentList != null) { + parentLists.add(hkIndex, parentList.clone()); + } + else { + parentLists.add(hkIndex, null); + } + + viewLists.add(hkIndex,vl); + s.changedViewGroup.add(this); + s.changedViewList.add(vl); + if (localToVworldKeys != null) { + s.keyList[s.changedViewGroup.size() -1] = hkIndex; + } + else { + s.keyList[s.changedViewGroup.size() -1] = 0; + } + + + + } + +} diff --git a/src/classes/share/javax/media/j3d/VirtualUniverse.java b/src/classes/share/javax/media/j3d/VirtualUniverse.java new file mode 100644 index 0000000..9ac8940 --- /dev/null +++ b/src/classes/share/javax/media/j3d/VirtualUniverse.java @@ -0,0 +1,910 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Map; + +/** + * A VirtualUniverse object is the top-level container for all scene + * graphs. A virtual universe consists of a set of Locale objects, + * each of which has a high-resolution position within the virtual + * universe. An application or applet may have more than one + * VirtualUniverse objects, but many applications will need only one. + * Virtual universes are separate entities in that no node object may + * exist in more than one virtual universe at any one time. Likewise, + * the objects in one virtual universe are not visible in, nor do they + * interact with objects in, any other virtual universe. + * <p> + * A VirtualUniverse object defines methods to enumerate its Locale + * objects and to remove them from the virtual universe. + * + * @see Locale + */ + +public class VirtualUniverse extends Object { + // NOTE TO DEVELOPERS: + // + // Developers who modify Java 3D in any way are required to modify + // the auxiliary implementation vendor string in VersionInfo.java. + // See that file for instructions. + + // The global MasterControl object. There is only one of these + // for all of Java 3D. + static MasterControl mc = null; + + // The lock to acquire before traversing the scene graph + Object sceneGraphLock = new Object(); + Object behaviorLock = new Object(); + + // A list of locales that are contained within this universe + Vector listOfLocales = new Vector(); + + // The list of view platforms. + ArrayList viewPlatforms = new ArrayList(); + + + // The cached list of vp's + Object[] viewPlatformList = null; + + // A flag that indicates that the list of view platforms has changed + boolean vpChanged = false; + + // The list of backgrounds + Vector backgrounds = new Vector(); + + // The list of clips + Vector clips = new Vector(); + + // The list of sounds + Vector sounds = new Vector(); + + // The list of soundscapes + Vector soundscapes = new Vector(); + + // The Behavior Scheduler Thread for this Virtual Universe. + BehaviorScheduler behaviorScheduler = null; + + + // The geometry structure for this Universe + GeometryStructure geometryStructure = null; + + // The transform structure for this Universe + TransformStructure transformStructure = null; + + // The behavior structure for this Universe + BehaviorStructure behaviorStructure = null; + + // The sound structure for this Universe + SoundStructure soundStructure = null; + + // The rendering attributes structure for this Universe + RenderingEnvironmentStructure renderingEnvironmentStructure = null; + + // Reference count of users of the RenderingEnvironmentStructure + int renderingEnvironmentStructureRefCount = 0; + + // This is a global counter for node id's. + long nodeIdCount = 0; + + // This is a global counter for view id's. + int viewIdCount = 0; + + // This is a vector of free nodeid's + Vector nodeIdFreeList = new Vector(); + + // This is a vector of free viewid's + ArrayList viewIdFreeList = new ArrayList(); + + // The number of nodes in this universe + int numNodes = 0; + + // The State object used when branch graphs are added + SetLiveState setLiveState; + + // This is an array of references to objects that need their mirror + // copies updated. It is updated by the traverser and emptied by + // the view thread. + ObjectUpdate[] updateObjects = new ObjectUpdate[16]; + + // The number of valid entries in updateObjects + int updateObjectsLen = 0; + + // A list of all mirror geometry object that are dirty + ArrayList dirtyGeomList = new ArrayList(); + + // The current primary view for this universe + View currentView; + + // A flag to indicate that we are in a behavior routine + boolean inBehavior = false; + + // Flags to indicate if events need to be delivered + boolean enableComponent = false; + boolean enableFocus = false; + boolean enableKey = false; + boolean enableMouse = false; + boolean enableMouseMotion = false; + + // Keep track of how many active View use this universe + int activeViewCount = 0; + + // Root ThreadGroup for creating Java 3D threads + static ThreadGroup rootThreadGroup; + + // Lock for MasterControl Object creation + static Object mcLock = new Object(); + + // Properties object for getProperties + private static J3dQueryProps properties = null; + + // Flag to indicate that user thread has to + // stop until MC completely register/unregister View. + View regViewWaiting = null; + View unRegViewWaiting = null; + boolean isSceneGraphLock = false; + + private Object waitLock = new Object(); + + /** + * Constructs a new VirtualUniverse. + */ + public VirtualUniverse() { + setLiveState = new SetLiveState(this); + initMCStructure(); + } + + + void initMCStructure() { + if (geometryStructure != null) { + geometryStructure.cleanup(); + } + geometryStructure = new GeometryStructure(this); + if (transformStructure != null) { + transformStructure.cleanup(); + } + transformStructure = new TransformStructure(this); + if (behaviorStructure != null) { + behaviorStructure.cleanup(); + } + behaviorStructure = new BehaviorStructure(this); + if (soundStructure != null) { + soundStructure.cleanup(); + } + soundStructure = new SoundStructure(this); + if (renderingEnvironmentStructure != null) { + renderingEnvironmentStructure.cleanup(); + } + renderingEnvironmentStructure = new + RenderingEnvironmentStructure(this); + + } + + static void createMC() { + synchronized (mcLock) { + if (mc == null) { + mc = new MasterControl(); + } + } + } + + static void destroyMC() { + synchronized (mcLock) { + mc = null; + } + } + + /** + * Initialize the native interface and anything else that needs + * to be initialized. + */ + static void loadLibraries() { + // No need to do anything. The act of calling any method in this + // class is sufficient to cause the static MasterControl object + // to be created which, in turn, loads the native libraries. + } + + static { + if(J3dBuildInfo.isDebug) { + System.out.println("Initializing Java 3D runtime system:"); + System.out.println(" version = " + VersionInfo.getVersion()); + System.out.println(" vendor = " + VersionInfo.getVendor()); + System.out.println(" specification.version = " + + VersionInfo.getSpecificationVersion()); + System.out.println(" specification.vendor = " + + VersionInfo.getSpecificationVendor()); + } + + MasterControl.loadLibraries(); + createMC(); + + if(J3dBuildInfo.isDebug) { + System.out.println("Java 3D system initialized"); + System.out.println(); + } + } + + /** + * Adds a locale at the end of list of locales + * @param locale the locale to be added + */ + void addLocale(Locale locale) { + listOfLocales.addElement(locale); + } + + /** + * Removes a Locale and its associates branch graphs from this + * universe. All branch graphs within the specified Locale are + * detached, regardless of whether their ALLOW_DETACH capability + * bits are set. The Locale is then marked as being dead: no + * branch graphs may subsequently be attached. + * + * @param locale the Locale to be removed. + * + * @exception IllegalArgumentException if the specified Locale is not + * attached to this VirtualUniverse. + * + * @since Java 3D 1.2 + */ + public void removeLocale(Locale locale) { + if (locale.getVirtualUniverse() != this) { + throw new IllegalArgumentException(J3dI18N.getString("VirtualUniverse0")); + } + + listOfLocales.removeElement(locale); + locale.removeFromUniverse(); + if (isEmpty()) { + VirtualUniverse.mc.postRequest(MasterControl.EMPTY_UNIVERSE, + this); + } + setLiveState.reset(null); + } + + + /** + * Removes all Locales and their associates branch graphs from + * this universe. All branch graphs within each Locale are + * detached, regardless of whether their ALLOW_DETACH capability + * bits are set. Each Locale is then marked as being dead: no + * branch graphs may subsequently be attached. This method + * should be called by applications and applets to allow + * Java 3D to cleanup its resources. + * + * @since Java 3D 1.2 + */ + public void removeAllLocales() { + // NOTE: this is safe because Locale.removeFromUniverse does not + // remove the Locale from the listOfLocales + int i; + + + for (i = listOfLocales.size()-1; i > 0; i--) { + ((Locale)listOfLocales.get(i)).removeFromUniverse(); + } + + if (i >= 0) { + // We have to clear() the listOfLocales first before + // invoke the last removeFromUniverse() so that isEmpty() + // (call from View.deactivate() ) will return true and + // threads can destroy from MC. + Locale loc = (Locale) listOfLocales.get(0); + listOfLocales.clear(); + loc.removeFromUniverse(); + } + VirtualUniverse.mc.postRequest(MasterControl.EMPTY_UNIVERSE, + this); + + setLiveState.reset(null); + } + + + /** + * Returns the enumeration object of all locales in this virtual universe. + * @return the enumeration object + */ + public Enumeration getAllLocales() { + return this.listOfLocales.elements(); + } + + /** + * Returns the number of locales. + * @return the count of locales + */ + public int numLocales() { + return this.listOfLocales.size(); + } + + + /** + * Sets the priority of all Java 3D threads to the specified + * value. The default value is the priority of the thread that + * started Java 3D. + * + * @param priority the new thread priority + * + * @exception IllegalArgumentException if the priority is not in + * the range MIN_PRIORITY to MAX_PRIORITY + * + * @exception SecurityException if the priority is greater than + * that of the calling thread + * + * @since Java 3D 1.2 + */ + public static void setJ3DThreadPriority(int priority) { + if (priority > Thread.MAX_PRIORITY) { + priority = Thread.MAX_PRIORITY; + } else if (priority < Thread.MIN_PRIORITY) { + priority = Thread.MIN_PRIORITY; + } + VirtualUniverse.mc.setThreadPriority(priority); + } + + + /** + * Retrieves that priority of Java 3D's threads. + * + * @return the current priority of Java 3D's threads + * + * @since Java 3D 1.2 + */ + public static int getJ3DThreadPriority() { + return VirtualUniverse.mc.getThreadPriority(); + } + + + /** + * Returns a read-only Map object containing key-value pairs that + * define various global properties for Java 3D. All of the keys + * are String objects. The values are key-specific, but most will + * be String objects. + * + * <p> + * The set of global Java 3D properties always includes values for + * the following keys: + * + * <p> + * <ul> + * <table BORDER=1 CELLSPACING=1 CELLPADDING=1> + * <tr> + * <td><b>Key (String)</b></td> + * <td><b>Value Type</b></td> + * </tr> + * <tr> + * <td><code>j3d.version</code></td> + * <td>String</td> + * </tr> + * <tr> + * <td><code>j3d.vendor</code></td> + * <td>String</td> + * </tr> + * <tr> + * <td><code>j3d.specification.version</code></td> + * <td>String</td> + * </tr> + * <tr> + * <td><code>j3d.specification.vendor</code></td> + * <td>String</td> + * </tr> + * <tr> + * <td><code>j3d.renderer</code></td> + * <td>String</td> + * </tr> + * </table> + * </ul> + * + * <p> + * The descriptions of the values returned for each key are as follows: + * + * <p> + * <ul> + * + * <li> + * <code>j3d.version</code> + * <ul> + * A String that defines the Java 3D implementation version. + * The portion of the implementation version string before the first + * space must adhere to one of the the following three formats + * (anything after the first space is an optional free-form addendum + * to the version): + * <ul> + * <i>x</i>.<i>y</i>.<i>z</i><br> + * <i>x</i>.<i>y</i>.<i>z</i>_<i>p</i><br> + * <i>x</i>.<i>y</i>.<i>z</i>-<i>ssss</i><br> + * </ul> + * where: + * <ul> + * <i>x</i> is the major version number<br> + * <i>y</i> is the minor version number<br> + * <i>z</i> is the sub-minor version number<br> + * <i>p</i> is the patch revision number <br> + * <i>ssss</i> is a string, identifying a non-release build + * (e.g., beta1, build47, rc1, etc.). It may only + * contain letters, numbers, periods, dashes, or + * underscores. + * </ul> + * </ul> + * </li> + * <p> + * + * <li> + * <code>j3d.vendor</code> + * <ul> + * String that specifies the Java 3D implementation vendor. + * </ul> + * </li> + * <p> + * + * <li> + * <code>j3d.specification.version</code> + * <ul> + * A String that defines the Java 3D specification version. + * This string must be of the following form: + * <ul> + * <i>x</i>.<i>y</i> + * </ul> + * where: + * <ul> + * <i>x</i> is the major version number<br> + * <i>y</i> is the minor version number<br> + * </ul> + * No other characters are allowed in the specification version string. + * </ul> + * </li> + * <p> + * + * <li> + * <code>j3d.specification.vendor</code> + * <ul> + * String that specifies the Java 3D specification vendor. + * </ul> + * </li> + * <p> + * + * <li> + * <code>j3d.renderer</code> + * <ul> + * String that specifies the Java 3D rendering library. This could + * be one of: "OpenGL" or "DirectX". + * </ul> + * </li> + * <p> + * + * </ul> + * + * @return the global Java 3D properties + * + * @since Java 3D 1.3 + */ + public static final Map getProperties() { + if (properties == null) { + // Create lists of keys and values + ArrayList keys = new ArrayList(); + ArrayList values = new ArrayList(); + + // Implementation version string is obtained from the + // ImplementationVersion class. + keys.add("j3d.version"); + values.add(VersionInfo.getVersion()); + + keys.add("j3d.vendor"); + values.add(VersionInfo.getVendor()); + + keys.add("j3d.specification.version"); + values.add(VersionInfo.getSpecificationVersion()); + + keys.add("j3d.specification.vendor"); + values.add(VersionInfo.getSpecificationVendor()); + + keys.add("j3d.renderer"); + values.add(mc.isD3D() ? "DirectX" : "OpenGL"); + + // Now Create read-only properties object + properties = + new J3dQueryProps((String[]) keys.toArray(new String[0]), + values.toArray()); + } + return properties; + } + + + /** + * This returns the next available nodeId as a string. + */ + // TODO: reuse of id's imply a slight collision problem in the + // render queue's. + // BUG 4181362 + String getNodeId() { + String str; + + if (nodeIdFreeList.size() == 0) { + str = Long.toString(nodeIdCount); + nodeIdCount++; + } else { + str = (String) nodeIdFreeList.lastElement(); + nodeIdFreeList.removeElement(str); + } + return(str); + } + + /** + * This returns the next available viewId + */ + Integer getViewId() { + Integer id; + int size; + + synchronized (viewIdFreeList) { + size = viewIdFreeList.size(); + if (size == 0) { + id = new Integer(viewIdCount++); + } else { + id = (Integer) viewIdFreeList.remove(size-1); + } + } + return(id); + } + + /** + * This returns a viewId to the freelist + */ + void addViewIdToFreeList(Integer viewId) { + synchronized (viewIdFreeList) { + viewIdFreeList.add(viewId); + } + } + + void addViewPlatform(ViewPlatformRetained vp) { + vpChanged = true; + viewPlatforms.add(vp); + } + + void removeViewPlatform(ViewPlatformRetained vp) { + vpChanged = true; + viewPlatforms.remove(viewPlatforms.indexOf(vp)); + } + + synchronized Object[] getViewPlatformList() { + if (vpChanged) { + viewPlatformList = viewPlatforms.toArray(); + vpChanged = false; + } + return viewPlatformList; + } + + void checkForEnableEvents() { + enableComponentEvents(); + if (enableFocus) { + enableFocusEvents(); + } + if (enableKey) { + enableKeyEvents(); + } + if (enableMouse) { + enableMouseEvents(); + } + if (enableMouseMotion) { + enableMouseMotionEvents(); + } + } + + void enableComponentEvents() { + Enumeration cvs; + Canvas3D cv; + ViewPlatformRetained vp; + View views[]; + Object[] vps = getViewPlatformList(); + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.enableComponentEvents(); + } + } + } + } + } + + void disableFocusEvents() { + Enumeration cvs; + Canvas3D cv; + ViewPlatformRetained vp; + View views[]; + Object[] vps = getViewPlatformList(); + enableFocus = false; + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.disableFocusEvents(); + } + } + } + } + + } + + void enableFocusEvents() { + Enumeration cvs; + Canvas3D cv; + ViewPlatformRetained vp; + View views[]; + Object[] vps = getViewPlatformList(); + enableFocus = true; + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.enableFocusEvents(); + } + } + } + } + } + + + void disableKeyEvents() { + Enumeration cvs; + Canvas3D cv; + ViewPlatformRetained vp; + Object[] vps = getViewPlatformList(); + View views[]; + + enableKey = false; + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.disableKeyEvents(); + } + } + } + } + } + + + void enableKeyEvents() { + Enumeration cvs; + Canvas3D cv; + ViewPlatformRetained vp; + Object[] vps = getViewPlatformList(); + View views[]; + + enableKey = true; + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.enableKeyEvents(); + } + } + } + } + } + + + void disableMouseEvents() { + Enumeration cvs; + Canvas3D cv; + View views[]; + ViewPlatformRetained vp; + Object[] vps = getViewPlatformList(); + + enableMouse = false; + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.disableMouseEvents(); + } + } + } + } + } + + void enableMouseEvents() { + Enumeration cvs; + Canvas3D cv; + View views[]; + ViewPlatformRetained vp; + Object[] vps = getViewPlatformList(); + + enableMouse = true; + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.enableMouseEvents(); + } + } + } + } + } + + + void disableMouseMotionEvents() { + Enumeration cvs; + Canvas3D cv; + View views[]; + ViewPlatformRetained vp; + Object[] vps = getViewPlatformList(); + + enableMouseMotion = false; + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.disableMouseMotionEvents(); + } + } + } + } + } + + void enableMouseMotionEvents() { + Enumeration cvs; + Canvas3D cv; + View views[]; + ViewPlatformRetained vp; + Object[] vps = getViewPlatformList(); + + enableMouseMotion = true; + + if (vps != null) { + for (int i=0; i<vps.length; i++) { + vp =(ViewPlatformRetained)vps[i]; + views = vp.getViewList(); + for (int j=views.length-1; j>=0; j--) { + cvs = views[j].getAllCanvas3Ds(); + while(cvs.hasMoreElements()) { + cv = (Canvas3D) cvs.nextElement(); + // offscreen canvas does not have event catcher + if (cv.eventCatcher != null) + cv.eventCatcher.enableMouseMotionEvents(); + } + } + } + } + } + + /** + * Sets the "current" view (during view activation) for this virtual + * universe. + * @param last activated view + */ + final void setCurrentView(View view) { + this.currentView = view; + } + + /** + * Returns the "current" view (the last view activated for this virtual + * universe. + * @return last activated view + */ + final View getCurrentView() { + return this.currentView; + } + + + /** + * Method to return the root thread group. This must be called from + * within a doPrivileged block. + */ + static ThreadGroup getRootThreadGroup() { + return rootThreadGroup; + } + + /** + * return true if all Locales under it don't have branchGroup + * attach to it. + */ + boolean isEmpty() { + Enumeration elm = listOfLocales.elements(); + + while (elm.hasMoreElements()) { + Locale loc = (Locale) elm.nextElement(); + if (!loc.branchGroups.isEmpty()) { + return false; + } + } + return true; + } + + void resetWaitMCFlag() { + synchronized (waitLock) { + regViewWaiting = null; + unRegViewWaiting = null; + isSceneGraphLock = true; + } + } + + void waitForMC() { + synchronized (waitLock) { + if (unRegViewWaiting != null) { + if ((regViewWaiting == null) || + (regViewWaiting != unRegViewWaiting)) { + while (!unRegViewWaiting.doneUnregister) { + MasterControl.threadYield(); + } + unRegViewWaiting.doneUnregister = false; + unRegViewWaiting = null; + } + } + + if (regViewWaiting != null) { + while (!VirtualUniverse.mc.isRegistered(regViewWaiting)) { + MasterControl.threadYield(); + } + regViewWaiting = null; + } + isSceneGraphLock = false; + } + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupAnd.java b/src/classes/share/javax/media/j3d/WakeupAnd.java new file mode 100644 index 0000000..2649077 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupAnd.java @@ -0,0 +1,115 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; + +/** + * Class specifying any number of wakeup conditions ANDed together. + * This WakeupCondition object specifies that Java 3D should awaken + * this Behavior when all of the WakeupCondition's constituent wakeup + * criteria become valid. + * <p> + * Note that a unique WakeupCriterion object must be used + * for each individual element in the array of wakeup criteria. + */ + +public final class WakeupAnd extends WakeupCondition { + + WakeupCriterion conditions[]; + boolean conditionsMet[]; + + /** + * Constructs a new WakeupAnd criterion. + * @param conditions a vector of individual Wakeup conditions + */ + public WakeupAnd(WakeupCriterion conditions[]) { + this.conditions = new WakeupCriterion[conditions.length]; + this.conditionsMet = new boolean[conditions.length]; + + for(int i = 0; i < conditions.length; i++){ + this.conditions[i] = conditions[i]; + // It is false by default when array is initialized. + // this.conditionsMet[i] = false; + } + } + + + /** + * This sets the bit for the given child, then checks if the full condition is met + */ + void setConditionMet(int id, Boolean checkSchedulingRegion) { + conditionsMet[id] = true; + + for (int i=0; i<this.conditionsMet.length; i++) { + if (!conditionsMet[i]) { + return; + } + } + + if (parent == null) { + super.setConditionMet(this.id, checkSchedulingRegion); + } else { + parent.setConditionMet(this.id, checkSchedulingRegion); + } + } + + /** + * This gets called when this condition is added to the AndOr tree. + */ + void buildTree(WakeupCondition parent, int id, BehaviorRetained b) { + super.buildTree(parent, id, b); + + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].buildTree(this, i, b); + } + } + } + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure lists + */ + void cleanTree(BehaviorStructure bs) { + for (int i=0; i<conditions.length; i++) { + conditions[i].cleanTree(bs); + conditionsMet[i] = false; + } + } + + + void reInsertElapseTimeCond() { + super.reInsertElapseTimeCond(); + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].reInsertElapseTimeCond(); + } + } + } + + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure. + */ + void resetTree() { + super.resetTree(); + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].resetTree(); + } + } + } + +} diff --git a/src/classes/share/javax/media/j3d/WakeupAndOfOrs.java b/src/classes/share/javax/media/j3d/WakeupAndOfOrs.java new file mode 100644 index 0000000..44a3316 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupAndOfOrs.java @@ -0,0 +1,116 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; + +/** + * Class specifying any number of OR wakeup conditions ANDed together. + * This WakeupCondition object specifies that Java 3D should awaken this + * Behavior when all of the WakeupCondition's constituent WakeupOr + * conditions become valid. + * <p> + * Note that a unique WakeupCriterion object must be used for each + * individual element in the set of arrays specified by the array of + * WakeupOr objects. + */ + +public final class WakeupAndOfOrs extends WakeupCondition { + + WakeupOr conditions[]; + boolean conditionsMet[]; + + /** + * Constructs a new WakeupAndOfOrs criterion. + * @param conditions a vector of individual Wakeup conditions + */ + public WakeupAndOfOrs(WakeupOr conditions[]) { + this.conditions = new WakeupOr[conditions.length]; + this.conditionsMet = new boolean[conditions.length]; + + for(int i = 0; i < conditions.length; i++){ + this.conditions[i] = conditions[i]; + // conditionsMet is false by default when it is initilized + // this.conditionsMet[i] = false; + } + } + + + /** + * This sets the bit for the given child, then checks if the full condition is met + */ + void setConditionMet(int id, Boolean checkSchedulingRegion) { + conditionsMet[id] = true; + + for (int i=0; i<this.conditionsMet.length; i++) { + if (!conditionsMet[i]) { + return; + } + } + + if (parent == null) { + super.setConditionMet(this.id, checkSchedulingRegion); + } else { + parent.setConditionMet(this.id, checkSchedulingRegion); + } + } + + /** + * This gets called when this condition is added to the AndOr tree. + */ + void buildTree(WakeupCondition parent, int id, BehaviorRetained b) { + + super.buildTree(parent, id, b); + + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].buildTree(this, i, b); + } + } + } + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure lists + */ + void cleanTree(BehaviorStructure bs) { + for (int i=0; i<conditions.length; i++) { + conditions[i].cleanTree(bs); + conditionsMet[i] = false; + } + } + + + void reInsertElapseTimeCond() { + super.reInsertElapseTimeCond(); + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].reInsertElapseTimeCond(); + } + } + } + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure. + */ + void resetTree() { + super.resetTree(); + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].resetTree(); + } + } + } + +} diff --git a/src/classes/share/javax/media/j3d/WakeupCondition.java b/src/classes/share/javax/media/j3d/WakeupCondition.java new file mode 100644 index 0000000..ebe9572 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupCondition.java @@ -0,0 +1,132 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; + +/** + * An abstract class specifying a single wakeup Condition. This class + * is extended by the WakeupCriterion, WakeupOr, WakeupAnd, + * WakeupOrOfAnds, and WakeupAndOfOr classes. A Behavior node hands a + * WakeupCondition object to the behavior scheduler and the behavior + * scheduler hands back an enumeration of that WakeupCondition. + */ + +public abstract class WakeupCondition extends Object { + + static final int ALL_ELEMENTS = 0; + static final int TRIGGERED_ELEMENTS = 1; + + /** + * This boolean indicates whether this condition has been fully met. + */ + boolean conditionMet = false; + + /** + * This points to the parent of this criterion in the AndOr tree + */ + WakeupCondition parent = null; + + /** + * The location of this criterion in the parents array. + */ + int id; + + /** + * The BehaviorRetained node that is using this condition + */ + BehaviorRetained behav = null; + + /** + * This is the allElements enumerator + */ + WakeupCriteriaEnumerator allEnum = null; + + /** + * This is the triggeredElements enumerator + */ + WakeupCriteriaEnumerator trigEnum = null; + + // Use in WakeupIndexedList + int listIdx[][]; + + /** + * Returns an enumeration of all WakeupCriterias in this Condition. + */ + public Enumeration allElements() { + if (allEnum == null) { + allEnum = new WakeupCriteriaEnumerator(this, ALL_ELEMENTS); + } else { + allEnum.reset(this, ALL_ELEMENTS); + } + return allEnum; + } + + /** + * Returns an enumeration of all triggered WakeupCriterias in this Condition. + */ + public Enumeration triggeredElements() { + if (trigEnum == null) { + trigEnum = new WakeupCriteriaEnumerator(this, TRIGGERED_ELEMENTS); + } else { + trigEnum.reset(this, TRIGGERED_ELEMENTS); + } + return trigEnum; + } + + /** + * this sets the conditionMet flag. + */ + void setConditionMet(int id, Boolean checkSchedulingRegion) { + + if (!conditionMet) { + conditionMet = true; + J3dMessage message = VirtualUniverse.mc.getMessage(); + message.type = J3dMessage.COND_MET; + message.threads = J3dThread.UPDATE_BEHAVIOR; + message.universe = behav.universe; + message.args[0] = behav; + message.args[1] = checkSchedulingRegion; + message.args[2] = this; + VirtualUniverse.mc.processMessage(message); + } + } + + /** + * Initialize And/Or tree and add criterion to the BehaviourStructure + */ + void buildTree(WakeupCondition parent, int id, BehaviorRetained b){ + this.parent = parent; + this.behav = b; + this.id = id; + conditionMet = false; + } + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure. + * We can't use behav.universe.behaviorStructure since behav + * may reassign to another universe at this time. + */ + void cleanTree(BehaviorStructure bs) { + conditionMet = false; + } + + void reInsertElapseTimeCond() { + conditionMet = false; + } + + void resetTree() { + conditionMet = false; + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupCriteriaEnumerator.java b/src/classes/share/javax/media/j3d/WakeupCriteriaEnumerator.java new file mode 100644 index 0000000..1b6f1c9 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupCriteriaEnumerator.java @@ -0,0 +1,133 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Enumeration; +import java.util.NoSuchElementException; + +/** + * A class that enumerates all wakeup criteria in a wakeup condition + */ + +class WakeupCriteriaEnumerator implements Enumeration { + + // An array used for the current criteria in this object + WakeupCriterion[] criterion = null; + + // A pointer to the current criteria + int currentIndex = 0; + + // The number of valid criteria in the array, may be less than criterion.length + int length = 0; + + WakeupCriteriaEnumerator(WakeupCondition cond, int type) { + this.reset(cond, type); + } + + void reset(WakeupCondition cond, int type) { + int i, j; + + currentIndex = 0; + length = 0; + if (cond instanceof WakeupCriterion) { + WakeupCriterion crit = (WakeupCriterion)cond; + + if (criterion == null || criterion.length < 1) { + criterion = new WakeupCriterion[1]; + } + if (crit.triggered || type == WakeupCondition.ALL_ELEMENTS) { + criterion[0] = crit; + length = 1; + } + } else { + if (cond instanceof WakeupAnd) { + WakeupAnd condAnd = (WakeupAnd)cond; + + if (criterion == null || criterion.length < condAnd.conditions.length) { + criterion = new WakeupCriterion[condAnd.conditions.length]; + } + for (i=0; i<condAnd.conditions.length; i++) { + if (condAnd.conditions[i].triggered || type == WakeupCondition.ALL_ELEMENTS) { + criterion[length++] = condAnd.conditions[i]; + } + } + } else if (cond instanceof WakeupOr) { + WakeupOr condOr = (WakeupOr)cond; + + if (criterion == null || criterion.length < condOr.conditions.length) { + criterion = new WakeupCriterion[condOr.conditions.length]; + } + for (i=0; i<condOr.conditions.length; i++) { + if (condOr.conditions[i].triggered || type == WakeupCondition.ALL_ELEMENTS) { + criterion[length++] = condOr.conditions[i]; + } + } + } else if (cond instanceof WakeupOrOfAnds) { + WakeupOrOfAnds condOrOfAnds = (WakeupOrOfAnds)cond; + int lengthNeeded = 0; + + for (i=0; i<condOrOfAnds.conditions.length; i++) { + lengthNeeded += condOrOfAnds.conditions[i].conditions.length; + } + + if (criterion == null || criterion.length < lengthNeeded) { + criterion = new WakeupCriterion[lengthNeeded]; + } + + for (i=0; i<condOrOfAnds.conditions.length; i++) { + for (j=0; j<condOrOfAnds.conditions[i].conditions.length; j++) { + if (condOrOfAnds.conditions[i].conditions[j].triggered || + type == WakeupCondition.ALL_ELEMENTS) { + criterion[length++] = condOrOfAnds.conditions[i].conditions[j]; + } + } + } + } else { + WakeupAndOfOrs condAndOfOrs = (WakeupAndOfOrs)cond; + int lengthNeeded = 0; + + for (i=0; i<condAndOfOrs.conditions.length; i++) { + lengthNeeded += condAndOfOrs.conditions[i].conditions.length; + } + + if (criterion == null || criterion.length < lengthNeeded) { + criterion = new WakeupCriterion[lengthNeeded]; + } + + for (i=0; i<condAndOfOrs.conditions.length; i++) { + for (j=0; j<condAndOfOrs.conditions[i].conditions.length; j++) { + if (condAndOfOrs.conditions[i].conditions[j].triggered || + type == WakeupCondition.ALL_ELEMENTS) { + criterion[length++] = condAndOfOrs.conditions[i].conditions[j]; + } + } + } + } + } + } + + public boolean hasMoreElements() { + if (currentIndex == length) { + return false; + } + return true; + } + + public Object nextElement() { + if (currentIndex < length) { + return ((Object)criterion[currentIndex++]); + } else { + throw new NoSuchElementException(J3dI18N.getString("WakeupCriteriaEnumerator0")); + } + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupCriterion.java b/src/classes/share/javax/media/j3d/WakeupCriterion.java new file mode 100644 index 0000000..a19d93d --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupCriterion.java @@ -0,0 +1,108 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * An abstract class specifying a singleton wakeup Criterion. This + * class consists of several subclasses, each of which specifies one + * particular wakeup criterion, that criterion's associated arguments + * (if any), and either a flag that indicates whether this criterion + * caused a Behavior object to awaken or a return field containing the + * information that caused the Behavior object to awaken. + * <p> + * Note that a unique WakeupCriterion object must be used with each instance + * of a Behavior. Sharing wakeup criteria among different instances of + * a Behavior is illegal. Similarly, a unique WakeupCriterion object + * must be used for each individual element in the set of arrays used + * to construct WakeupOr, WakeupAnd, WakeupOrOfAnds, and + * WakeupAndOfOrs objects. + */ + +public abstract class WakeupCriterion extends WakeupCondition { + + /** + * Flag specifying whether this criterion triggered a wakeup + */ + boolean triggered; + + /** + * Returns true if this criterion triggered the wakeup. + * @return true if this criterion triggered the wakeup. + */ + public boolean hasTriggered(){ + return this.triggered; + } + + /** + * Set the Criterion's trigger flag to true. + */ + void setTriggered(){ + this.triggered = true; + if (this.parent == null) { + super.setConditionMet(id, Boolean.TRUE); + } else { + parent.setConditionMet(id, Boolean.TRUE); + } + } + + /** + * Initialize And/Or tree and add criterion to the BehaviourStructure. + * + */ + void buildTree(WakeupCondition parent, int id, BehaviorRetained b) { + super.buildTree(parent, id, b); + triggered = false; + addBehaviorCondition(b.universe.behaviorStructure); + } + + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure. + * We can't use behav.universe.behaviorStructure since behav + * may reassign to another universe at this time. + * + */ + void cleanTree(BehaviorStructure bs){ + conditionMet = false; + removeBehaviorCondition(bs); + }; + + + /** + * This goes through the AndOr tree to reset various criterion. + */ + void resetTree() { + conditionMet = false; + triggered = false; + resetBehaviorCondition(behav.universe.behaviorStructure); + } + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + abstract void addBehaviorCondition(BehaviorStructure bs); + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + abstract void removeBehaviorCondition(BehaviorStructure bs); + + /** + * It is used reset wakeupCondition when it is reused. + */ + abstract void resetBehaviorCondition(BehaviorStructure bs); +} diff --git a/src/classes/share/javax/media/j3d/WakeupIndexedList.java b/src/classes/share/javax/media/j3d/WakeupIndexedList.java new file mode 100644 index 0000000..c033086 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupIndexedList.java @@ -0,0 +1,584 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A strongly type unorder indexed array list. + * All operations remove(WakeupCondition), add(WakeupCondition), + * contains(WakeupCondition) etc. take O(1) time. + * The class is designed to optimize speed. So many reductance + * procedures call and range check as found in ArrayList are + * removed. + * + * <p> + * Use the following code to iterate through an array. + * + * <pre> + * WakeupIndexedList list = new WakeupIndexedList(YourClass.class); + * // add element here + * + * YourClass[] arr = (YourClass []) list.toArray(); + * int size = list.arraySize(); + * for (int i=0; i < size; i++) { + * YourClass obj = arr[i]; + * .... + * } + * </pre> + * + * <p> + * Note: + * <ul> + * 1) The array return is a copied of internal array.<br> + * 2) Don't use arr.length , use list.arraySize();<br> + * 3) No need to do casting for individual element as in + * ArrayList.<br> + * 4) WakeupIndexedList is thread safe.<br> + * 5) Object implement this interface MUST initialize the index + * to -1.<br> + * </ul> + * + * <p> + * Limitation: + * <ul> + * - Same element can't add in two different WakeupIndexedList<br> + * - Order of WakeupCondition is not important<br> + * - Can't modify the clone() copy.<br> + * - Object can't be null<br> + * </ul> + */ + +class WakeupIndexedList implements Cloneable, java.io.Serializable { + + // TODO: set to false when release + final static boolean debug = false; + + /** + * The array buffer into which the elements of the ArrayList are stored. + * The capacity of the ArrayList is the length of this array buffer. + * + * It is non-private to enable compiler do inlining for get(), + * set(), remove() when -O flag turn on. + */ + transient WakeupCondition elementData[]; + + /** + * Clone copy of elementData return by toArray(true); + */ + transient Object cloneData[]; + // size of the above clone objec. + transient int cloneSize; + + transient boolean isDirty = true; + + /** + * Component Type of individual array element entry + */ + Class componentType; + + /** + * The size of the ArrayList (the number of elements it contains). + * + * We make it non-private to enable compiler do inlining for + * getSize() when -O flag turn on. + */ + int size; + + int listType; + + // Current VirtualUniverse using this structure + VirtualUniverse univ; + + /** + * Constructs an empty list with the specified initial capacity. + * and the class data Type + * + * @param initialCapacity the initial capacity of the list. + * @param componentType class type of element in the list. + */ + WakeupIndexedList(int initialCapacity, Class componentType, + int listType, VirtualUniverse univ) { + this.componentType = componentType; + this.elementData = (WakeupCondition[])java.lang.reflect.Array.newInstance( + componentType, initialCapacity); + this.listType = listType; + this.univ = univ; + } + + /** + * Constructs an empty list. + * @param componentType class type of element in the list. + */ + WakeupIndexedList(Class componentType, int listType, + VirtualUniverse univ) { + this(10, componentType, listType, univ); + } + + + /** + * Constructs an empty list with the specified initial capacity. + * + * @param initialCapacity the initial capacity of the list. + */ + WakeupIndexedList(int initialCapacity, int listType, + VirtualUniverse univ) { + this(initialCapacity, WakeupCondition.class, listType, univ); + } + + + /** + * Constructs an empty list. + * componentType default to Object. + */ + WakeupIndexedList(int listType, VirtualUniverse univ) { + this(10, WakeupCondition.class, listType, univ); + } + + + /** + * Initialize all indexes to -1 + */ + final static void init(WakeupCondition obj, int len) { + obj.listIdx = new int[2][len]; + + for (int i=0; i < len; i++) { + obj.listIdx[0][i] = -1; + obj.listIdx[1][i] = -1; + } + } + + /** + * Returns the number of elements in this list. + * + * @return the number of elements in this list. + */ + final int size() { + return size; + } + + /** + * Returns the size of entry use in toArray() number of elements + * in this list. + * + * @return the number of elements in this list. + */ + final int arraySize() { + return cloneSize; + } + + /** + * Tests if this list has no elements. + * + * @return <tt>true</tt> if this list has no elements; + * <tt>false</tt> otherwise. + */ + final boolean isEmpty() { + return size == 0; + } + + /** + * Returns <tt>true</tt> if this list contains the specified element. + * + * @param o element whose presence in this List is to be tested. + */ + synchronized final boolean contains(WakeupCondition o) { + return (o.listIdx[o.behav.getIdxUsed(univ)][listType] >= 0); + } + + + /** + * Searches for the last occurence of the given argument, testing + * for equality using the <tt>equals</tt> method. + * + * @param o an object. + * @return the index of the first occurrence of the argument in this + * list; returns <tt>-1</tt> if the object is not found. + * @see Object#equals(Object) + */ + synchronized final int indexOf(WakeupCondition o) { + return o.listIdx[o.behav.getIdxUsed(univ)][listType]; + } + + /** + * Returns a shallow copy of this <tt>ArrayList</tt> instance. (The + * elements themselves are not copied.) + * + * @return a clone of this <tt>ArrayList</tt> instance. + */ + synchronized protected final Object clone() { + try { + WakeupIndexedList v = (WakeupIndexedList)super.clone(); + v.elementData = (WakeupCondition[])java.lang.reflect.Array.newInstance( + componentType, size); + System.arraycopy(elementData, 0, v.elementData, 0, size); + isDirty = true; // can't use the old cloneData reference + return v; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + } + + + /** + * Returns an array containing all of the elements in this list. + * The size of the array may longer than the actual size. Use + * arraySize() to retrieve the size. + * The array return is a copied of internal array. if copy + * is true. + * + * @return an array containing all of the elements in this list + */ + synchronized final Object[] toArray(boolean copy) { + if (copy) { + if (isDirty) { + if ((cloneData == null) || cloneData.length < size) { + cloneData = (Object[])java.lang.reflect.Array.newInstance( + componentType, size); + } + System.arraycopy(elementData, 0, cloneData, 0, size); + cloneSize = size; + isDirty = false; + } + return cloneData; + } else { + cloneSize = size; + return elementData; + } + + } + + /** + * Returns an array containing all of the elements in this list. + * The size of the array may longer than the actual size. Use + * arraySize() to retrieve the size. + * The array return is a copied of internal array. So another + * thread can continue add/delete the current list. However, + * it should be noticed that two call to toArray() may return + * the same copy. + * + * @return an array containing all of the elements in this list + */ + synchronized final Object[] toArray() { + return toArray(true); + } + + + /** + * Returns an array containing elements starting from startElement + * all of the elements in this list. A new array of exact size + * is always allocated. + * + * @param startElement starting element to copy + * + * @return an array containing elements starting from + * startElement, null if element not found. + * + */ + synchronized final Object[] toArray(WakeupCondition startElement) { + int idx = indexOf(startElement); + if (idx < 0) { + return (Object[])java.lang.reflect.Array.newInstance(componentType, 0); + } + + int s = size - idx; + Object data[] = (Object[])java.lang.reflect.Array.newInstance(componentType, s); + System.arraycopy(elementData, idx, data, 0, s); + return data; + } + + /** + * Trims the capacity of this <tt>ArrayList</tt> instance to be the + * list's current size. An application can use this operation to minimize + * the storage of an <tt>ArrayList</tt> instance. + */ + synchronized final void trimToSize() { + if (elementData.length > size) { + Object oldData[] = elementData; + elementData = (WakeupCondition[])java.lang.reflect.Array.newInstance( + componentType, + size); + System.arraycopy(oldData, 0, elementData, 0, size); + } + } + + + // Positional Access Operations + + /** + * Returns the element at the specified position in this list. + * + * @param index index of element to return. + * @return the element at the specified position in this list. + * @throws IndexOutOfBoundsException if index is out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final Object get(int index) { + return elementData[index]; + } + + /** + * Replaces the element at the specified position in this list with + * the specified element. + * + * @param index index of element to replace. + * @param o element to be stored at the specified position. + * @return the element previously at the specified position. + * @throws IndexOutOfBoundsException if index out of range + * <tt>(index < 0 || index >= size())</tt>. + */ + synchronized final void set(int index, WakeupCondition o) { + + WakeupCondition oldElm = elementData[index]; + if (oldElm != null) { + oldElm.listIdx[oldElm.behav.getIdxUsed(univ)][listType] = -1; + } + elementData[index] = o; + + int univIdx = o.behav.getIdxUsed(univ); + + if (debug) { + if (o.listIdx[univIdx][listType] != -1) { + System.out.println("Illegal use of UnorderIndexedList idx in set " + + o.listIdx[univIdx][listType]); + Thread.dumpStack(); + } + } + + o.listIdx[univIdx][listType] = index; + isDirty = true; + } + + /** + * Appends the specified element to the end of this list. + * It is the user responsible to ensure that the element add is of + * the same type as array componentType. + * + * @param o element to be appended to this list. + */ + synchronized final void add(WakeupCondition o) { + if (elementData.length == size) { + WakeupCondition oldData[] = elementData; + elementData = (WakeupCondition[])java.lang.reflect.Array.newInstance( + componentType, + (size << 1)); + System.arraycopy(oldData, 0, elementData, 0, size); + + } + + int univIdx = o.behav.getIdxUsed(univ); + // System.out.println(this + " add " + o + " univ " + univIdx); + if (debug) { + int idx = o.listIdx[univIdx][listType]; + if (idx >= 0) { + if (elementData[idx] != o) { + System.out.println("Illegal use of UnorderIndexedList idx in add " + idx); + Thread.dumpStack(); + } + } + } + + int idx = size++; + elementData[idx] = o; + o.listIdx[univIdx][listType] = idx; + isDirty = true; + } + + + /** + * Removes the element at the specified position in this list. + * Replace the removed element by the last one. + * + * @param index the index of the element to removed. + * @throws IndexOutOfBoundsException if index out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final void remove(int index) { + WakeupCondition elm = elementData[index]; + int univIdx = elm.behav.getIdxUsed(univ); + + if (debug) { + if (elm.listIdx[univIdx][listType] != index) { + System.out.println("Inconsistent idx in remove, expect " + index + + " actual " + elm.listIdx[univIdx][listType]); + Thread.dumpStack(); + } + } + + elm.listIdx[univIdx][listType] = -1; + size--; + if (index != size) { + elm = elementData[size]; + elm.listIdx[univIdx][listType] = index; + elementData[index] = elm; + } + elementData[size] = null; + isDirty = true; + /* + if ((cloneData != null) && (index < cloneData.length)) { + cloneData[index] = null; // for gc + } + */ + } + + + /** + * Removes the element at the last position in this list. + * @return The element remove + * @throws IndexOutOfBoundsException if array is empty + */ + synchronized final Object removeLastElement() { + WakeupCondition elm = elementData[--size]; + elementData[size] = null; + elm.listIdx[elm.behav.getIdxUsed(univ)][listType] = -1; + isDirty = true; + /* + if ((cloneData != null) && (size < cloneData.length)) { + cloneData[size] = null; // for gc + } + */ + return elm; + } + + + /** + * Removes the specified element in this list. + * Replace the removed element by the last one. + * + * @param o the element to removed. + * @return true if object remove + * @throws IndexOutOfBoundsException if index out of range <tt>(index + * < 0 || index >= size())</tt>. + */ + synchronized final boolean remove(WakeupCondition o) { + int univIdx = o.behav.getIdxUsed(univ); + int idx = o.listIdx[univIdx][listType]; + + // System.out.println(this + " remove " + o + " univ " + univIdx); + + if (idx >= 0) { + // Object in the container + if (debug) { + if (o != elementData[idx]) { + System.out.println(" Illegal use of UnorderIndexedList in remove expect " + o + " actual " + elementData[idx] + " idx = " + idx); + Thread.dumpStack(); + } + } + size--; + if (idx != size) { + WakeupCondition elm = elementData[size]; + elementData[idx] = elm; + elm.listIdx[elm.behav.getIdxUsed(univ)][listType] = idx; + } + elementData[size] = null; + o.listIdx[univIdx][listType] = -1; + isDirty = true; + return true; + } + return false; + } + + + /** + * Removes all of the elements from this list. The list will + * be empty after this call returns. + */ + synchronized final void clear() { + WakeupCondition o; + + for (int i = size-1; i >= 0; i--) { + o = elementData[i]; + o.listIdx[o.behav.getIdxUsed(univ)][listType] = -1; + elementData[i] = null; // Let gc do its work + } + + size = 0; + isDirty = true; + } + + synchronized final void clearMirror() { + if (cloneData != null) { + for (int i = cloneData.length-1; i >= 0; i--) { + // don't set index to -1 since the original + // copy is using this. + cloneData[i] = null; // Let gc do its work + } + } + cloneSize = 0; + isDirty = true; + } + + final Class getComponentType() { + return componentType; + } + + synchronized public String toString() { + StringBuffer sb = new StringBuffer(hashCode() + " Size = " + size + "["); + int len = size-1; + Object obj; + + for (int i=0; i < size; i++) { + obj = elementData[i]; + if (obj != null) { + sb.append(elementData[i].toString()); + } else { + sb.append("NULL"); + } + if (i != len) { + sb.append(", "); + } + } + sb.append("]"); + return sb.toString(); + } + + /** + * Save the state of the <tt>ArrayList</tt> instance to a stream (that + * is, serialize it). + * + * @serialData The length of the array backing the <tt>ArrayList</tt> + * instance is emitted (int), followed by all of its elements + * (each an <tt>Object</tt>) in the proper order. + */ + private synchronized void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException{ + // Write out element count, and any hidden stuff + s.defaultWriteObject(); + + // Write out array length + s.writeInt(elementData.length); + + // Write out all elements in the proper order. + for (int i=0; i<size; i++) + s.writeObject(elementData[i]); + + } + + /** + * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, + * deserialize it). + */ + private synchronized void readObject(java.io.ObjectInputStream s) + throws java.io.IOException, ClassNotFoundException { + // Read in size, and any hidden stuff + s.defaultReadObject(); + + // Read in array length and allocate array + int arrayLength = s.readInt(); + elementData = (WakeupCondition[])java.lang.reflect.Array.newInstance( + componentType, arrayLength); + + // Read in all elements in the proper order. + for (int i=0; i<size; i++) + elementData[i] = (WakeupCondition) s.readObject(); + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnAWTEvent.java b/src/classes/share/javax/media/j3d/WakeupOnAWTEvent.java new file mode 100644 index 0000000..8c81612 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnAWTEvent.java @@ -0,0 +1,145 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.awt.AWTEvent; +import java.awt.event.*; +import java.util.Vector; + +/** + * Class that specifies a Behavior wakeup when a specific AWT event occurs. + */ +public final class WakeupOnAWTEvent extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 1; + + // one of these two variables must be equal to zero + int AwtId; + long EventMask; + long enableAWTEventTS = 0L; + Vector events = new Vector(); + + /** + * Constructs a new WakeupOnAWTEvent object that informs the Java 3D + * scheduler to wake up the specified Behavior object whenever the + * specified AWT event occurs. + * @param AWTId the AWT ids that this behavior wishes to intercept + */ + public WakeupOnAWTEvent(int AWTId) { + this.AwtId = AWTId; + this.EventMask = 0L; + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Constructs a new WakeupOnAWTEvent using Ored EVENT_MASK values. + * @param eventMask the AWT EVENT_MASK values Ored together + */ + public WakeupOnAWTEvent(long eventMask) { + this.EventMask = eventMask; + this.AwtId = 0; + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Retrieves the array of consecutive AWT event that triggered this wakeup. + * A value of null implies that this event was not the trigger for the + * behavior wakeup. + * @return either null (if not resposible for wakeup) or the array of + * AWTEvents responsible for the wakeup. + */ + public AWTEvent[] getAWTEvent(){ + AWTEvent eventArray[]; + + synchronized (events) { + eventArray = new AWTEvent[events.size()]; + events.copyInto(eventArray); + events.removeAllElements(); + } + + return eventArray; + } + + + /** + * Sets the AWT event that will cause a behavior wakeup. + * @param event The event causing this wakeup + */ + void addAWTEvent(AWTEvent event){ + events.addElement(event); + this.setTriggered(); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + resetBehaviorCondition(bs); + bs.wakeupOnAWTEvent.add(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + bs.wakeupOnAWTEvent.remove(this); + } + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) { + + if (enableAWTEventTS != bs.awtEventTimestamp) { + if ((AwtId >= ComponentEvent.COMPONENT_FIRST && + AwtId <= ComponentEvent.COMPONENT_LAST) || + (EventMask & AWTEvent.COMPONENT_EVENT_MASK) != 0) { + behav.universe.enableComponentEvents(); + } + if ((AwtId >= FocusEvent.FOCUS_FIRST && AwtId <= FocusEvent.FOCUS_LAST) || + (EventMask & AWTEvent.FOCUS_EVENT_MASK) != 0) { + behav.universe.enableFocusEvents(); + } + if ((AwtId >= KeyEvent.KEY_FIRST && AwtId <= KeyEvent.KEY_LAST) || + (EventMask & AWTEvent.KEY_EVENT_MASK) != 0) { + behav.universe.enableKeyEvents(); + } + if ((AwtId >= MouseEvent.MOUSE_FIRST) && + (AwtId <= MouseEvent.MOUSE_LAST)) { + if ((AwtId == MouseEvent.MOUSE_DRAGGED) || + (AwtId == MouseEvent.MOUSE_MOVED)) { + behav.universe.enableMouseMotionEvents(); + } else { + behav.universe.enableMouseEvents(); + } + } else { + if ((EventMask & AWTEvent.MOUSE_EVENT_MASK) != 0) { + behav.universe.enableMouseEvents(); + } + if ((EventMask & AWTEvent.MOUSE_MOTION_EVENT_MASK) != 0) { + behav.universe.enableMouseMotionEvents(); + } + } + enableAWTEventTS = bs.awtEventTimestamp; + } + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnActivation.java b/src/classes/share/javax/media/j3d/WakeupOnActivation.java new file mode 100644 index 0000000..56a646d --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnActivation.java @@ -0,0 +1,65 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class specifying a wakeup the first time an active Viewplatform's + * activation + * volume intersects with this object's scheduling region. + * This gives the behavior an explicit means of executing code when + * it is activated. + */ +public final class WakeupOnActivation extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 1; + + /** + * Constructs a new WakeupOnActivation criterion. + */ + public WakeupOnActivation() { + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + behav.wakeupArray[BehaviorRetained.WAKEUP_ACTIVATE_INDEX]++; + behav.wakeupMask |= BehaviorRetained.WAKEUP_ACTIVATE; + bs.wakeupOnActivation.add(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + behav.wakeupArray[BehaviorRetained.WAKEUP_ACTIVATE_INDEX]--; + if (behav.wakeupArray[BehaviorRetained.WAKEUP_ACTIVATE_INDEX] == 0) { + behav.wakeupMask &= ~BehaviorRetained.WAKEUP_ACTIVATE; + } + bs.wakeupOnActivation.remove(this); + } + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) {} +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnBehaviorPost.java b/src/classes/share/javax/media/j3d/WakeupOnBehaviorPost.java new file mode 100644 index 0000000..d8733e0 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnBehaviorPost.java @@ -0,0 +1,113 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class that specifies a Behavior wakeup when a specific behavior object + * posts a specific event + */ +public final class WakeupOnBehaviorPost extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 1; + + Behavior armingBehavior, triggeringBehavior; + int post, triggeringPost; + + + /** + * Constructs a new WakeupOnBehaviorPost criterion. A behavior of null + * specifies a wakeup from any behavior on the specified postId. A postId + * of 0 specifies a wakeup on any postId from the specified behavior. + * A behavior of null AND a postId of 0 specify a wakeup on any postId + * from any behavior. + * @param behavior the behavior that must be the source of the post, + * if behavior == null, then any behavior posting the postId will cause + * the wakeup. + * @param postId the postId that will trigger a wakeup if posted by the + * specified behavior, if postId == 0, then any post by the specified + * behavior will cause the wakeup. + */ + public WakeupOnBehaviorPost(Behavior behavior, int postId) { + this.armingBehavior = behavior; + this.post = postId; + triggeringPost = -1; + triggeringBehavior = null; + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Retrieve the WakeupCriterion's specified postId + * @return the post id specified in this object's construction. + */ + public int getPostId(){ + return post; + } + + + /** + * Returns the behavior specified in this object's constructor. + * @return the arming behavior + */ + public Behavior getBehavior () { + return armingBehavior; + } + + + /** + * Returns the postId that caused the behavior to wakeup. If the postId + * used to construct this wakeup criterion was not zero, then the + * triggering postId will always be equal to the postId used in the + * constructor. + */ + public int getTriggeringPostId() { + return triggeringPost; + } + + + /** + * Returns the behavior that triggered this wakeup. If the arming + * behavior used to construct this object was not null, then the + * triggering behavior will be the same as the arming behavior. + */ + public Behavior getTriggeringBehavior() { + return triggeringBehavior; + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + bs.wakeupOnBehaviorPost.add(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + bs.wakeupOnBehaviorPost.remove(this); + } + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) {} +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnCollisionEntry.java b/src/classes/share/javax/media/j3d/WakeupOnCollisionEntry.java new file mode 100644 index 0000000..78386ee --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnCollisionEntry.java @@ -0,0 +1,579 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.*; + +/** + * Class specifying a wakeup when the specified object + * collides with any other object in the scene graph. + * + */ +public final class WakeupOnCollisionEntry extends WakeupCriterion { + + // different types of WakeupIndexedList that use in GeometryStructure + static final int COND_IN_GS_LIST = 0; + static final int COLLIDEENTRY_IN_BS_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + /** + * Use geometry in computing collisions. + */ + public static final int USE_GEOMETRY = 10; + + /** + * Use geometric bounds as an approximation in computing collisions. + */ + public static final int USE_BOUNDS = 11; + + static final int GROUP = NodeRetained.GROUP; + static final int BOUNDINGLEAF = NodeRetained.BOUNDINGLEAF; + static final int SHAPE = NodeRetained.SHAPE; + static final int MORPH = NodeRetained.MORPH; + static final int ORIENTEDSHAPE3D = NodeRetained.ORIENTEDSHAPE3D; + static final int BOUND = 0; + + /** + * Accuracy mode one of USE_GEOMETRY or USE_BOUNDS + */ + int accuracyMode; + + // Cached the arming Node being used when it is not BOUND + NodeRetained armingNode; + + // A transformed Bounds of Group/Bounds, use by + // BOUND, GROUP + Bounds vwcBounds = null; + + // Use by BoundingLeaf, point to mirror BoundingLeaf + // transformedRegion under this leaf is used. + BoundingLeafRetained boundingLeaf = null; + + /** + * Geometry atoms that this wakeup condition refer to. + * Only use by SHAPE, MORPH, GROUP, ORIENTEDSHAPE + */ + UnorderList geometryAtoms = null; + + // one of GROUP, BOUNDINGLEAF, SHAPE, MORPH, BOUND + int nodeType; + + SceneGraphPath armingPath = null; + Bounds armingBounds = null; + + // the following two references are set only after a collision + // has occurred + Bounds collidingBounds = null; + SceneGraphPath collidingPath = null; + + /** + * Constructs a new WakeupOnCollisionEntry criterion with + * USE_BOUNDS for a speed hint. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or + * BoundingLeaf node. + */ + public WakeupOnCollisionEntry(SceneGraphPath armingPath) { + this(armingPath, USE_BOUNDS); + } + + /** + * Constructs a new WakeupOnCollisionEntry criterion. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or + * BoundingLeaf node. + */ + public WakeupOnCollisionEntry(SceneGraphPath armingPath, + int speedHint) { + this(new SceneGraphPath(armingPath), speedHint, null); + } + + /** + * Constructs a new WakeupOnCollisionEntry criterion. + * @param armingNode the Group, Shape, or Morph node used to + * <em>arm</em> collision detection + * @exception IllegalArgumentException if object is under a + * SharedGroup node or object is other than a Group, Shape3D, + * Morph or BoundingLeaf node. + */ + public WakeupOnCollisionEntry(Node armingNode) { + this(armingNode, USE_BOUNDS); + } + + /** + * Constructs a new WakeupOnCollisionEntry criterion. + * @param armingNode the Group, Shape, or Morph node used to + * <em>arm</em> collision detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object is under a + * SharedGroup node or object is other than a Group, Shape3D, + * Morph or BoundingLeaf node. + */ + public WakeupOnCollisionEntry(Node armingNode, int speedHint) { + this(new SceneGraphPath(null, armingNode), speedHint, null); + } + + + /** + * Constructs a new WakeupOnCollisionEntry criterion. + * @param armingBounds the bounds object used to <em>arm</em> collision + * detection + */ + public WakeupOnCollisionEntry(Bounds armingBounds) { + this(null, USE_BOUNDS, (Bounds) armingBounds.clone()); + } + + /** + * Constructs a new WakeupOnCollisionEntry criterion. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @param armingBounds the bounds object used to <em>arm</em> collision + * detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or + * BoundingLeaf node. + */ + WakeupOnCollisionEntry(SceneGraphPath armingPath, + int speedHint, Bounds armingBounds) { + if (armingPath != null) { + this.armingNode = (NodeRetained) armingPath.getObject().retained; + nodeType = getNodeType(armingNode, armingPath, + "WakeupOnCollisionEntry"); + this.armingPath = armingPath; + validateSpeedHint(speedHint, "WakeupOnCollisionEntry4"); + } else { + this.armingBounds = armingBounds; + nodeType = BOUND; + } + accuracyMode = speedHint; + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Returns the path used in specifying the collision condition. + * @return the SceneGraphPath object generated when arming this + * criterion---null implies that a bounds object armed this criteria + */ + public SceneGraphPath getArmingPath() { + return (armingPath != null ? + new SceneGraphPath(armingPath) : null); + } + + /** + * Returns the bounds object used in specifying the collision condition. + * @return the Bounds object generated when arming this + * criterion---null implies that a SceneGraphPath armed this criteria + */ + public Bounds getArmingBounds() { + return (armingBounds != null ? + (Bounds)armingBounds.clone() : null); + } + + /** + * Retrieves the path describing the object causing the collision. + * @return the SceneGraphPath that describes the triggering object. + * @exception IllegalStateException if not called from within the + * a behavior's processStimulus method which was awoken by a collision. + */ + public SceneGraphPath getTriggeringPath() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry5")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new IllegalStateException + (J3dI18N.getString("WakeupOnCollisionEntry5")); + } + } + return (collidingPath != null ? + new SceneGraphPath(collidingPath): null); + } + + /** + * Retrieves the Bounds object that caused the collision + * @return the colliding Bounds object. + * @exception IllegalStateException if not called from within the + * a behavior's processStimulus method which was awoken by a collision. + */ + public Bounds getTriggeringBounds() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry6")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new IllegalStateException + (J3dI18N.getString("WakeupOnCollisionEntry6")); + } + } + return (collidingBounds != null ? + (Bounds)(collidingBounds.clone()): null); + } + + + /** + * Node legality checker + * throw Exception if node is not legal. + * @return nodeType + */ + static int getNodeType(NodeRetained armingNode, + SceneGraphPath armingPath, String s) + throws IllegalArgumentException { + + // check if SceneGraphPath is unique + // Note that graph may not live at this point so we + // can't use node.inSharedGroup. + if (!armingPath.validate()) { + throw new IllegalArgumentException(J3dI18N.getString(s + "7")); + } + + if (armingNode.inBackgroundGroup) { + throw new IllegalArgumentException(J3dI18N.getString(s + "1")); + } + + // This should come before Shape3DRetained check + if (armingNode instanceof OrientedShape3DRetained) { + return ORIENTEDSHAPE3D; + } + + if (armingNode instanceof Shape3DRetained) { + return SHAPE; + } + + if (armingNode instanceof MorphRetained) { + return MORPH; + } + + if (armingNode instanceof GroupRetained) { + return GROUP; + } + + if (armingNode instanceof BoundingLeafRetained) { + return BOUNDINGLEAF; + } + + throw new IllegalArgumentException(J3dI18N.getString(s + "0")); + } + + /** + * speedHint legality checker + * throw Exception if speedHint is not legal + */ + static void validateSpeedHint(int speedHint, String s) + throws IllegalArgumentException { + if ((speedHint != USE_GEOMETRY) && (speedHint != USE_BOUNDS)) { + throw new IllegalArgumentException(J3dI18N.getString(s)); + } + + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + + switch (nodeType) { + case SHAPE: // Use geometryAtoms[].collisionBounds + case ORIENTEDSHAPE3D: + if (!armingNode.source.isLive()) { + return; + } + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + Shape3DRetained shape = (Shape3DRetained) armingNode; + geometryAtoms.add(Shape3DRetained.getGeomAtom(shape.getMirrorShape(armingPath))); + break; + case MORPH: // Use geometryAtoms[].collisionBounds + if (!armingNode.source.isLive()) { + return; + } + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + MorphRetained morph = (MorphRetained) armingNode; + geometryAtoms.add(Shape3DRetained.getGeomAtom(morph.getMirrorShape(armingPath))); + break; + case BOUNDINGLEAF: // use BoundingLeaf.transformedRegion + if (!armingNode.source.isLive()) { + return; + } + this.boundingLeaf = ((BoundingLeafRetained) armingNode).mirrorBoundingLeaf; + break; + case BOUND: // use this.vwcBounds + vwcBounds = (Bounds) armingBounds.clone(); + this.armingNode = behav; + break; + case GROUP: + if (!armingNode.source.isLive()) { + return; + } + if (accuracyMode == USE_GEOMETRY) { + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms); + } + // else use this.vwcBounds + default: + } + + behav.universe.geometryStructure.addWakeupOnCollision(this); + } + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + vwcBounds = null; + if (geometryAtoms != null) { + geometryAtoms.clear(); + } + boundingLeaf = null; + behav.universe.geometryStructure.removeWakeupOnCollision(this); + } + + + // Set collidingPath & collidingBounds + void setTarget(BHLeafInterface leaf) { + SceneGraphPath path; + Bounds bound; + + if (leaf instanceof GeometryAtom) { + // Find the triggered Path & Bounds for this geometry Atom + GeometryAtom geomAtom = (GeometryAtom) leaf; + Shape3DRetained shape = geomAtom.source; + + path = getSceneGraphPath(shape.sourceNode, + shape.key, + shape.getCurrentLocalToVworld(0)); + bound = getTriggeringBounds(shape); + + } else { + // Find the triggered Path & Bounds for this alternative + // collision target + GroupRetained group = (GroupRetained) leaf; + path = getSceneGraphPath(group); + bound = getTriggeringBounds(group); + } + + if (path != null) { + // colliding path may be null when branch detach before + // user behavior retrieve the previous colliding path + collidingPath = path; + collidingBounds = bound; + } + } + + + // Invoke from GeometryStructure to update vwcBounds of GROUP + void updateCollisionBounds(boolean reEvaluateGAs){ + if (nodeType == GROUP) { + GroupRetained group = (GroupRetained) armingNode; + if (group.collisionBound != null) { + vwcBounds = (Bounds) group.collisionBound.clone(); + } else { + // this may involve recursive tree traverse if + // BoundsAutoCompute is true, we can't avoid + // since the bound under it may change by transform + vwcBounds = group.getEffectiveBounds(); + } + group.transformBounds(armingPath, vwcBounds); + } else if (nodeType == BOUND) { + vwcBounds.transform(armingBounds, behav.getCurrentLocalToVworld()); + } + + if (reEvaluateGAs && + (nodeType == GROUP) && + (accuracyMode == USE_GEOMETRY)) { + geometryAtoms.clear(); + ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms); + } + } + + + /** + * Return the TriggeringBounds for node + */ + static Bounds getTriggeringBounds(Shape3DRetained mirrorShape) { + NodeRetained node = mirrorShape.sourceNode; + + if (node instanceof Shape3DRetained) { + Shape3DRetained shape = (Shape3DRetained) node; + if (shape.collisionBound == null) { + // TODO: get bounds by copy + return shape.getEffectiveBounds(); + } + return shape.collisionBound; + } + + + MorphRetained morph = (MorphRetained) node; + if (morph.collisionBound == null) { + // TODO: get bounds by copy + return morph.getEffectiveBounds(); + } + return morph.collisionBound; + } + + + /** + * Return the TriggeringBounds for node + */ + static Bounds getTriggeringBounds(GroupRetained group) { + if (group.collisionBound == null) { + // TODO: get bounds by copy + return group.getEffectiveBounds(); + } + return group.collisionBound; + } + + static SceneGraphPath getSceneGraphPath(GroupRetained group) { + // Find the transform base on the key + Transform3D transform = null; + GroupRetained srcGroup = group.sourceNode; + + synchronized (srcGroup.universe.sceneGraphLock) { + if (group.key == null) { + transform = srcGroup.getCurrentLocalToVworld(); + } else { + HashKey keys[] = srcGroup.localToVworldKeys; + if (keys == null) { + // the branch is already detach when + // Collision got this message + return null; + } + transform = srcGroup.getCurrentLocalToVworld(group.key); + } + return getSceneGraphPath(srcGroup, group.key, transform); + } + + } + + /** + * return the SceneGraphPath of the geomAtom. + * Find the alternative Collision target closest to the locale. + */ + static SceneGraphPath getSceneGraphPath(NodeRetained startNode, + HashKey key, + Transform3D transform) { + synchronized (startNode.universe.sceneGraphLock) { + NodeRetained target = startNode; + + UnorderList path = new UnorderList(5, Node.class); + NodeRetained nodeR = target; + Locale locale = nodeR.locale; + String nodeId; + Vector parents; + NodeRetained linkR; + + if (nodeR.inSharedGroup) { + // getlastNodeId() will destroy this key + if (key != null) { + key = new HashKey(key); + } else { + key = new HashKey(startNode.localToVworldKeys[0]); + } + } + + do { + if (nodeR.source.getCapability(Node.ENABLE_COLLISION_REPORTING)){ + path.add(nodeR.source); + } + + if (nodeR instanceof SharedGroupRetained) { + + // retrieve the last node ID + nodeId = key.getLastNodeId(); + parents = ((SharedGroupRetained) nodeR).parents; + NodeRetained prevNodeR = nodeR; + for(int i=parents.size()-1; i >=0; i--) { + linkR = (NodeRetained) parents.elementAt(i); + if (linkR.nodeId.equals(nodeId)) { + nodeR = linkR; + break; + } + } + if (nodeR == prevNodeR) { + // the branch is already detach when + // Collision got this message + return null; + } + } else if ((nodeR instanceof GroupRetained) && + ((GroupRetained) nodeR).collisionTarget) { + // we need to find the collision target closest to the + // root of tree + target = nodeR; + + if (key == null) { + transform = nodeR.getCurrentLocalToVworld(null); + } else { + transform = nodeR.getCurrentLocalToVworld(key); + } + } + nodeR = nodeR.parent; + } while (nodeR != null); // reach Locale + + Node nodes[]; + if (target == startNode) { // in most case + nodes = (Node []) path.toArray(false); + } else { // alternativeCollisionTarget is set + nodes = (Node []) path.toArray(target); + } + SceneGraphPath sgpath = new SceneGraphPath(locale, + nodes, + (Node) target.source); + sgpath.setTransform(transform); + return sgpath; + } + } + + + void setTriggered(){ + // if path not set, probably the branch is just detach. + if (collidingPath != null) { + super.setTriggered(); + } + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) { + // The reference geometryAtom will not change once + // Shape3D create so there is no need to set this. + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnCollisionExit.java b/src/classes/share/javax/media/j3d/WakeupOnCollisionExit.java new file mode 100644 index 0000000..f34b0bd --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnCollisionExit.java @@ -0,0 +1,377 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.*; + +/** + * Class specifying a wakeup when the specified object + * no longer collides with any other object in the scene graph. + */ +public final class WakeupOnCollisionExit extends WakeupCriterion { + + // different types of WakeupIndexedList that use in GeometryStructure + static final int COND_IN_GS_LIST = 0; + static final int COLLIDEEXIT_IN_BS_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + /** + * Use geometry in computing collisions. + */ + public static final int USE_GEOMETRY = WakeupOnCollisionEntry.USE_GEOMETRY; + + /** + * Use geometric bounds as an approximation in computing collisions. + */ + public static final int USE_BOUNDS = WakeupOnCollisionEntry.USE_BOUNDS; + + /** + * Accuracy mode one of USE_GEOMETRY or USE_BOUNDS + */ + int accuracyMode; + + // Cached the arming Node being used when it is not BOUND + NodeRetained armingNode; + + // A transformed Bounds of Group/Bounds + // use by BOUND, GROUP + Bounds vwcBounds; + + + // use by GROUP to cache local bounds + Bounds localBounds = null; + + // Use by BoundingLeaf, point to mirror BoundingLeaf + // transformedRegion under this leaf is used. + BoundingLeafRetained boundingLeaf = null; + + /** + * Geometry atoms that this wakeup condition refer to. + * Only use by SHAPE, MORPH, GROUP, ORIENTEDSHAPE + */ + UnorderList geometryAtoms = null; + + // one of GROUP, BOUNDINGLEAF, SHAPE, MORPH, BOUND + int nodeType; + + SceneGraphPath armingPath = null; + Bounds armingBounds = null; + + // the following two references are set only after a collision + // has occurred + SceneGraphPath collidingPath = null; + Bounds collidingBounds = null; + + /** + * Constructs a new WakeupOnCollisionExit criterion. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or BoundingLeaf node. + */ + public WakeupOnCollisionExit(SceneGraphPath armingPath) { + this(armingPath, USE_BOUNDS); + } + + /** + * Constructs a new WakeupOnCollisionExit criterion. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or BoundingLeaf node. + */ + public WakeupOnCollisionExit(SceneGraphPath armingPath, int speedHint) { + this(new SceneGraphPath(armingPath), speedHint, null); + } + + /** + * Constructs a new WakeupOnCollisionExit criterion. + * @param armingNode the Group, Shape, or Morph node used to + * <em>arm</em> collision detection + * @exception IllegalArgumentException if object is under a + * SharedGroup node or object is other than a Group, Shape3D, + * Morph or BoundingLeaf node. + */ + public WakeupOnCollisionExit(Node armingNode) { + this(armingNode, USE_BOUNDS); + } + + /** + * Constructs a new WakeupOnCollisionExit criterion. + * @param armingNode the Group, Shape, or Morph node used to + * <em>arm</em> collision detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object is under a + * SharedGroup node or object is other than a Group, Shape3D, + * Morph or BoundingLeaf node. + */ + public WakeupOnCollisionExit(Node armingNode, int speedHint) { + this(new SceneGraphPath(null, armingNode), speedHint, null); + } + + + /** + * Constructs a new WakeupOnCollisionExit criterion. + * @param armingBounds the bounds object used to <em>arm</em> collision + * detection + */ + public WakeupOnCollisionExit(Bounds armingBounds) { + this(null, USE_BOUNDS, (Bounds) armingBounds.clone()); + } + + /** + * Constructs a new WakeupOnCollisionExit criterion. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @param armingBounds the bounds object used to <em>arm</em> collision + * detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or BoundingLeaf node. + */ + WakeupOnCollisionExit(SceneGraphPath armingPath, + int speedHint, Bounds armingBounds) { + if (armingPath != null) { + this.armingNode = (NodeRetained) armingPath.getObject().retained; + nodeType = WakeupOnCollisionEntry.getNodeType(armingNode, armingPath, + "WakeupOnCollisionExit"); + this.armingPath = armingPath; + WakeupOnCollisionEntry.validateSpeedHint(speedHint, + "WakeupOnCollisionExit4"); + } else { + this.armingBounds = armingBounds; + nodeType = WakeupOnCollisionEntry.BOUND; + } + accuracyMode = speedHint; + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Returns the path used in specifying the collision condition. + * @return the SceneGraphPath object generated when arming this + * criterion---null implies that a bounds object armed this criteria + */ + public SceneGraphPath getArmingPath() { + return (armingPath != null ? + new SceneGraphPath(armingPath) : null); + } + + /** + * Returns the bounds object used in specifying the collision condition. + * @return the Bounds object generated when arming this + * criterion---null implies that a SceneGraphPath armed this criteria + */ + public Bounds getArmingBounds() { + return (armingBounds != null ? + (Bounds)armingBounds.clone() : null); + } + + /** + * Retrieves the path describing the object causing the collision. + * @return the SceneGraphPath that describes the triggering object. + * @exception IllegalStateException if not called from within the + * a behavior's processStimulus method which was awoken by a collision. + */ + public SceneGraphPath getTriggeringPath() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionExit5")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new IllegalStateException + (J3dI18N.getString("WakeupOnCollisionExit5")); + } + } + return (collidingPath != null ? + new SceneGraphPath(collidingPath): null); + } + + /** + * Retrieves the Bounds object that caused the collision + * @return the colliding Bounds object. + * @exception IllegalStateException if not called from within the + * a behavior's processStimulus method which was awoken by a collision. + */ + public Bounds getTriggeringBounds() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionExit6")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new IllegalStateException + (J3dI18N.getString("WakeupOnCollisionExit6")); + } + } + return (collidingBounds != null ? + (Bounds)(collidingBounds.clone()): null); + } + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + + switch (nodeType) { + case WakeupOnCollisionEntry.SHAPE: // Use geometryAtoms[].collisionBounds + case WakeupOnCollisionEntry.ORIENTEDSHAPE3D: + if (!armingNode.source.isLive()) { + return; + } + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + Shape3DRetained shape = (Shape3DRetained) armingNode; + geometryAtoms.add(Shape3DRetained.getGeomAtom(shape.getMirrorShape(armingPath))); + break; + case WakeupOnCollisionEntry.MORPH: // Use geometryAtoms[].collisionBounds + if (!armingNode.source.isLive()) { + return; + } + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + MorphRetained morph = (MorphRetained) armingNode; + geometryAtoms.add(Shape3DRetained.getGeomAtom(morph.getMirrorShape(armingPath))); + break; + case WakeupOnCollisionEntry.BOUNDINGLEAF: // use BoundingLeaf.transformedRegion + if (!armingNode.source.isLive()) { + return; + } + this.boundingLeaf = ((BoundingLeafRetained) armingNode).mirrorBoundingLeaf; + break; + case WakeupOnCollisionEntry.BOUND: // use this.vwcBounds + vwcBounds = (Bounds) armingBounds.clone(); + this.armingNode = behav; + break; + case WakeupOnCollisionEntry.GROUP: + if (!armingNode.source.isLive()) { + return; + } + if (accuracyMode == USE_GEOMETRY) { + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms); + } + // else use this.vwcBounds + default: + } + + behav.universe.geometryStructure.addWakeupOnCollision(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + vwcBounds = null; + if (geometryAtoms != null) { + geometryAtoms.clear(); + } + boundingLeaf = null; + behav.universe.geometryStructure.removeWakeupOnCollision(this); + } + + + + // Set collidingPath & collidingBounds + void setTarget(BHLeafInterface leaf) { + SceneGraphPath path; + Bounds bound; + + if (leaf instanceof GeometryAtom) { + // Find the triggered Path & Bounds for this geometry Atom + GeometryAtom geomAtom = (GeometryAtom) leaf; + Shape3DRetained shape = geomAtom.source; + path = WakeupOnCollisionEntry.getSceneGraphPath( + shape.sourceNode, + shape.key, + shape.getCurrentLocalToVworld(0)); + bound = WakeupOnCollisionEntry.getTriggeringBounds(shape); + + + } else { + // Find the triggered Path & Bounds for this alternative + // collision target + GroupRetained group = (GroupRetained) leaf; + path = WakeupOnCollisionEntry.getSceneGraphPath(group); + bound = WakeupOnCollisionEntry.getTriggeringBounds(group); + } + + if (path != null) { + // colliding path may be null when branch detach before + // user behavior retrieve the previous colliding path + collidingPath = path; + collidingBounds = bound; + } + + } + + // Invoke from GeometryStructure to update vwcBounds of GROUP + void updateCollisionBounds(boolean reEvaluateGAs){ + if (nodeType == WakeupOnCollisionEntry.GROUP) { + GroupRetained group = (GroupRetained) armingNode; + if (group.collisionBound != null) { + vwcBounds = (Bounds) group.collisionBound.clone(); + } else { + // this may involve recursive tree traverse if + // BoundsAutoCompute is true, we can't avoid + // since the bound under it may change by transform + vwcBounds = group.getEffectiveBounds(); + } + group.transformBounds(armingPath, vwcBounds); + } else if (nodeType == WakeupOnCollisionEntry.BOUND) { + vwcBounds.transform(armingBounds, behav.getCurrentLocalToVworld()); + } + + if (reEvaluateGAs && + (nodeType == WakeupOnCollisionEntry.GROUP) && + (accuracyMode == USE_GEOMETRY)) { + geometryAtoms.clear(); + ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms); + } + } + + void setTriggered(){ + // if path not set, probably the branch is just detach. + if (collidingPath != null) { + super.setTriggered(); + } + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) { + // The reference geometryAtom will not change once + // Shape3D create so there is no need to set this. + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnCollisionMovement.java b/src/classes/share/javax/media/j3d/WakeupOnCollisionMovement.java new file mode 100644 index 0000000..f7e160c --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnCollisionMovement.java @@ -0,0 +1,385 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.*; + +/** + * Class specifying a wakeup when the specified object + * moves while in collision with any other object in the scene graph. + */ +public final class WakeupOnCollisionMovement extends WakeupCriterion { + + // different types of WakeupIndexedList that use in GeometryStructure + static final int COND_IN_GS_LIST = 0; + static final int COLLIDEMOVE_IN_BS_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + /** + * Use geometry in computing collisions. + */ + public static final int USE_GEOMETRY = WakeupOnCollisionEntry.USE_GEOMETRY; + + /** + * Use geometric bounds as an approximation in computing collisions. + */ + public static final int USE_BOUNDS = WakeupOnCollisionEntry.USE_BOUNDS; + + /** + * Accuracy mode one of USE_GEOMETRY or USE_BOUNDS + */ + int accuracyMode; + + // Cached the arming Node being used when it is not BOUND + NodeRetained armingNode; + + // transformed Bounds of Group/Bounds, use by + // BOUND, BOUNDINGLEAF, GROUP + Bounds vwcBounds; + + + // use by GROUP to cache local bounds + Bounds localBounds = null; + + // source bound when collision occur last time + // These three variables are used to keep track of duplicate + // wakupOnMovement event + Bounds lastSrcBounds = null; + Bounds lastDstBounds = null; + boolean duplicateEvent = false; + + // Use by BoundingLeaf, point to mirror BoundingLeaf + // transformedRegion under this leaf is used. + BoundingLeafRetained boundingLeaf = null; + + /** + * Geometry atoms that this wakeup condition refer to. + * Only use by SHAPE, MORPH, GROUP, ORIENTEDSHAPE + */ + UnorderList geometryAtoms = null; + + // one of GROUP, BOUNDINGLEAF, SHAPE, MORPH, BOUND + int nodeType; + + SceneGraphPath armingPath = null; + Bounds armingBounds = null; + + // the following two references are set only after a collision + // has occurred + SceneGraphPath collidingPath = null; + Bounds collidingBounds = null; + + /** + * Constructs a new WakeupOnCollisionMovement criterion. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or BoundingLeaf node. + */ + public WakeupOnCollisionMovement(SceneGraphPath armingPath) { + this(armingPath, USE_BOUNDS); + } + + /** + * Constructs a new WakeupOnCollisionMovement criterion. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or BoundingLeaf node. + */ + public WakeupOnCollisionMovement(SceneGraphPath armingPath, + int speedHint) { + this(new SceneGraphPath(armingPath), speedHint, null); + } + + /** + * Constructs a new WakeupOnCollisionMovement criterion. + * @param armingNode the Group, Shape, or Morph node used to + * <em>arm</em> collision detection + * @exception IllegalArgumentException if object is under a + * SharedGroup node or object is other than a Group, Shape3D, + * Morph or BoundingLeaf node. + */ + public WakeupOnCollisionMovement(Node armingNode) { + this(armingNode, USE_BOUNDS); + } + + /** + * Constructs a new WakeupOnCollisionMovement criterion. + * @param armingNode the Group, Shape, or Morph node used to + * <em>arm</em> collision detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object is under a + * SharedGroup node or object is other than a Group, Shape3D, + * Morph or BoundingLeaf node. + */ + public WakeupOnCollisionMovement(Node armingNode, int speedHint) { + this(new SceneGraphPath(null, armingNode), speedHint, null); + } + + + /** + * Constructs a new WakeupOnCollisionMovement criterion. + * @param armingBounds the bounds object used to <em>arm</em> collision + * detection + */ + public WakeupOnCollisionMovement(Bounds armingBounds) { + this(null, USE_BOUNDS, (Bounds)armingBounds.clone()); + } + + /** + * Constructs a new WakeupOnCollisionMovement criterion. + * @param armingPath the path used to <em>arm</em> collision + * detection + * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how + * accurately Java 3D will perform collision detection + * @param armingBounds the bounds object used to <em>arm</em> collision + * detection + * @exception IllegalArgumentException if hint is not one of + * USE_GEOMETRY or USE_BOUNDS. + * @exception IllegalArgumentException if object associated with the + * SceneGraphPath is other than a Group, Shape3D, Morph, or BoundingLeaf node. + */ + WakeupOnCollisionMovement(SceneGraphPath armingPath, + int speedHint, Bounds armingBounds) { + if (armingPath != null) { + this.armingNode = (NodeRetained) armingPath.getObject().retained; + nodeType = WakeupOnCollisionEntry.getNodeType(armingNode, armingPath, + "WakeupOnCollisionMovement"); + this.armingPath = armingPath; + WakeupOnCollisionEntry.validateSpeedHint(speedHint, + "WakeupOnCollisionMovement4"); + } else { + this.armingBounds = armingBounds; + nodeType = WakeupOnCollisionEntry.BOUND; + } + accuracyMode = speedHint; + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Returns the path used in specifying the collision condition. + * @return the SceneGraphPath object generated when arming this + * criterion---null implies that a bounds object armed this criteria + */ + public SceneGraphPath getArmingPath() { + return (armingPath != null ? + new SceneGraphPath(armingPath) : null); + } + + /** + * Returns the bounds object used in specifying the collision condition. + * @return the Bounds object generated when arming this + * criterion---null implies that a SceneGraphPath armed this criteria + */ + public Bounds getArmingBounds() { + return (armingBounds != null ? + (Bounds)armingBounds.clone() : null); + } + + /** + * Retrieves the path describing the object causing the collision. + * @return the SceneGraphPath that describes the triggering object. + * @exception IllegalStateException if not called from within the + * a behavior's processStimulus method which was awoken by a collision. + */ + public SceneGraphPath getTriggeringPath() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionMovement5")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new IllegalStateException + (J3dI18N.getString("WakeupOnCollisionMovement5")); + } + } + return (collidingPath != null ? + new SceneGraphPath(collidingPath): null); + } + + /** + * Retrieves the Bounds object that caused the collision + * @return the colliding Bounds object. + * @exception IllegalStateException if not called from within the + * a behavior's processStimulus method which was awoken by a collision. + */ + public Bounds getTriggeringBounds() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionMovement6")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new IllegalStateException + (J3dI18N.getString("WakeupOnCollisionMovement6")); + } + } + return (collidingBounds != null ? + (Bounds)(collidingBounds.clone()): null); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + + switch (nodeType) { + case WakeupOnCollisionEntry.SHAPE: // Use geometryAtoms[].collisionBounds + case WakeupOnCollisionEntry.ORIENTEDSHAPE3D: + if (!armingNode.source.isLive()) { + return; + } + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + Shape3DRetained shape = (Shape3DRetained) armingNode; + geometryAtoms.add(Shape3DRetained.getGeomAtom(shape.getMirrorShape(armingPath))); + break; + case WakeupOnCollisionEntry.MORPH: // Use geometryAtoms[].collisionBounds + if (!armingNode.source.isLive()) { + return; + } + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + MorphRetained morph = (MorphRetained) armingNode; + geometryAtoms.add(Shape3DRetained.getGeomAtom(morph.getMirrorShape(armingPath))); + break; + case WakeupOnCollisionEntry.BOUNDINGLEAF: // use BoundingLeaf.transformedRegion + if (!armingNode.source.isLive()) { + return; + } + this.boundingLeaf = ((BoundingLeafRetained) armingNode).mirrorBoundingLeaf; + break; + case WakeupOnCollisionEntry.BOUND: // use this.vwcBounds + vwcBounds = (Bounds) armingBounds.clone(); + this.armingNode = behav; + break; + case WakeupOnCollisionEntry.GROUP: + if (!armingNode.source.isLive()) { + return; + } + if (accuracyMode == USE_GEOMETRY) { + if (geometryAtoms == null) { + geometryAtoms = new UnorderList(1, GeometryAtom.class); + } + ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms); + } + // else use this.vwcBounds + default: + } + + behav.universe.geometryStructure.addWakeupOnCollision(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + vwcBounds = null; + if (geometryAtoms != null) { + geometryAtoms.clear(); + } + boundingLeaf = null; + behav.universe.geometryStructure.removeWakeupOnCollision(this); + } + + + // Set collidingPath & collidingBounds + void setTarget(BHLeafInterface leaf) { + SceneGraphPath path; + Bounds bound; + + if (leaf instanceof GeometryAtom) { + // Find the triggered Path & Bounds for this geometry Atom + GeometryAtom geomAtom = (GeometryAtom) leaf; + Shape3DRetained shape = geomAtom.source; + path = WakeupOnCollisionEntry.getSceneGraphPath( + shape.sourceNode, + shape.key, + shape.getCurrentLocalToVworld(0)); + bound = WakeupOnCollisionEntry.getTriggeringBounds(shape); + + } else { + // Find the triggered Path & Bounds for this alternative + // collision target + GroupRetained group = (GroupRetained) leaf; + path = WakeupOnCollisionEntry.getSceneGraphPath(group); + bound = WakeupOnCollisionEntry.getTriggeringBounds(group); + } + + if (path != null) { + // colliding path may be null when branch detach before + // user behavior retrieve the previous colliding path + collidingPath = path; + collidingBounds = bound; + } + } + + // Invoke from GeometryStructure to update vwcBounds of GROUP + void updateCollisionBounds(boolean reEvaluateGAs) { + if (nodeType == WakeupOnCollisionEntry.GROUP) { + GroupRetained group = (GroupRetained) armingNode; + if (group.collisionBound != null) { + vwcBounds = (Bounds) group.collisionBound.clone(); + } else { + // this may involve recursive tree traverse if + // BoundsAutoCompute is true, we can't avoid + // since the bound under it may change by transform + vwcBounds = group.getEffectiveBounds(); + } + group.transformBounds(armingPath, vwcBounds); + } else if (nodeType == WakeupOnCollisionEntry.BOUND) { + vwcBounds.transform(armingBounds, behav.getCurrentLocalToVworld()); + } + + + if (reEvaluateGAs && + (nodeType == WakeupOnCollisionEntry.GROUP) && + (accuracyMode == USE_GEOMETRY)) { + geometryAtoms.clear(); + ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms); + } + } + + void setTriggered(){ + // if path not set, probably the branch is just detach. + if (collidingPath != null) { + super.setTriggered(); + } + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) { + // The reference geometryAtom will not change once + // Shape3D create so there is no need to set this. + } + +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnDeactivation.java b/src/classes/share/javax/media/j3d/WakeupOnDeactivation.java new file mode 100644 index 0000000..0635c14 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnDeactivation.java @@ -0,0 +1,78 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class specifying a wakeup on first detection of a Viewplatform's + * activation volume no longer intersecting with this object's scheduling + * region. This gives the behavior an explicit means of executing code + * when it is deactivated. + */ +public final class WakeupOnDeactivation extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 1; + + /** + * Constructs a new WakeupOnDeactivation criterion. + */ + public WakeupOnDeactivation() { + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + + /** + * Set the Criterion's trigger flag to true. + * No need to check for scheduling region in this case + */ + void setTriggered(){ + this.triggered = true; + if (this.parent == null) { + super.setConditionMet(id, Boolean.FALSE); + } else { + parent.setConditionMet(id, Boolean.FALSE); + } + } + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + behav.wakeupArray[BehaviorRetained.WAKEUP_DEACTIVATE_INDEX]++; + behav.wakeupMask |= BehaviorRetained.WAKEUP_DEACTIVATE; + bs.wakeupOnDeactivation.add(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + behav.wakeupArray[BehaviorRetained.WAKEUP_DEACTIVATE_INDEX]--; + if (behav.wakeupArray[BehaviorRetained.WAKEUP_DEACTIVATE_INDEX] == 0) { + behav.wakeupMask &= ~BehaviorRetained.WAKEUP_DEACTIVATE; + } + bs.wakeupOnDeactivation.remove(this); + } + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) {} +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnElapsedFrames.java b/src/classes/share/javax/media/j3d/WakeupOnElapsedFrames.java new file mode 100644 index 0000000..256e7a2 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnElapsedFrames.java @@ -0,0 +1,164 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class specifying a wakeup when a specific number of frames have + * elapsed. The wakeup criterion can either be passive or + * non-passive. If any behavior uses a non-passive + * WakeupOnElapsedFrames, the rendering system will run continuously. + * + * <p> + * In general, applications cannot count on behavior execution being + * synchronized with rendering. Behaviors that use + * WakeupOnElapsedFrames with a frame count of 0 are an exception to + * this general rule. Such behaviors will be executed every frame. + * Further, all modifications to scene graph objects (not including + * geometry by-reference or texture by-reference) made from the + * <code>processStimulus</code> methods of such behaviors are + * guaranteed to take effect in the same rendering frame. + */ +public final class WakeupOnElapsedFrames extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 1; + + // Indicates whether the wakeup condition is passive or + // non-passive. Only behaviors using a non-passive + // WakeupOnElapsedFrames will force a continuous traversal. + boolean passive; + + // Number of frames before wakeup + int frameCount; + + // When this reaches 0, this criterion is met. + int countdown; + + /** + * Constructs a non-passive WakeupOnElapsedFrames criterion. + * + * @param frameCount the number of frames that Java 3D should draw + * before awakening this behavior object; a value of N means + * wakeup at the end of frame N, where the current frame is zero, + * a value of zero means wakeup at the end of the current frame. + * + * @exception IllegalArgumentException if frameCount is less than zero + */ + public WakeupOnElapsedFrames(int frameCount) { + this(frameCount, false); + } + + /** + * Constructs a WakeupOnElapsedFrames criterion. + * + * @param frameCount the number of frames that Java 3D should draw + * before awakening this behavior object; a value of N means + * wakeup at the end of frame N, where the current frame is zero, + * a value of zero means wakeup at the end of the current frame. + * + * @param passive flag indicating whether this behavior is + * passive; a non-passive behavior will cause the rendering system + * to run continuously, while a passive behavior will only run + * when some other event causes a frame to be run. + * + * @exception IllegalArgumentException if frameCount is less than zero + * + * @since Java 3D 1.2 + */ + public WakeupOnElapsedFrames(int frameCount, boolean passive) { + if (frameCount < 0) + throw new IllegalArgumentException(J3dI18N.getString("WakeupOnElapsedFrames0")); + + this.frameCount = frameCount; + this.passive = passive; + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Retrieves the elapsed frame count that was used when + * constructing this object. + * + * @return the elapsed frame count specified when constructing + * this object + */ + public int getElapsedFrameCount() { + return frameCount; + } + + /** + * Retrieves the state of the passive flag that was used when + * constructing this object. + * + * @return true if this wakeup criterion is passive, false otherwise + * + * @since Java 3D 1.2 + */ + public boolean isPassive() { + return passive; + } + + /** + * decrement the frame count, and set trigger if 0 + */ + void newFrame() { + if (this.countdown == 0) { + this.setTriggered(); + } else { + this.countdown--; + } + } + + + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + this.countdown = this.frameCount; + bs.wakeupOnElapsedFrames.add(this); + if (!passive && (behav != null) && behav.enable) { + bs.activeWakeupOnFrameCount++; + } + + // This is necessary to invoke this condition next time + // Otherwise jftc won't work for static scene. + VirtualUniverse.mc.sendRunMessage(bs.universe, + J3dThread.UPDATE_BEHAVIOR); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + bs.wakeupOnElapsedFrames.remove(this); + if (!passive && (behav != null) && behav.enable) { + bs.activeWakeupOnFrameCount--; + } + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) { + this.countdown = this.frameCount; + } + +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnElapsedTime.java b/src/classes/share/javax/media/j3d/WakeupOnElapsedTime.java new file mode 100644 index 0000000..354827b --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnElapsedTime.java @@ -0,0 +1,94 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class specifying a wakeup when a specific number of milliseconds + * have elapsed. + * + */ +public final class WakeupOnElapsedTime extends WakeupCriterion { + + long wait; + + /** + * This represents the triggered time + */ + long triggeredTime; + + /** + * Constructs a new WakeupOnElapsedTime criterion. + * @param milliseconds the number of milliseconds to the wakeup. A value + * of zero or less will cause an IllegalArgumentException to be thrown. + */ + public WakeupOnElapsedTime(long milliseconds) { + if(milliseconds <= 0L) + throw new IllegalArgumentException(J3dI18N.getString("WakeupOnElapsedTime0")); + this.wait = milliseconds; + } + + /** + * Retrieve the WakeupCriterion's elapsed time value that was used when + * constructing this object. + * @return the elapsed time specified when constructing this object + */ + public long getElapsedFrameTime(){ + return wait; + } + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + this.triggeredTime = wait + System.currentTimeMillis(); + behav.wakeupArray[BehaviorRetained.WAKEUP_TIME_INDEX]++; + behav.wakeupMask |= BehaviorRetained.WAKEUP_TIME; + VirtualUniverse.mc.timerThread.add(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + behav.wakeupArray[BehaviorRetained.WAKEUP_TIME_INDEX]--; + if (behav.wakeupArray[BehaviorRetained.WAKEUP_TIME_INDEX] == 0) { + behav.wakeupMask &= ~BehaviorRetained.WAKEUP_TIME; + } + VirtualUniverse.mc.timerThread.remove(this); + } + + /** + * This is invoked when Behavior processStimulus can't schedule + * to run because behav.active = false. In this case we must + * reinsert the wakeupOnElapseTime condition back to the + * TimerThread wakeup heap + */ + void reInsertElapseTimeCond() { + super.reInsertElapseTimeCond(); + this.triggeredTime = wait + System.currentTimeMillis(); + VirtualUniverse.mc.timerThread.add(this); + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) { + this.triggeredTime = wait + System.currentTimeMillis(); + VirtualUniverse.mc.timerThread.add(this); + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnElapsedTimeHeap.java b/src/classes/share/javax/media/j3d/WakeupOnElapsedTimeHeap.java new file mode 100644 index 0000000..9b9c570 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnElapsedTimeHeap.java @@ -0,0 +1,210 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * A Binary heap to store WakeupOnElapsedTime. It is arranged so that the + * smallest triggeredTime of the wakeup object is put at the top of the heap. + * Add/deletion takes O(log n) time. + * For better performance we can consider to use Fibonacci Heaps. + * + */ +class WakeupOnElapsedTimeHeap implements Cloneable { + + // entry 0 is not used so index can be calculated more efficiently + WakeupOnElapsedTime data[]; + int size = 0; + + /** + * Construct heap with user-defined capacity + */ + WakeupOnElapsedTimeHeap(int initCapacity) { + data = new WakeupOnElapsedTime[initCapacity+1]; + } + + /** + * Construct heap of default capacity 10 + */ + WakeupOnElapsedTimeHeap() { + this(10); + } + + /** + * Return size of heap + */ + final int size() { + return size; + } + + /** + * Return true if heap is empty + */ + final boolean isEmpty() { + return (size == 0); + } + + /** + * Get the minimum element from the heap. + * User has to make sure that size > 0 before it is called. + */ + final WakeupOnElapsedTime getMin() { + return data[1]; + } + + + /** + * Insert the key into the heap + */ + final void insert(WakeupOnElapsedTime key) { + if (data.length == size + 1) { + WakeupOnElapsedTime oldData[] = data; + data = new WakeupOnElapsedTime[oldData.length << 1]; + System.arraycopy(oldData, 0, data, 0, oldData.length); + } + + int i = ++size; + + int parentIdx = i >> 1; + WakeupOnElapsedTime parentKey = data[parentIdx]; + long time = key.triggeredTime; + + while ((i > 1) && (parentKey.triggeredTime > time)) { + data[i] = parentKey; + i = parentIdx; + parentIdx >>= 1; + parentKey = data[parentIdx]; + } + data[i] = key; + } + + /** + * Extract wakeup condition belongs to behav from the heap. + * Return true if wakeup is found. + */ + final void extract(BehaviorRetained behav) { + for (int i=1; i <= size; i++) { + if (data[i].behav == behav) { + extract(i); + } + } + } + + /** + * Extract wakeup from the heap. + * Return true if wakeup is found. + */ + final boolean extract(WakeupOnElapsedTime wakeup) { + for (int i=1; i <= size; i++) { + if (data[i] == wakeup) { + extract(i); + return true; + } + } + return false; + } + + /** + * Extract the minimum value from the heap. + * User has to make sure that size > 0 before it is called. + */ + final WakeupOnElapsedTime extractMin() { + return extract(1); + } + + /** + * Extract the ith value from the heap. + * User has to make sure that i <= size before it is called. + */ + final WakeupOnElapsedTime extract(int i) { + WakeupOnElapsedTime min = data[i]; + WakeupOnElapsedTime temp; + int l, r; + int smallest; + data[i] = data[size]; + data[size] = null; // for gc + size--; + + + do { + l = i << 1; + r = l+1; + + if ((l <= size) && + (data[l].triggeredTime < data[i].triggeredTime)) { + smallest = l; + } else { + smallest = i; + } + if ((r <= size) && + (data[r].triggeredTime < data[smallest].triggeredTime)) { + smallest = r; + } + if (smallest == i) { + break; + } + temp = data[smallest]; + data[smallest] = data[i]; + data[i] = temp; + i = smallest; + } while (true); + + return min; + } + + + /*** + * Trims the capacity of this instance to be the + * list's current size. + */ + final void trimToSize() { + if (data.length > size+1) { + WakeupOnElapsedTime oldData[] = data; + data = new WakeupOnElapsedTime[size+1]; + System.arraycopy(oldData, 0, data, 0, data.length); + } + } + + /** + * Clone this heap + */ + protected final Object clone() { + try { + WakeupOnElapsedTimeHeap heap = (WakeupOnElapsedTimeHeap)super.clone(); + heap.data = new WakeupOnElapsedTime[size+1]; + System.arraycopy(data, 0, heap.data, 0, size+1); + return heap; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(); + } + + } + + + public String toString() { + StringBuffer sb = new StringBuffer("[ "); + + if (size > 0) { + sb.append(data[1]); + } + + for (int i=2; i <= size; i++) { + sb.append("," + data[i]); + } + sb.append(" ]"); + return sb.toString(); + } + + + +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnSensorEntry.java b/src/classes/share/javax/media/j3d/WakeupOnSensorEntry.java new file mode 100644 index 0000000..a74f221 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnSensorEntry.java @@ -0,0 +1,129 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class specifying a wakeup on first sensor intersection with the + * specified boundary. + */ +public final class WakeupOnSensorEntry extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + static final int SENSORENTRY_IN_BS_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + Bounds region; + + // Transformed region used by BehaviorStructure + Bounds transformedRegion; + + Sensor armingSensor; + + /** + * Constructs a new WakeupOnEntry criterion. + * @param region the region that will trigger a wakeup if a Sensor + * intersects. + */ + public WakeupOnSensorEntry(Bounds region) { + this.region = (Bounds)region.clone(); + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Returns this object's bounds specification + * @return the bounds used in constructing this WakeupCriterion. + */ + public Bounds getBounds() { + return (Bounds) region.clone(); + } + + /** + * Update the cached Transfrom Region, call from BehaviorStructure + */ + void updateTransformRegion() { + if (transformedRegion != null) { + transformedRegion.set(region); + } else { + // region is read only once initialize (since there is no + // set method for region). So no need to use cloneWithLock() + transformedRegion = (Bounds) region.clone(); + } + transformedRegion.transform(behav.getCurrentLocalToVworld(null)); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + bs.addSensorEntryCondition(this); + if ((behav != null) && behav.enable) { + bs.activeWakeupOnSensorCount++; + } + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + bs.removeSensorEntryCondition(this); + if ((behav != null) && behav.enable) { + bs.activeWakeupOnSensorCount--; + } + } + + /** + * Set the sensor that trigger this behavior + */ + void setTarget(Sensor sensor) { + this.armingSensor = sensor; + } + + /** + * Retrieves the Sensor object that caused the wakeup. + * + * @return the triggering Sensor object + * + * @exception IllegalStateException if not called from within + * a behavior's processStimulus method which was awoken by a sensor + * entry. + * + * @since Java 3D 1.2 + */ + public Sensor getTriggeringSensor() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnSensorEntry0")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new + IllegalStateException(J3dI18N.getString("WakeupOnSensorEntry0")); + } + } + return armingSensor; + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) {} +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnSensorExit.java b/src/classes/share/javax/media/j3d/WakeupOnSensorExit.java new file mode 100644 index 0000000..c8d355d --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnSensorExit.java @@ -0,0 +1,128 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class specifying a wakeup on first detection of sensors no + * longer intersecting the specified boundary. + */ +public final class WakeupOnSensorExit extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + static final int SENSOREXIT_IN_BS_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + Bounds region; + // Transformed region used by BehaviorStructure + Bounds transformedRegion; + + Sensor armingSensor; + + /** + * Constructs a new WakeupOnExit criterion. + * @param region the region that will trigger a wakeup if a Sensor + * intersects. + */ + public WakeupOnSensorExit(Bounds region) { + this.region = (Bounds)region.clone(); + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Returns this object's bounds specification + * @return the bounds used in constructing this WakeupCriterion. + */ + public Bounds getBounds() { + return (Bounds)region.clone(); + } + + /** + * Update the cached Transfrom Region, call from BehaviorStructure + */ + void updateTransformRegion() { + if (transformedRegion != null) { + transformedRegion.set(region); + } else { + // region is read only once initialize (since there is no + // set method for region). So no need to use cloneWithLock() + transformedRegion = (Bounds) region.clone(); + } + transformedRegion.transform(behav.getCurrentLocalToVworld(null)); + } + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + bs.addSensorExitCondition(this); + if ((behav != null) && behav.enable) { + bs.activeWakeupOnSensorCount++; + } + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + bs.removeSensorExitCondition(this); + if ((behav != null) && behav.enable) { + bs.activeWakeupOnSensorCount--; + } + } + + + /** + * Set the sensor that trigger this behavior + */ + void setTarget(Sensor sensor) { + this.armingSensor = sensor; + } + + /** + * Retrieves the Sensor object that caused the wakeup. + * + * @return the triggering Sensor object + * + * @exception IllegalStateException if not called from within + * a behavior's processStimulus method which was awoken by a sensor + * exit. + * + * @since Java 3D 1.2 + */ + public Sensor getTriggeringSensor() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnSensorExit0")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new + IllegalStateException(J3dI18N.getString("WakeupOnSensorExit0")); + } + } + return armingSensor; + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) {} +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnTransformChange.java b/src/classes/share/javax/media/j3d/WakeupOnTransformChange.java new file mode 100644 index 0000000..ffa4b17 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnTransformChange.java @@ -0,0 +1,80 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; +import java.util.ArrayList; + +/** + * Class specifying a wakeup when the transform within a specified + * TransformGroup changes + */ +public final class WakeupOnTransformChange extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 1; + + TransformGroupRetained transform; + + /** + * Constructs a new WakeupOnTransformChange criterion. + * + * @param node the TransformGroup node that will trigger a wakeup if + * its transform is modified + */ + public WakeupOnTransformChange(TransformGroup node) { + this.transform = (TransformGroupRetained)node.retained; + synchronized (transform) { + if (transform.transformChange == null) { + transform.transformChange = new WakeupIndexedList(1, + WakeupOnTransformChange.class, + WakeupOnTransformChange.COND_IN_BS_LIST, + transform.universe); + } + } + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Returns the TransformGroup node used in creating this WakeupCriterion + * @return the TransformGroup used in this criterion's construction + */ + public TransformGroup getTransformGroup(){ + return (TransformGroup)this.transform.source; + } + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + transform.addCondition(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + transform.removeCondition(this); + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) {} +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnViewPlatformEntry.java b/src/classes/share/javax/media/j3d/WakeupOnViewPlatformEntry.java new file mode 100644 index 0000000..4d0d8ae --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnViewPlatformEntry.java @@ -0,0 +1,133 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class specifying a wakeup when an active ViewPlatform intersects the + * specified boundary. + */ +public final class WakeupOnViewPlatformEntry extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + static final int BOUNDSENTRY_IN_BS_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + Bounds region; + + /** + * Transformed region + */ + Bounds transformedRegion; + + /** + * ViewPlatform that triggered this wakeup condition. + */ + ViewPlatformRetained triggeredVP; + + /** + * Constructs a new WakeupOnEntry criterion. + * @param region the region that will trigger a wakeup if a ViewPlatform + * intersects. + */ + public WakeupOnViewPlatformEntry(Bounds region) { + this.region = (Bounds)region.clone(); + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + /** + * Returns this object's bounds specification + * @return the bounds used in constructing this WakeupCriterion. + */ + public Bounds getBounds() { + return (Bounds)region.clone(); + } + + /** + * Retrieves the ViewPlatform node that caused the wakeup. + * + * @return the triggering ViewPlatform node + * + * @exception IllegalStateException if not called from within + * a behavior's processStimulus method that was awoken by a + * view platform entry. + * + * @since Java 3D 1.3 + */ + public ViewPlatform getTriggeringViewPlatform() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnViewPlatformEntry0")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnViewPlatformEntry0")); + } + } + + return (triggeredVP != null) ? (ViewPlatform)triggeredVP.source : null; + } + + /** + * Update the cached Transfrom Region, call from BehaviorStructure + * when TRANSFORM_CHANGED message get. Also call from buildTree. + */ + void updateTransformRegion(BehaviorRetained b) { + if (transformedRegion != null) { + transformedRegion.set(region); + } else { + // region is read only once initialize (since there is no + // set method for region). So no need to use cloneWithLock() + transformedRegion = (Bounds) region.clone(); + } + transformedRegion.transform(b.getCurrentLocalToVworld(null)); + } + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + updateTransformRegion(behav); + behav.wakeupArray[BehaviorRetained.WAKEUP_VP_ENTRY_INDEX]++; + behav.wakeupMask |= BehaviorRetained.WAKEUP_VP_ENTRY; + bs.addVPEntryCondition(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + behav.wakeupArray[BehaviorRetained.WAKEUP_VP_ENTRY_INDEX]--; + if (behav.wakeupArray[BehaviorRetained.WAKEUP_VP_ENTRY_INDEX] == 0) { + behav.wakeupMask &= ~BehaviorRetained.WAKEUP_VP_ENTRY; + } + bs.removeVPEntryCondition(this); + } + + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) { + // updateTransformRegion() is invoked in BehaviorStructure + // whenever Behavior transform change so there is + // no need to transform here every time. + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupOnViewPlatformExit.java b/src/classes/share/javax/media/j3d/WakeupOnViewPlatformExit.java new file mode 100644 index 0000000..12a95fa --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOnViewPlatformExit.java @@ -0,0 +1,135 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +/** + * Class specifying a wakeup when an active ViewPlatform no longer + * intersects the specified boundary. + */ +public final class WakeupOnViewPlatformExit extends WakeupCriterion { + + // different types of WakeupIndexedList that use in BehaviorStructure + static final int COND_IN_BS_LIST = 0; + static final int BOUNDSEXIT_IN_BS_LIST = 1; + + // total number of different IndexedUnorderedSet types + static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2; + + Bounds region; + + + /** + * Transformed region + */ + Bounds transformedRegion; + + /** + * ViewPlatform that triggered this wakeup condition. + */ + ViewPlatformRetained triggeredVP; + + /** + * Constructs a new WakeupOnExit criterion. + * @param region the region that will trigger a wakeup if a ViewPlatform + * no longer intersects. + */ + public WakeupOnViewPlatformExit(Bounds region) { + this.region = (Bounds)region.clone(); + WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES); + } + + + /** + * Returns this object's bounds specification + * @return the bounds used in constructing this WakeupCriterion. + */ + public Bounds getBounds() { + return (Bounds)region.clone(); + } + + /** + * Retrieves the ViewPlatform node that caused the wakeup. + * + * @return the triggering ViewPlatform node + * + * @exception IllegalStateException if not called from within + * a behavior's processStimulus method that was awoken by a + * view platform exit. + * + * @since Java 3D 1.3 + */ + public ViewPlatform getTriggeringViewPlatform() { + if (behav == null) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnViewPlatformExit0")); + } + + synchronized (behav) { + if (!behav.inCallback) { + throw new IllegalStateException(J3dI18N.getString("WakeupOnViewPlatformExit0")); + } + } + + return (triggeredVP != null) ? (ViewPlatform)triggeredVP.source : null; + } + + /** + * Update the cached Transfrom Region, call from BehaviorStructure + * when TRANSFORM_CHANGED message get. Also call from buildTree. + */ + void updateTransformRegion(BehaviorRetained b) { + if (transformedRegion != null) { + transformedRegion.set(region); + } else { + // region is read only once initialize (since there is no + // set method for region). So no need to use cloneWithLock() + transformedRegion = (Bounds) region.clone(); + } + transformedRegion.transform(b.getCurrentLocalToVworld(null)); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to add wakeupCondition to behavior structure. + */ + void addBehaviorCondition(BehaviorStructure bs) { + updateTransformRegion(behav); + behav.wakeupArray[BehaviorRetained.WAKEUP_VP_EXIT_INDEX]++; + behav.wakeupMask |= BehaviorRetained.WAKEUP_VP_EXIT; + bs.addVPExitCondition(this); + } + + + /** + * This is a callback from BehaviorStructure. It is + * used to remove wakeupCondition from behavior structure. + */ + void removeBehaviorCondition(BehaviorStructure bs) { + bs.removeVPExitCondition(this); + behav.wakeupArray[BehaviorRetained.WAKEUP_VP_EXIT_INDEX]--; + if (behav.wakeupArray[BehaviorRetained.WAKEUP_VP_EXIT_INDEX] == 0) { + behav.wakeupMask &= ~BehaviorRetained.WAKEUP_VP_EXIT; + } + } + + + /** + * Perform task in addBehaviorCondition() that has to be + * set every time the condition met. + */ + void resetBehaviorCondition(BehaviorStructure bs) { + // updateTransformRegion() is invoked in BehaviorStructure + // whenever Behavior transform change so there is + // no need to transform here every time. + } +} diff --git a/src/classes/share/javax/media/j3d/WakeupOr.java b/src/classes/share/javax/media/j3d/WakeupOr.java new file mode 100644 index 0000000..c9ecb6a --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOr.java @@ -0,0 +1,100 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; + +/** + * Class specifying any number of wakeup conditions ORed together. + * This WakeupCondition object specifies that Java 3D should awaken + * this Behavior when any of the WakeupCondition's constituent wakeup + * criteria becomes valid. + * <p> + * Note that a unique WakeupCriterion object must be used + * for each individual element in the array of wakeup criteria. + */ + +public final class WakeupOr extends WakeupCondition { + + WakeupCriterion conditions[]; + + /** + * Constructs a new WakeupOr criterion. + * @param conditions a vector of individual Wakeup conditions + */ + public WakeupOr(WakeupCriterion conditions[]) { + this.conditions = new WakeupCriterion[conditions.length]; + + for(int i = 0; i < conditions.length; i++){ + this.conditions[i] = conditions[i]; + } + } + + /** + * This sets the bit for the given child, then checks if the full condition is met + */ + void setConditionMet(int id, Boolean checkSchedulingRegion) { + if (parent == null) { + super.setConditionMet(this.id, checkSchedulingRegion); + } else { + parent.setConditionMet(this.id, checkSchedulingRegion); + } + } + + + /** + * This gets called when this condition is added to the AndOr tree. + */ + void buildTree(WakeupCondition parent, int id, BehaviorRetained b) { + super.buildTree(parent, id, b); + + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].buildTree(this, i, b); + } + } + } + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure lists + */ + void cleanTree(BehaviorStructure bs) { + for (int i=0; i<conditions.length; i++) { + conditions[i].cleanTree(bs); + } + } + + void reInsertElapseTimeCond() { + super.reInsertElapseTimeCond(); + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].reInsertElapseTimeCond(); + } + } + } + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure. + */ + void resetTree() { + super.resetTree(); + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].resetTree(); + } + } + } + +} diff --git a/src/classes/share/javax/media/j3d/WakeupOrOfAnds.java b/src/classes/share/javax/media/j3d/WakeupOrOfAnds.java new file mode 100644 index 0000000..1f59841 --- /dev/null +++ b/src/classes/share/javax/media/j3d/WakeupOrOfAnds.java @@ -0,0 +1,100 @@ +/* + * $RCSfile$ + * + * Copyright (c) 2004 Sun Microsystems, Inc. All rights reserved. + * + * Use is subject to license terms. + * + * $Revision$ + * $Date$ + * $State$ + */ + +package javax.media.j3d; + +import java.util.Vector; + +/** + * Class specifying any number of AND wakeup conditions ORed together. + * This WakeupCondition object specifies that Java 3D should awaken + * this Behavior when any of the WakeupCondition's constituent WakeupAnd + * conditions becomes valid. + * <p> + * Note that a unique WakeupCriterion object must be used for each + * individual element in the set of arrays specified by the array of + * WakeupAnd objects. + */ + +public final class WakeupOrOfAnds extends WakeupCondition { + + WakeupAnd conditions[]; + + /** + * Constructs a new WakeupOrOfAnds criterion. + * @param conditions a vector of individual Wakeup conditions + */ + public WakeupOrOfAnds(WakeupAnd conditions[]) { + this.conditions = new WakeupAnd[conditions.length]; + for(int i = 0; i < conditions.length; i++){ + this.conditions[i] = conditions[i]; + } + } + + + /** + * This sets the bit for the given child, then checks if the full condition is met + */ + void setConditionMet(int id, Boolean checkSchedulingRegion) { + if (parent == null) { + super.setConditionMet(this.id, checkSchedulingRegion); + } else { + parent.setConditionMet(this.id, checkSchedulingRegion); + } + } + + /** + * This gets called when this condition is added to the AndOr tree. + */ + void buildTree(WakeupCondition parent, int id, BehaviorRetained b) { + super.buildTree(parent, id, b); + + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].buildTree(this, i, b); + } + } + } + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure lists + */ + void cleanTree(BehaviorStructure bs) { + for (int i=0; i<conditions.length; i++) { + conditions[i].cleanTree(bs); + } + } + + void reInsertElapseTimeCond() { + super.reInsertElapseTimeCond(); + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].reInsertElapseTimeCond(); + } + } + } + + /** + * This goes through the AndOr tree to remove the various criterion from the + * BehaviorStructure. + */ + void resetTree() { + super.resetTree(); + for(int i = 0; i < conditions.length; i++) { + if (conditions[i] != null) { + conditions[i].resetTree(); + } + } + } + +} |