001: /*
002: * $RCSfile: Sphere.java,v $
003: *
004: * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * - Redistribution of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * - Redistribution in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * Neither the name of Sun Microsystems, Inc. or the names of
019: * contributors may be used to endorse or promote products derived
020: * from this software without specific prior written permission.
021: *
022: * This software is provided "AS IS," without a warranty of any
023: * kind. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND
024: * WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY,
025: * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY
026: * EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL
027: * NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF
028: * USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
029: * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR
030: * ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL,
031: * CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND
032: * REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF OR
033: * INABILITY TO USE THIS SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
034: * POSSIBILITY OF SUCH DAMAGES.
035: *
036: * You acknowledge that this software is not designed, licensed or
037: * intended for use in the design, construction, operation or
038: * maintenance of any nuclear facility.
039: *
040: * $Revision: 1.6 $
041: * $Date: 2007/04/24 18:50:59 $
042: * $State: Exp $
043: */
044:
045: package com.sun.j3d.utils.geometry;
046:
047: import com.sun.j3d.utils.geometry.*;
048: import java.io.*;
049: import java.util.*;
050: import javax.media.j3d.*;
051: import javax.vecmath.*;
052: import java.math.*;
053:
054: /**
055: * Sphere is a geometry primitive created with a given radius and resolution.
056: * It is centered at the origin.
057: * <p>
058: * When a texture is applied to a Sphere, it is mapped CCW from the back
059: * of the sphere.
060: * <p>
061: * By default all primitives with the same parameters share their
062: * geometry (e.g., you can have 50 shperes in your scene, but the
063: * geometry is stored only once). A change to one primitive will
064: * effect all shared nodes. Another implication of this
065: * implementation is that the capabilities of the geometry are shared,
066: * and once one of the shared nodes is live, the capabilities cannot
067: * be set. Use the GEOMETRY_NOT_SHARED flag if you do not wish to
068: * share geometry among primitives with the same parameters.
069: */
070:
071: public class Sphere extends Primitive {
072:
073: /**
074: * Sphere shape identifier, used by <code>getShape</code>.
075: *
076: * @see Sphere#getShape
077: */
078: public static final int BODY = 0;
079:
080: static final int MID_REZ_DIV = 16;
081: float radius;
082: int divisions;
083:
084: /**
085: * Constructs a Sphere of a given radius. Normals are generated
086: * by default, texture coordinates are not. The resolution defaults to
087: * 15 divisions along sphere's axes. Appearance defaults to white.
088: * @param radius Radius
089: */
090: public Sphere(float radius) {
091: this (radius, GENERATE_NORMALS, MID_REZ_DIV);
092: }
093:
094: /**
095: * Constructs a default Sphere of radius of 1.0. Normals are generated
096: * by default, texture coordinates are not.
097: * Resolution defaults to 15 divisions. Appearance defaults to white.
098: */
099: public Sphere() {
100: this (1.0f, GENERATE_NORMALS, MID_REZ_DIV);
101: }
102:
103: /**
104: * Constructs a Sphere of a given radius and appearance.
105: * Normals are generated by default, texture coordinates are not.
106: * @param radius Radius
107: * @param ap Appearance
108: */
109:
110: public Sphere(float radius, Appearance ap) {
111: this (radius, GENERATE_NORMALS, MID_REZ_DIV, ap);
112: }
113:
114: /**
115: * Constructs a Sphere of a given radius and appearance with
116: * additional parameters specified by the Primitive flags.
117: * @param radius Radius
118: * @param primflags
119: * @param ap appearance
120: */
121: public Sphere(float radius, int primflags, Appearance ap) {
122: this (radius, primflags, MID_REZ_DIV, ap);
123: }
124:
125: /**
126: * Constructs a Sphere of a given radius and number of divisions
127: * with additional parameters specified by the Primitive flags.
128: * Appearance defaults to white.
129: * @param radius Radius
130: * @param divisions Divisions
131: * @param primflags Primflags
132: */
133: public Sphere(float radius, int primflags, int divisions) {
134: this (radius, primflags, divisions, null);
135: }
136:
137: /**
138: * Obtains Sphere's shape node that contains the geometry.
139: * This allows users to modify the appearance or geometry.
140: * @param partId The part to return (must be BODY for Spheres)
141: * @return The Shape3D object associated with the partId. If an
142: * invalid partId is passed in, null is returned.
143: */
144: public Shape3D getShape(int partId) {
145: if (partId != BODY)
146: return null;
147: // return (Shape3D)((Group)getChild(0)).getChild(BODY);
148: return (Shape3D) getChild(BODY);
149: }
150:
151: /** Obtains Sphere's shape node that contains the geometry.
152: */
153: public Shape3D getShape() {
154: // return (Shape3D)((Group)getChild(0)).getChild(BODY);
155: return (Shape3D) getChild(BODY);
156: }
157:
158: /** Sets appearance of the Sphere.
159: */
160: public void setAppearance(Appearance ap) {
161: // ((Shape3D)((Group)getChild(0)).getChild(BODY)).setAppearance(ap);
162: ((Shape3D) getChild(BODY)).setAppearance(ap);
163: }
164:
165: /**
166: * Gets the appearance of the specified part of the sphere.
167: *
168: * @param partId identifier for a given subpart of the sphere
169: *
170: * @return The appearance object associated with the partID. If an
171: * invalid partId is passed in, null is returned.
172: *
173: * @since Java 3D 1.2.1
174: */
175: public Appearance getAppearance(int partId) {
176: if (partId != BODY)
177: return null;
178: return getShape(partId).getAppearance();
179: }
180:
181: /**
182: * Constructs a customized Sphere of a given radius,
183: * number of divisions, and appearance, with additional parameters
184: * specified by the Primitive flags. The resolution is defined in
185: * terms of number of subdivisions along the sphere's axes. More
186: * divisions lead to more finely tesselated objects.
187: * <p>
188: * If the appearance is null, the sphere defaults to a white appearance.
189: */
190: public Sphere(float radius, int primflags, int divisions,
191: Appearance ap) {
192: super ();
193:
194: int sign;
195: int n, nstep;
196:
197: this .radius = radius;
198: this .divisions = divisions;
199:
200: /*
201: * The sphere algorithm evaluates spherical angles along regular
202: * units. For each spherical coordinate, (theta, rho), a (x,y,z) is
203: * evaluated (along with the normals and texture coordinates).
204: *
205: * The spherical angles theta varies from 0 to 2pi and rho from 0
206: * to pi. Sample points depends on the number of divisions.
207: */
208:
209: flags = primflags;
210: boolean texCoordYUp = (flags & GENERATE_TEXTURE_COORDS_Y_UP) != 0;
211:
212: //Depending on whether normal inward bit is set.
213: if ((flags & GENERATE_NORMALS_INWARD) != 0) {
214: sign = -1;
215: } else {
216: sign = 1;
217: }
218:
219: if (divisions < 4) {
220: nstep = 1;
221: n = 4;
222: } else {
223: int mod = divisions % 4;
224: if (mod == 0) {
225: n = divisions;
226: } else {
227: n = divisions + (4 - mod);
228: }
229: nstep = n / 4;
230: }
231:
232: GeomBuffer cache = getCachedGeometry(Primitive.SPHERE, radius,
233: 0.0f, 0.0f, divisions, 0, primflags);
234:
235: Shape3D shape;
236:
237: if (cache != null) {
238: shape = new Shape3D(cache.getComputedGeometry());
239: numVerts += cache.getNumVerts();
240: numTris += cache.getNumTris();
241: } else {
242: // buffer size = 8*(1 + 2E{i} + (nstep+1))
243: // where E{i} = sum of i = 2 ... nstep
244: GeomBuffer gbuf = new GeomBuffer(8 * nstep * (nstep + 2));
245:
246: for (int i = 0; i < 4; i++) {
247: buildQuadrant(gbuf, i * Math.PI / 2, (i + 1) * Math.PI
248: / 2, sign, nstep, n, true);
249: buildQuadrant(gbuf, i * Math.PI / 2, (i + 1) * Math.PI
250: / 2, sign, nstep, n, false);
251: }
252:
253: // Fix to Issue 411. Java 3D prefers images used for texture mapping to be Y-up
254: if (texCoordYUp) {
255: TexCoord2f[] texCoords = gbuf.getTexCoords();
256: if (texCoords != null) {
257: for (int ii = 0; ii < texCoords.length; ii++) {
258: texCoords[ii].y = 1.0f - texCoords[ii].y;
259: }
260: }
261: }
262:
263: shape = new Shape3D(gbuf.getGeom(flags));
264: numVerts = gbuf.getNumVerts();
265: numTris = gbuf.getNumTris();
266:
267: if ((primflags & Primitive.GEOMETRY_NOT_SHARED) == 0) {
268: cacheGeometry(Primitive.SPHERE, radius, 0.0f, 0.0f,
269: divisions, 0, primflags, gbuf);
270: }
271: }
272:
273: if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) {
274: shape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
275: shape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
276: }
277:
278: if ((flags & ENABLE_GEOMETRY_PICKING) != 0) {
279: shape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
280: }
281:
282: this .addChild(shape);
283:
284: if (ap == null) {
285: setAppearance();
286: } else
287: setAppearance(ap);
288: }
289:
290: /**
291: * Used to create a new instance of the node. This routine is called
292: * by <code>cloneTree</code> to duplicate the current node.
293: * <code>cloneNode</code> should be overridden by any user subclassed
294: * objects. All subclasses must have their <code>cloneNode</code>
295: * method consist of the following lines:
296: * <P><blockquote><pre>
297: * public Node cloneNode(boolean forceDuplicate) {
298: * UserSubClass usc = new UserSubClass();
299: * usc.duplicateNode(this, forceDuplicate);
300: * return usc;
301: * }
302: * </pre></blockquote>
303: * @param forceDuplicate when set to <code>true</code>, causes the
304: * <code>duplicateOnCloneTree</code> flag to be ignored. When
305: * <code>false</code>, the value of each node's
306: * <code>duplicateOnCloneTree</code> variable determines whether
307: * NodeComponent data is duplicated or copied.
308: *
309: * @see Node#cloneTree
310: * @see Node#duplicateNode
311: * @see NodeComponent#setDuplicateOnCloneTree
312: */
313: public Node cloneNode(boolean forceDuplicate) {
314: Sphere s = new Sphere(radius, flags, divisions, getAppearance());
315: s.duplicateNode(this , forceDuplicate);
316:
317: return s;
318: }
319:
320: /**
321: * Copies all node information from <code>originalNode</code> into
322: * the current node. This method is called from the
323: * <code>cloneNode</code> method which is, in turn, called by the
324: * <code>cloneTree</code> method.
325: * <P>
326: * For any <i>NodeComponent</i> objects
327: * contained by the object being duplicated, each <i>NodeComponent</i>
328: * object's <code>duplicateOnCloneTree</code> value is used to determine
329: * whether the <i>NodeComponent</i> should be duplicated in the new node
330: * or if just a reference to the current node should be placed in the
331: * new node. This flag can be overridden by setting the
332: * <code>forceDuplicate</code> parameter in the <code>cloneTree</code>
333: * method to <code>true</code>.
334: *
335: * @param originalNode the original node to duplicate.
336: * @param forceDuplicate when set to <code>true</code>, causes the
337: * <code>duplicateOnCloneTree</code> flag to be ignored. When
338: * <code>false</code>, the value of each node's
339: * <code>duplicateOnCloneTree</code> variable determines whether
340: * NodeComponent data is duplicated or copied.
341: *
342: * @see Node#cloneTree
343: * @see Node#cloneNode
344: * @see NodeComponent#setDuplicateOnCloneTree
345: */
346: public void duplicateNode(Node originalNode, boolean forceDuplicate) {
347: super .duplicateNode(originalNode, forceDuplicate);
348: }
349:
350: /**
351: * Returns the radius of the sphere
352: *
353: * @since Java 3D 1.2.1
354: */
355: public float getRadius() {
356: return radius;
357: }
358:
359: /**
360: * Returns the number of divisions
361: *
362: * @since Java 3D 1.2.1
363: */
364: public int getDivisions() {
365: return divisions;
366: }
367:
368: void buildQuadrant(GeomBuffer gbuf, double startDelta,
369: double endDelta, int sign, int nstep, int n,
370: boolean upperSphere) {
371:
372: double ds, dt, theta, delta;
373: int i, j, index, i2;
374: double h, r, vx, vz;
375: Point3f pt;
376: Vector3f norm;
377: TexCoord2f texCoord;
378: double starth;
379: double t;
380: boolean leftToRight;
381:
382: if (upperSphere) {
383: dt = Math.PI / (2 * nstep);
384: theta = dt;
385: starth = 1;
386: leftToRight = (sign > 0);
387: } else {
388: dt = -Math.PI / (2 * nstep);
389: theta = Math.PI + dt;
390: starth = -1;
391: leftToRight = (sign < 0);
392: }
393:
394: for (i = 1; i <= nstep; i++) {
395: h = Math.cos(theta);
396: r = Math.sin(theta);
397: if (sign > 0) {
398: t = 1 - theta / Math.PI;
399: } else {
400: t = theta / Math.PI;
401: }
402:
403: i2 = i << 1;
404: // subdivision decreases towards the pole
405: ds = (endDelta - startDelta) / i;
406:
407: gbuf.begin(GeomBuffer.TRIANGLE_STRIP);
408:
409: if (leftToRight) {
410: // Build triangle strips from left to right
411: delta = startDelta;
412:
413: for (j = 0; j < i; j++) {
414: vx = r * Math.cos(delta);
415: vz = r * Math.sin(delta);
416:
417: gbuf.normal3d(vx * sign, h * sign, vz * sign);
418: gbuf.texCoord2d(0.75 - delta / (2 * Math.PI), t);
419: gbuf.vertex3d(vx * radius, h * radius, vz * radius);
420: if (i > 1) {
421: // get previous vertex from buffer
422: index = gbuf.currVertCnt - i2;
423: pt = gbuf.pts[index];
424: norm = gbuf.normals[index];
425: texCoord = gbuf.tcoords[index];
426: // connect with correspondent vertices from previous row
427: gbuf.normal3d(norm.x, norm.y, norm.z);
428: gbuf.texCoord2d(texCoord.x, texCoord.y);
429: gbuf.vertex3d(pt.x, pt.y, pt.z);
430: } else {
431: gbuf.normal3d(0, sign * starth, 0);
432: if (sign > 0) {
433: gbuf.texCoord2d(0.75
434: - (startDelta + endDelta)
435: / (4 * Math.PI), 1.0 - (theta - dt)
436: / Math.PI);
437: } else {
438: gbuf.texCoord2d(0.75
439: - (startDelta + endDelta)
440: / (4 * Math.PI), (theta - dt)
441: / Math.PI);
442: }
443: gbuf.vertex3d(0, starth * radius, 0);
444:
445: }
446: delta += ds;
447: }
448:
449: // Put the last vertex in that row,
450: // for numerical accuracy we don't use delta
451: // compute from above.
452: delta = endDelta;
453: vx = r * Math.cos(delta);
454: vz = r * Math.sin(delta);
455: gbuf.normal3d(vx * sign, h * sign, vz * sign);
456: gbuf.texCoord2d(0.75 - delta / (2 * Math.PI), t);
457: gbuf.vertex3d(vx * radius, h * radius, vz * radius);
458: } else {
459: delta = endDelta;
460: // Build triangle strips from right to left
461: for (j = i; j > 0; j--) {
462: vx = r * Math.cos(delta);
463: vz = r * Math.sin(delta);
464:
465: gbuf.normal3d(vx * sign, h * sign, vz * sign);
466: // Convert texture coordinate back to one
467: // set in previous version
468: gbuf.texCoord2d(0.75 - delta / (2 * Math.PI), t);
469: gbuf.vertex3d(vx * radius, h * radius, vz * radius);
470: if (i > 1) {
471: // get previous vertex from buffer
472: index = gbuf.currVertCnt - i2;
473: pt = gbuf.pts[index];
474: norm = gbuf.normals[index];
475: texCoord = gbuf.tcoords[index];
476: gbuf.normal3d(norm.x, norm.y, norm.z);
477: gbuf.texCoord2d(texCoord.x, texCoord.y);
478: gbuf.vertex3d(pt.x, pt.y, pt.z);
479: } else {
480: gbuf.normal3d(0, sign * starth, 0);
481: if (sign > 0) {
482: gbuf.texCoord2d(0.75
483: - (startDelta + endDelta)
484: / (4 * Math.PI), 1.0 - (theta - dt)
485: / Math.PI);
486: } else {
487: gbuf.texCoord2d(0.75
488: - (startDelta + endDelta)
489: / (4 * Math.PI), (theta - dt)
490: / Math.PI);
491: }
492: gbuf.vertex3d(0, starth * radius, 0);
493:
494: }
495: delta -= ds;
496: }
497:
498: // Put the last vertex in that row,
499: // for numerical accuracy we don't use delta
500: // compute from above.
501: delta = startDelta;
502: vx = r * Math.cos(delta);
503: vz = r * Math.sin(delta);
504: gbuf.normal3d(vx * sign, h * sign, vz * sign);
505: gbuf.texCoord2d(0.75 - delta / (2 * Math.PI), t);
506: gbuf.vertex3d(vx * radius, h * radius, vz * radius);
507:
508: }
509:
510: gbuf.end();
511:
512: if (i < nstep) {
513: theta += dt;
514: } else { // take care of numerical imprecision
515: theta = Math.PI / 2;
516: }
517: }
518:
519: }
520: }
|