001: /*
002: * $RCSfile: ConeSoundRetained.java,v $
003: *
004: * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
006: *
007: * This code is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License version 2 only, as
009: * published by the Free Software Foundation. Sun designates this
010: * particular file as subject to the "Classpath" exception as provided
011: * by Sun in the LICENSE file that accompanied this code.
012: *
013: * This code is distributed in the hope that it will be useful, but WITHOUT
014: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: * version 2 for more details (a copy is included in the LICENSE file that
017: * accompanied this code).
018: *
019: * You should have received a copy of the GNU General Public License version
020: * 2 along with this work; if not, write to the Free Software Foundation,
021: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
022: *
023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
024: * CA 95054 USA or visit www.sun.com if you need additional information or
025: * have any questions.
026: *
027: * $Revision: 1.5 $
028: * $Date: 2008/02/28 20:17:20 $
029: * $State: Exp $
030: */
031:
032: package javax.media.j3d;
033:
034: import javax.vecmath.Vector3f;
035: import javax.vecmath.Vector3d;
036: import javax.vecmath.Point2f;
037: import javax.vecmath.Point3f;
038:
039: /**
040: * A ConeSoundRetained node defines a point sound source located at some
041: * location
042: * in space whose amplitude is constrained not only by maximum and minimum
043: * amplitude
044: * spheres but by two concentric cone volumes directed down an vector radiating
045: * from the sound's location.
046: */
047:
048: class ConeSoundRetained extends PointSoundRetained {
049: /**
050: * The Cone Sound's direction vector. This is the cone axis.
051: */
052: Vector3f direction = new Vector3f(0.0f, 0.0f, 1.0f);
053:
054: // The transformed direction of this sound
055: Vector3f xformDirection = new Vector3f(0.0f, 0.0f, 1.0f);
056:
057: // Sound's gain is attenuated for listener locations off-angle from
058: // the source source direction.
059: // This can be set of three numbers:
060: // angular distance in radians
061: // gain scale factor
062: // filtering (currently the only filtering supported is lowpass)
063:
064: // For now the only supported filterType will be LOW_PASS frequency cutoff.
065: // At some time full FIR filtering will be supported.
066: static final int NO_FILTERING = -1;
067: static final int LOW_PASS = 1;
068:
069: // Pairs of distances and gain scale factors that define piecewise linear
070: // gain BACK attenuation between each pair.
071: // These are used for defining elliptical attenuation regions.
072: float[] backAttenuationDistance = null;
073: float[] backAttenuationGain = null;
074:
075: float[] angularDistance = { 0.0f, ((float) (Math.PI) * 0.5f) };
076: float[] angularGain = { 1.0f, 0.0f };
077: int filterType = NO_FILTERING;
078: float[] frequencyCutoff = { Sound.NO_FILTER, Sound.NO_FILTER };
079:
080: ConeSoundRetained() {
081: this .nodeType = NodeRetained.CONESOUND;
082: }
083:
084: // *********************
085: //
086: // Distance Gain methods
087: //
088: // *********************
089:
090: /**
091: * Sets this sound's distance gain elliptical attenuation -
092: * where gain scale factor is applied to sound based on distance listener
093: * is from sound source.
094: * @param frontAttenuation defined by pairs of (distance,gain-scale-factor)
095: * @param backAttenuation defined by pairs of (distance,gain-scale-factor)
096: * @exception CapabilityNotSetException if appropriate capability is
097: * not set and this object is part of live or compiled scene graph
098: */
099: void setDistanceGain(Point2f[] frontAttenuation,
100: Point2f[] backAttenuation) {
101:
102: this .setDistanceGain(frontAttenuation);
103: this .setBackDistanceGain(backAttenuation);
104: }
105:
106: /**
107: * Sets this sound's distance gain attenuation as an array of Point2fs.
108: * @param frontDistance array of monotonically-increasing floats
109: * @param frontGain array of non-negative scale factors
110: * @param backDistance array of monotonically-increasing floats
111: * @param backGain array of non-negative scale factors
112: * @exception CapabilityNotSetException if appropriate capability is
113: * not set and this object is part of live or compiled scene graph
114: */
115: void setDistanceGain(float[] frontDistance, float[] frontGain,
116: float[] backDistance, float[] backGain) {
117: this .setDistanceGain(frontDistance, frontGain);
118: this .setBackDistanceGain(backDistance, backGain);
119: }
120:
121: /**
122: * Sets this sound's back distance gain attenuation - where gain scale
123: * factor is applied to sound based on distance listener along the negative
124: * sound direction axis from sound source.
125: * @param attenuation defined by pairs of (distance,gain-scale-factor)
126: * @exception CapabilityNotSetException if appropriate capability is
127: * not set and this object is part of live or compiled scene graph
128: */
129: void setBackDistanceGain(Point2f[] attenuation) {
130: // if attenuation array null set both attenuation components to null
131: if (attenuation == null) {
132: this .backAttenuationDistance = null;
133: this .backAttenuationGain = null;
134: } else {
135: int attenuationLength = attenuation.length;
136: if (attenuationLength == 0) {
137: this .backAttenuationDistance = null;
138: this .backAttenuationGain = null;
139: } else {
140: this .backAttenuationDistance = new float[attenuationLength];
141: this .backAttenuationGain = new float[attenuationLength];
142: for (int i = 0; i < attenuationLength; i++) {
143: this .backAttenuationDistance[i] = attenuation[i].x;
144: this .backAttenuationGain[i] = attenuation[i].y;
145: }
146: }
147: }
148: dispatchAttribChange(BACK_DISTANCE_GAIN_DIRTY_BIT, attenuation);
149: if (source != null && source.isLive()) {
150: notifySceneGraphChanged(false);
151: }
152: }
153:
154: /**
155: * Sets this sound's back distance gain attenuation as an array of Point2fs.
156: * @param distance array of monotonically-increasing floats
157: * @param gain array of non-negative scale factors
158: * @exception CapabilityNotSetException if appropriate capability is
159: * not set and this object is part of live or compiled scene graph
160: */
161: void setBackDistanceGain(float[] distance, float[] gain) {
162: int distanceLength = 0;
163: // if distance or gain arrays are null then treat both as null
164: if (distance == null || gain == null) {
165: this .backAttenuationDistance = null;
166: this .backAttenuationGain = null;
167: } else {
168: // now process the back attenuation values
169: int gainLength = gain.length;
170: distanceLength = distance.length;
171: if (distanceLength == 0 || gainLength == 0) {
172: this .backAttenuationDistance = null;
173: this .backAttenuationGain = null;
174: } else {
175: this .backAttenuationDistance = new float[distanceLength];
176: this .backAttenuationGain = new float[distanceLength];
177: // Copy the distance array into nodes field
178: System
179: .arraycopy(distance, 0,
180: this .backAttenuationDistance, 0,
181: distanceLength);
182: // Copy the gain array an array of same length as the distance array
183: if (distanceLength <= gainLength) {
184: System.arraycopy(gain, 0, this .backAttenuationGain,
185: 0, distanceLength);
186: } else {
187: System.arraycopy(gain, 0, this .backAttenuationGain,
188: 0, gainLength);
189: // Extend gain array to length of distance array
190: // replicate last gain values.
191: for (int i = gainLength; i < distanceLength; i++) {
192: this .backAttenuationGain[i] = gain[gainLength - 1];
193: }
194: }
195: }
196: }
197:
198: Point2f[] attenuation = new Point2f[distanceLength];
199: for (int i = 0; i < distanceLength; i++) {
200: attenuation[i] = new Point2f(
201: this .backAttenuationDistance[i],
202: this .backAttenuationGain[i]);
203: }
204: dispatchAttribChange(BACK_DISTANCE_GAIN_DIRTY_BIT, attenuation);
205: if (source != null && source.isLive()) {
206: notifySceneGraphChanged(false);
207: }
208: }
209:
210: /**
211: * Gets this sound's elliptical distance attenuation
212: * @param frontAttenuation arrays containing forward distances attenuation pairs
213: * @param backAttenuation arrays containing backward distances attenuation pairs
214: * @exception CapabilityNotSetException if appropriate capability is
215: * not set and this object is part of live or compiled scene graph
216: */
217: void getDistanceGain(Point2f[] frontAttenuation,
218: Point2f[] backAttenuation) {
219: this .getDistanceGain(frontAttenuation);
220: this .getBackDistanceGain(backAttenuation);
221: }
222:
223: /**
224: * Gets this sound's elliptical distance gain attenuation values in separate arrays
225: * @param frontDistance array of float distances along the sound axis
226: * @param fronGain array of non-negative scale factors associated with front distances
227: * @param backDistance array of float negative distances along the sound axis
228: * @param backGain array of non-negative scale factors associated with back distances
229: * @exception CapabilityNotSetException if appropriate capability is
230: * not set and this object is part of live or compiled scene graph
231: */
232: void getDistanceGain(float[] frontDistance, float[] frontGain,
233: float[] backDistance, float[] backGain) {
234: this .getDistanceGain(frontDistance, frontGain);
235: this .getBackDistanceGain(backDistance, backGain);
236: }
237:
238: /**
239: * Retieves sound's back distance attenuation
240: * Put the contents of the two separate distance and gain arrays into
241: * an array of Point2f.
242: * @param attenuation containing distance attenuation pairs
243: */
244: void getBackDistanceGain(Point2f[] attenuation) {
245: // Write into arrays passed in, don't do a new
246: if (attenuation == null)
247: return;
248: if (this .backAttenuationDistance == null
249: || this .backAttenuationGain == null)
250: return;
251: // These two array length should be the same
252: // can assume lengths are non-zero
253: int distanceLength = this .backAttenuationDistance.length;
254: int attenuationLength = attenuation.length;
255: if (distanceLength < attenuationLength)
256: distanceLength = attenuationLength;
257: for (int i = 0; i < distanceLength; i++) {
258: attenuation[i].x = this .backAttenuationDistance[i];
259: attenuation[i].y = this .backAttenuationGain[i];
260: }
261: }
262:
263: /**
264: * Retieves this sound's back attenuation distance and gain arrays,
265: * returned in separate arrays.
266: * @param distance array of monotonically-increasing floats.
267: * @param gain array of amplitude scale factors associated with distances.
268: */
269: void getBackDistanceGain(float[] distance, float[] gain) {
270: // write into arrays passed in, don't do a new
271: if (distance == null || gain == null)
272: return;
273: if (this .backAttenuationDistance == null
274: || this .backAttenuationGain == null)
275: return;
276: // backAttenuationDistance and backAttenuationGain array length should
277: // be the same
278: // can assume length is non-zero
279: int attenuationLength = this .backAttenuationDistance.length;
280: int distanceLength = distance.length;
281: if (attenuationLength > distanceLength)
282: attenuationLength = distanceLength;
283: System.arraycopy(this .backAttenuationDistance, 0, distance, 0,
284: attenuationLength);
285: attenuationLength = this .backAttenuationGain.length;
286: int gainLength = gain.length;
287: if (attenuationLength > gainLength)
288: attenuationLength = gainLength;
289: System.arraycopy(this .backAttenuationGain, 0, gain, 0,
290: attenuationLength);
291: }
292:
293: // *********************
294: //
295: // Direction Methods
296: //
297: // *********************
298:
299: /**
300: * Sets this sound's direction from the vector provided.
301: * @param direction the new direction
302: */
303: void setDirection(Vector3f direction) {
304: if (staticTransform != null) {
305: staticTransform.transform.transform(direction,
306: this .direction);
307: } else {
308: this .direction.set(direction);
309: }
310: dispatchAttribChange(DIRECTION_DIRTY_BIT, (new Vector3f(
311: this .direction)));
312:
313: if (source != null && source.isLive()) {
314: notifySceneGraphChanged(false);
315: }
316: }
317:
318: /**
319: * Sets this sound's direction from the three values provided.
320: * @param x the new x direction
321: * @param y the new y direction
322: * @param z the new z direction
323: */
324: void setDirection(float x, float y, float z) {
325: direction.x = x;
326: direction.y = y;
327: direction.z = z;
328: if (staticTransform != null) {
329: staticTransform.transform.transform(direction);
330: }
331: dispatchAttribChange(DIRECTION_DIRTY_BIT, (new Vector3f(
332: direction)));
333:
334: if (source != null && source.isLive()) {
335: notifySceneGraphChanged(false);
336: }
337: }
338:
339: /**
340: * Retrieves this sound's direction and places it in the
341: * vector provided.
342: * @return direction vector (axis of cones)
343: */
344: void getDirection(Vector3f direction) {
345: if (staticTransform != null) {
346: Transform3D invTransform = staticTransform
347: .getInvTransform();
348: invTransform.transform(this .direction, direction);
349: } else {
350: direction.set(this .direction);
351: }
352: }
353:
354: void getXformDirection(Vector3f direction) {
355: direction.set(this .xformDirection);
356: }
357:
358: // ***************************
359: //
360: // Angular Attenuation
361: //
362: // ***************************
363:
364: /**
365: * Sets this sound's angular gain attenuation (not including filter)
366: * @param attenuation array containing angular distance and gain
367: */
368: void setAngularAttenuation(Point2f[] attenuation) {
369: int attenuationLength = 0;
370: this .filterType = NO_FILTERING;
371: if (attenuation == null) {
372: this .angularDistance = null;
373: this .angularGain = null;
374: } else {
375: attenuationLength = attenuation.length;
376: if (attenuationLength == 0) {
377: this .angularDistance = null;
378: this .angularGain = null;
379: } else {
380: this .angularDistance = new float[attenuationLength];
381: this .angularGain = new float[attenuationLength];
382: for (int i = 0; i < attenuationLength; i++) {
383: this .angularDistance[i] = attenuation[i].x;
384: this .angularGain[i] = attenuation[i].y;
385: }
386: } // lengths not zero
387: } // arrays not null
388: Point3f[] attenuation3f = new Point3f[attenuationLength];
389: for (int i = 0; i < attenuationLength; i++) {
390: attenuation3f[i] = new Point3f(this .angularDistance[i],
391: this .angularGain[i], Sound.NO_FILTER);
392: }
393: dispatchAttribChange(ANGULAR_ATTENUATION_DIRTY_BIT,
394: attenuation3f);
395:
396: if (source != null && source.isLive()) {
397: notifySceneGraphChanged(false);
398: }
399: }
400:
401: /**
402: * Sets this sound's angular attenuation including both gain and filter.
403: * @param attenuation array containing angular distance, gain and filter
404: */
405: void setAngularAttenuation(Point3f[] attenuation) {
406: if (attenuation == null) {
407: this .angularDistance = null;
408: this .angularGain = null;
409: this .frequencyCutoff = null;
410: this .filterType = NO_FILTERING;
411: } else {
412: int attenuationLength = attenuation.length;
413: if (attenuationLength == 0) {
414: this .angularDistance = null;
415: this .angularGain = null;
416: this .frequencyCutoff = null;
417: this .filterType = NO_FILTERING;
418: } else {
419: this .angularDistance = new float[attenuationLength];
420: this .angularGain = new float[attenuationLength];
421: this .frequencyCutoff = new float[attenuationLength];
422: this .filterType = LOW_PASS;
423: for (int i = 0; i < attenuationLength; i++) {
424: this .angularDistance[i] = attenuation[i].x;
425: this .angularGain[i] = attenuation[i].y;
426: this .frequencyCutoff[i] = attenuation[i].z;
427: }
428: } // lengths not zero
429: } // arrays not null
430: dispatchAttribChange(ANGULAR_ATTENUATION_DIRTY_BIT, attenuation);
431: if (source != null && source.isLive()) {
432: notifySceneGraphChanged(false);
433: }
434: }
435:
436: /**
437: * Sets angular attenuation including gain and filter using separate arrays
438: * @param distance array containing angular distance
439: * @param filter array containing angular low-pass frequency cutoff values
440: */
441: void setAngularAttenuation(float[] distance, float[] gain,
442: float[] filter) {
443: int distanceLength = 0;
444: if (distance == null || gain == null || filter == null) {
445: this .angularDistance = null;
446: this .angularGain = null;
447: this .frequencyCutoff = null;
448: this .filterType = NO_FILTERING;
449: } else {
450: distanceLength = distance.length;
451: int gainLength = gain.length;
452: if (distanceLength == 0 || gainLength == 0) {
453: this .angularDistance = null;
454: this .angularGain = null;
455: this .frequencyCutoff = null;
456: this .filterType = NO_FILTERING;
457: } else {
458: int filterLength = filter.length;
459: this .angularDistance = new float[distanceLength];
460: this .angularGain = new float[distanceLength];
461: this .frequencyCutoff = new float[distanceLength];
462: // Copy the distance array into nodes field
463: System.arraycopy(distance, 0, this .angularDistance, 0,
464: distanceLength);
465: // Copy the gain array an array of same length as the distance array
466: if (distanceLength <= gainLength) {
467: System.arraycopy(gain, 0, this .angularGain, 0,
468: distanceLength);
469: } else {
470: System.arraycopy(gain, 0, this .angularGain, 0,
471: gainLength);
472: /**
473: * Extend gain array to length of distance array by
474: * replicate last gain values.
475: */
476: for (int i = gainLength; i < distanceLength; i++) {
477: this .angularGain[i] = gain[gainLength - 1];
478: }
479: }
480: // Copy the filter array an array of same length as the distance array
481: if (filterLength == 0)
482: this .filterType = NO_FILTERING;
483: else {
484: this .filterType = LOW_PASS;
485: if (distanceLength <= filterLength) {
486: System
487: .arraycopy(filter, 0,
488: this .frequencyCutoff, 0,
489: distanceLength);
490: } else {
491: System.arraycopy(filter, 0,
492: this .frequencyCutoff, 0, filterLength);
493: // Extend filter array to length of distance array by
494: // replicate last filter values.
495: for (int i = filterLength; i < distanceLength; i++) {
496: this .frequencyCutoff[i] = filter[filterLength - 1];
497: }
498: }
499: }
500: } // length not zero
501: } // arrays not null
502: Point3f[] attenuation = new Point3f[distanceLength];
503: for (int i = 0; i < distanceLength; i++) {
504: if (this .filterType != NO_FILTERING) {
505: attenuation[i] = new Point3f(this .angularDistance[i],
506: this .angularGain[i], this .frequencyCutoff[i]);
507: } else {
508: attenuation[i] = new Point3f(this .angularDistance[i],
509: this .angularGain[i], Sound.NO_FILTER);
510: }
511: }
512: dispatchAttribChange(ANGULAR_ATTENUATION_DIRTY_BIT, attenuation);
513: if (source != null && source.isLive()) {
514: notifySceneGraphChanged(false);
515: }
516: }
517:
518: /**
519: * Retrieves angular attenuation array length.
520: * All arrays are forced to same size
521: * @exception CapabilityNotSetException if appropriate capability is
522: * not set and this object is part of live or compiled scene graph
523: */
524: int getAngularAttenuationLength() {
525:
526: if (angularDistance == null)
527: return 0;
528: else
529: return (this .angularDistance.length);
530: }
531:
532: /**
533: * Retrieves angular attenuation including gain and filter in a single array
534: * @param attenuation applied to gain when listener is between cones
535: */
536: void getAngularAttenuation(Point3f[] attenuation) {
537: /// use attenuation array allocated by user - don't new it
538: // The three angular attenuation arrays length should be the same
539: if (this .angularDistance == null || this .angularGain == null)
540: return;
541: if (attenuation == null)
542: return;
543: int distanceLength = this .angularDistance.length;
544: if (attenuation.length < distanceLength)
545: distanceLength = attenuation.length;
546: for (int i = 0; i < distanceLength; i++) {
547: attenuation[i].x = this .angularDistance[i];
548: attenuation[i].y = this .angularGain[i];
549: if (filterType == NO_FILTERING
550: || this .frequencyCutoff == null)
551: attenuation[i].z = Sound.NO_FILTER;
552: else if (filterType == LOW_PASS)
553: attenuation[i].z = this .frequencyCutoff[i];
554: }
555: }
556:
557: /**
558: * Retrieves angular attenuation including gain and filter
559: * returned as separate arrays
560: * @param distance array containing angular distance
561: * @param gain array containing angular gain attenuation
562: * @param filter array containing angular low-pass frequency cutoff values
563: */
564: void getAngularAttenuation(float[] distance, float[] gain,
565: float[] filter) {
566: // use attenuation array allocated by user - don't new it
567: if (distance == null || gain == null || filter == null)
568: return;
569: if (this .angularDistance == null || this .angularGain == null)
570: return;
571: int distanceLength = this .angularDistance.length;
572: if (distance.length < distanceLength)
573: distanceLength = distance.length;
574: System.arraycopy(this .angularDistance, 0, distance, 0,
575: distanceLength);
576:
577: int gainLength = this .angularGain.length;
578: if (gain.length < gainLength)
579: gainLength = gain.length;
580: System.arraycopy(this .angularGain, 0, gain, 0, gainLength);
581:
582: int filterLength = 0;
583: if (this .frequencyCutoff == null || filterType == NO_FILTERING)
584: filterLength = filter.length;
585: else {
586: filterLength = this .frequencyCutoff.length;
587: if (filter.length < filterLength)
588: filterLength = filter.length;
589: }
590: if (filterType == NO_FILTERING || this .frequencyCutoff == null) {
591: for (int i = 0; i < filterLength; i++)
592: filter[i] = Sound.NO_FILTER;
593: }
594: if (filterType == LOW_PASS) {
595: System.arraycopy(this .frequencyCutoff, 0, filter, 0,
596: filterLength);
597: }
598: }
599:
600: /**
601: * This updates the Direction fields of cone sound.
602: *
603: * Neither Angular gain Attenuation and Filtering fields, nor
604: * back distance gain not maintained in mirror object
605: */
606: void updateMirrorObject(Object[] objs) {
607: if (debugFlag)
608: debugPrint("PointSoundRetained:updateMirrorObj()");
609: Transform3D trans = null;
610: int component = ((Integer) objs[1]).intValue();
611: int numSnds = ((Integer) objs[2]).intValue();
612: SoundRetained[] mSnds = (SoundRetained[]) objs[3];
613: if (component == -1) {
614: // update every field
615: initMirrorObject(((ConeSoundRetained) objs[2]));
616: return;
617: }
618: if ((component & DIRECTION_DIRTY_BIT) != 0) {
619: for (int i = 0; i < numSnds; i++) {
620: ConeSoundRetained cone = (ConeSoundRetained) mSnds[i];
621: cone.direction = (Vector3f) objs[4];
622: cone.getLastLocalToVworld().transform(cone.direction,
623: cone.xformDirection);
624: cone.xformDirection.normalize();
625: }
626: }
627: // call the parent's mirror object update routine
628: super .updateMirrorObject(objs);
629: }
630:
631: synchronized void initMirrorObject(ConeSoundRetained ms) {
632: super .initMirrorObject(ms);
633: ms.direction.set(this .direction);
634: ms.xformDirection.set(this .xformDirection);
635: }
636:
637: // Called on the mirror object
638: void updateTransformChange() {
639: Transform3D lastLocalToVworld = getLastLocalToVworld();
640:
641: super .updateTransformChange();
642: lastLocalToVworld.transform(direction, xformDirection);
643: xformDirection.normalize();
644: // set flag looked at by Scheduler to denote Transform change
645: // this flag will force resneding transformed direction to AudioDevice
646: if (debugFlag)
647: debugPrint("ConeSound xformDirection is ("
648: + xformDirection.x + ", " + xformDirection.y + ", "
649: + xformDirection.z + ")");
650: }
651:
652: void mergeTransform(TransformGroupRetained xform) {
653: super.mergeTransform(xform);
654: xform.transform.transform(direction);
655: }
656: }
|