001: /*
002: * $Header: /cvs/j3dfly/J3dFly/src/org/jdesktop/j3dfly/utils/environment/geometry/SkyDome.java,v 1.1 2005/04/20 21:04:55 paulby Exp $
003: *
004: * Sun Public License Notice
005: *
006: * The contents of this file are subject to the Sun Public License Version
007: * 1.0 (the "License"). You may not use this file except in compliance with
008: * the License. A copy of the License is available at http://www.sun.com/
009: *
010: * The Original Code is Java 3D(tm) Fly Through.
011: * The Initial Developer of the Original Code is Paul Byrne.
012: * Portions created by Paul Byrne are Copyright (C) 2002.
013: * All Rights Reserved.
014: *
015: * Contributor(s): Paul Byrne.
016: *
017: **/
018: package org.jdesktop.j3dfly.utils.environment.geometry;
019:
020: import java.util.*;
021: import javax.media.j3d.*;
022: import javax.vecmath.*;
023: import java.math.*;
024: import java.awt.Color;
025:
026: /**
027: */
028:
029: public class SkyDome extends Primitive {
030:
031: /**
032: * Sphere shape identifier, used by <code>getShape</code>.
033: *
034: * @see Sphere#getShape
035: */
036: public static final int BODY = 0;
037:
038: float radius;
039: int divisions;
040: double zAngleStart;
041: double zAngleEnd;
042: double yAngleStart;
043: double yAngleEnd;
044:
045: private float darkness;
046: private Shape3D skyShape;
047:
048: private TriangleStripArray skyGeom;
049:
050: private float[] skyRGB; // RGB values for entire sky
051:
052: private Color3f skyColor = null;
053:
054: /**
055: * Obtains Sphere's shape node that contains the geometry.
056: * This allows users to modify the appearance or geometry.
057: * @param partId The part to return (must be BODY for Spheres)
058: * @return The Shape3D object associated with the partId. If an
059: * invalid partId is passed in, null is returned.
060: */
061: public Shape3D getShape(int partId) {
062: if (partId != BODY)
063: return null;
064: return skyShape;
065: }
066:
067: /** Obtains Sphere's shape node that contains the geometry.
068: */
069: public Shape3D getShape() {
070: return skyShape;
071: }
072:
073: /** Sets appearance of the Sphere.
074: */
075: public void setAppearance(Appearance ap) {
076: skyShape.setAppearance(ap);
077: }
078:
079: /**
080: * Constructs a customized SphereSegment of a given radius with
081: * with the specified start and stop angles from the z and y axis,
082: * number of divisions, and appearance, with additional parameters
083: * specified by the Primitive flags. The resolution is defined in
084: * terms of number of subdivisions along the sphere's axes. More
085: * divisions lead to more finely tesselated objects.
086: * <p>
087: * If the appearance is null, the sphere segment defaults to a white appearance.
088: */
089: public SkyDome(double yAngleStart, int divisions, Appearance ap) {
090: super ();
091:
092: PolygonAttributes poly = new PolygonAttributes();
093: //poly.setPolygonMode( PolygonAttributes.POLYGON_LINE );
094: poly.setCullFace(PolygonAttributes.CULL_NONE);
095: ap.setPolygonAttributes(poly);
096:
097: int primflags = GENERATE_NORMALS_INWARD
098: | GeomBuffer.GENERATE_COLOR_3;
099:
100: this .zAngleStart = 0;
101: this .zAngleEnd = Math.PI * 2;
102:
103: this .yAngleStart = yAngleStart;
104: this .yAngleEnd = Math.PI;
105:
106: double rho, drho, theta, dtheta;
107: double vx, vy, vz;
108: double s, t, ds, dt;
109: int i, j;
110: double sign;
111:
112: this .radius = 1f;
113: this .divisions = divisions;
114:
115: Color startColor = new Color(154, 210, 244);
116: Color endColor = new Color(65, 145, 255);
117:
118: //Color startColor = new Color( 255, 0, 0 );
119: //Color endColor = new Color( 0, 255, 0 );
120:
121: Color3f skyColor = new Color3f(endColor.getRed() / 255f,
122: endColor.getGreen() / 255f, endColor.getBlue() / 255f);
123:
124: float[] startRGB = startColor.getRGBComponents(null);
125: float[] endRGB = endColor.getRGBComponents(null);
126:
127: int colorSteps = 5;
128:
129: float[] colorChange = new float[] {
130: (startRGB[0] - endRGB[0]) / (float) colorSteps,
131: (startRGB[1] - endRGB[1]) / (float) colorSteps,
132: (startRGB[2] - endRGB[2]) / (float) colorSteps };
133:
134: float[] currentRGB = startColor.getRGBComponents(null);
135:
136: /*
137: * The sphere algorithm evaluates spherical angles along regular
138: * units. For each spherical coordinate, (theta, rho), a (x,y,z) is
139: * evaluated (along with the normals and texture coordinates).
140: *
141: * The spherical angles theta varies from 0 to 2pi and rho from 0
142: * to pi. Sample points depends on the number of divisions.
143: */
144:
145: flags = primflags;
146:
147: //Depending on whether normal inward bit is set.
148: if ((flags & GENERATE_NORMALS_INWARD) != 0)
149: sign = -1.0;
150: else
151: sign = 1.0;
152:
153: // delta theta and delta rho depends on divsions.
154: /*
155: dtheta = 2.0 * Math.PI / (double) divisions;
156: drho = Math.PI / (double) divisions;
157: */
158: dtheta = Math.abs(zAngleEnd - zAngleStart) / (double) divisions;
159: drho = Math.abs(yAngleEnd - yAngleStart) / (double) divisions;
160:
161: t = 0.0;
162: ds = 1.0 / divisions;
163: dt = 1.0 / divisions;
164:
165: // Create a geometry buffer with given number points allocated.
166: GeomBuffer gbuf = new GeomBuffer(divisions * (divisions + 1)
167: * 2);
168:
169: for (i = divisions; i > 0; i--) {
170: rho = yAngleEnd - ((double) i * drho);
171: gbuf.begin(GeomBuffer.QUAD_STRIP);
172: s = 0.0;
173: for (j = 0; j <= divisions; j++) {
174:
175: // Takes care of boundary case.
176: if (j == divisions)
177: theta = zAngleStart;
178: else
179: theta = zAngleStart + ((double) j) * dtheta;
180:
181: // First quad vertex.
182: // Evaluate spherical coords to get unit sphere positions.
183:
184: vz = -Math.sin(theta) * Math.sin(rho);
185: vx = Math.cos(theta) * Math.sin(rho);
186: vy = Math.cos(rho);
187:
188: // Send to buffer.
189: gbuf.normal3d(vx * sign, vy * sign, vz * sign);
190: gbuf.texCoord2d(s, t + dt);
191: gbuf.color3f(currentRGB[0], currentRGB[1],
192: currentRGB[2]);
193: gbuf.vertex3d(vx * radius, vy * radius, vz * radius);
194:
195: // Second quad vertex.
196: vz = -Math.sin(theta) * Math.sin(rho + drho);
197: vx = Math.cos(theta) * Math.sin(rho + drho);
198: vy = Math.cos(rho + drho);
199:
200: // Send to Buffer
201: gbuf.normal3d(vx * sign, vy * sign, vz * sign);
202: gbuf.texCoord2d(s, t);
203: gbuf.color3f(currentRGB[0] - colorChange[0],
204: currentRGB[1] - colorChange[1], currentRGB[2]
205: - colorChange[2]);
206: gbuf.vertex3d(vx * radius, vy * radius, vz * radius);
207:
208: // increment s
209: s += ds;
210: }
211:
212: if (i > divisions - colorSteps) {
213: currentRGB[0] -= colorChange[0];
214: currentRGB[1] -= colorChange[1];
215: currentRGB[2] -= colorChange[2];
216: } else if (i == divisions - colorSteps) {
217: currentRGB[0] -= colorChange[0];
218: currentRGB[1] -= colorChange[1];
219: currentRGB[2] -= colorChange[2];
220:
221: colorChange[0] = currentRGB[0] - skyColor.x;
222: colorChange[1] = currentRGB[1] - skyColor.y;
223: colorChange[2] = currentRGB[2] - skyColor.z;
224: } else if (i == divisions - colorSteps - 1) {
225: colorChange[0] = 0f;
226: colorChange[1] = 0f;
227: colorChange[2] = 0f;
228:
229: currentRGB[0] = skyColor.x;
230: currentRGB[1] = skyColor.y;
231: currentRGB[2] = skyColor.z;
232: }
233: gbuf.end();
234: t += dt;
235:
236: }
237:
238: skyGeom = (TriangleStripArray) gbuf.getGeom(flags);
239: skyGeom.setCapability(TriangleStripArray.ALLOW_COLOR_WRITE);
240:
241: skyRGB = new float[skyGeom.getVertexCount() * 3];
242: skyGeom.getColors(0, skyRGB);
243:
244: skyShape = new Shape3D(skyGeom);
245: numVerts = gbuf.getNumVerts();
246: numTris = gbuf.getNumTris();
247:
248: if ((flags & ENABLE_APPEARANCE_MODIFY) != 0) {
249: skyShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
250: skyShape.setCapability(Shape3D.ALLOW_APPEARANCE_WRITE);
251: }
252:
253: this .addChild(skyShape);
254:
255: if (ap == null) {
256: setAppearance();
257: } else {
258: setAppearance(ap);
259: }
260: }
261:
262: /**
263: * Darken sky - factor 0 is black
264: * factor 1 is daylight
265: */
266: public void setDarkness(float factor) {
267: darkness = factor;
268: float[] newColor = new float[skyRGB.length];
269: for (int i = 0; i < skyRGB.length; i++) {
270: newColor[i] = skyRGB[i] * factor;
271: }
272:
273: skyGeom.setColors(0, newColor);
274: }
275:
276: public float getDarkness() {
277: return darkness;
278: }
279:
280: /**
281: * Used to create a new instance of the node. This routine is called
282: * by <code>cloneTree</code> to duplicate the current node.
283: * <code>cloneNode</code> should be overridden by any user subclassed
284: * objects. All subclasses must have their <code>cloneNode</code>
285: * method consist of the following lines:
286: * <P><blockquote><pre>
287: * public Node cloneNode(boolean forceDuplicate) {
288: * UserSubClass usc = new UserSubClass();
289: * usc.duplicateNode(this, forceDuplicate);
290: * return usc;
291: * }
292: * </pre></blockquote>
293: * @param forceDuplicate when set to <code>true</code>, causes the
294: * <code>duplicateOnCloneTree</code> flag to be ignored. When
295: * <code>false</code>, the value of each node's
296: * <code>duplicateOnCloneTree</code> variable determines whether
297: * NodeComponent data is duplicated or copied.
298: *
299: * @see Node#cloneTree
300: * @see Node#duplicateNode
301: * @see NodeComponent#setDuplicateOnCloneTree
302: */
303: public Node cloneNode(boolean forceDuplicate) {
304: SkyDome s = new SkyDome(yAngleStart, divisions, getAppearance());
305: s.duplicateNode(this , forceDuplicate);
306:
307: return s;
308: }
309:
310: /**
311: * Copies all node information from <code>originalNode</code> into
312: * the current node. This method is called from the
313: * <code>cloneNode</code> method which is, in turn, called by the
314: * <code>cloneTree</code> method.
315: * <P>
316: * For any <i>NodeComponent</i> objects
317: * contained by the object being duplicated, each <i>NodeComponent</i>
318: * object's <code>duplicateOnCloneTree</code> value is used to determine
319: * whether the <i>NodeComponent</i> should be duplicated in the new node
320: * or if just a reference to the current node should be placed in the
321: * new node. This flag can be overridden by setting the
322: * <code>forceDuplicate</code> parameter in the <code>cloneTree</code>
323: * method to <code>true</code>.
324: *
325: * @param originalNode the original node to duplicate.
326: * @param forceDuplicate when set to <code>true</code>, causes the
327: * <code>duplicateOnCloneTree</code> flag to be ignored. When
328: * <code>false</code>, the value of each node's
329: * <code>duplicateOnCloneTree</code> variable determines whether
330: * NodeComponent data is duplicated or copied.
331: *
332: * @see Node#cloneTree
333: * @see Node#cloneNode
334: * @see NodeComponent#setDuplicateOnCloneTree
335: */
336: public void duplicateNode(Node originalNode, boolean forceDuplicate) {
337: super.duplicateNode(originalNode, forceDuplicate);
338: }
339:
340: }
|