| 1 | /** |
|---|
| 2 | * Copyright (c) 2008-2011 Ardor Labs, Inc. |
|---|
| 3 | * |
|---|
| 4 | * This file is part of Ardor3D. |
|---|
| 5 | * |
|---|
| 6 | * Ardor3D is free software: you can redistribute it and/or modify it |
|---|
| 7 | * under the terms of its license which may be found in the accompanying |
|---|
| 8 | * LICENSE file or at <http://www.ardor3d.com/LICENSE>. |
|---|
| 9 | */ |
|---|
| 10 | |
|---|
| 11 | package com.ardor3d.scenegraph.extension; |
|---|
| 12 | |
|---|
| 13 | import java.io.IOException; |
|---|
| 14 | |
|---|
| 15 | import com.ardor3d.image.Texture; |
|---|
| 16 | import com.ardor3d.image.Texture.WrapMode; |
|---|
| 17 | import com.ardor3d.math.Matrix3; |
|---|
| 18 | import com.ardor3d.math.Vector3; |
|---|
| 19 | import com.ardor3d.renderer.Renderer; |
|---|
| 20 | import com.ardor3d.renderer.queue.RenderBucketType; |
|---|
| 21 | import com.ardor3d.renderer.state.FogState; |
|---|
| 22 | import com.ardor3d.renderer.state.RenderState; |
|---|
| 23 | import com.ardor3d.renderer.state.RenderState.StateType; |
|---|
| 24 | import com.ardor3d.renderer.state.TextureState; |
|---|
| 25 | import com.ardor3d.renderer.state.ZBufferState; |
|---|
| 26 | import com.ardor3d.scenegraph.Node; |
|---|
| 27 | import com.ardor3d.scenegraph.hint.CullHint; |
|---|
| 28 | import com.ardor3d.scenegraph.hint.LightCombineMode; |
|---|
| 29 | import com.ardor3d.scenegraph.hint.TextureCombineMode; |
|---|
| 30 | import com.ardor3d.scenegraph.shape.Quad; |
|---|
| 31 | import com.ardor3d.util.export.CapsuleUtils; |
|---|
| 32 | import com.ardor3d.util.export.InputCapsule; |
|---|
| 33 | import com.ardor3d.util.export.OutputCapsule; |
|---|
| 34 | |
|---|
| 35 | /** |
|---|
| 36 | * A Box made of textured quads that simulate having a sky, horizon and so forth around your scene. Either attach to a |
|---|
| 37 | * camera node or update on each frame to set this skybox at the camera's position. |
|---|
| 38 | */ |
|---|
| 39 | public class Skybox extends Node { |
|---|
| 40 | |
|---|
| 41 | public enum Face { |
|---|
| 42 | /** The +Z side of the skybox. */ |
|---|
| 43 | North, |
|---|
| 44 | /** The -Z side of the skybox. */ |
|---|
| 45 | South, |
|---|
| 46 | /** The -X side of the skybox. */ |
|---|
| 47 | East, |
|---|
| 48 | /** The +X side of the skybox. */ |
|---|
| 49 | West, |
|---|
| 50 | /** The +Y side of the skybox. */ |
|---|
| 51 | Up, |
|---|
| 52 | /** The -Y side of the skybox. */ |
|---|
| 53 | Down; |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | private float _xExtent; |
|---|
| 57 | private float _yExtent; |
|---|
| 58 | private float _zExtent; |
|---|
| 59 | |
|---|
| 60 | private Quad[] _skyboxQuads; |
|---|
| 61 | |
|---|
| 62 | public Skybox() {} |
|---|
| 63 | |
|---|
| 64 | /** |
|---|
| 65 | * Creates a new skybox. The size of the skybox and name is specified here. By default, no textures are set. |
|---|
| 66 | * |
|---|
| 67 | * @param name |
|---|
| 68 | * The name of the skybox. |
|---|
| 69 | * @param xExtent |
|---|
| 70 | * The x size of the skybox in both directions from the center. |
|---|
| 71 | * @param yExtent |
|---|
| 72 | * The y size of the skybox in both directions from the center. |
|---|
| 73 | * @param zExtent |
|---|
| 74 | * The z size of the skybox in both directions from the center. |
|---|
| 75 | */ |
|---|
| 76 | public Skybox(final String name, final float xExtent, final float yExtent, final float zExtent) { |
|---|
| 77 | super(name); |
|---|
| 78 | |
|---|
| 79 | _xExtent = xExtent; |
|---|
| 80 | _yExtent = yExtent; |
|---|
| 81 | _zExtent = zExtent; |
|---|
| 82 | |
|---|
| 83 | initialize(); |
|---|
| 84 | } |
|---|
| 85 | |
|---|
| 86 | /** |
|---|
| 87 | * Set the texture to be displayed on the given face of the skybox. Replaces any existing texture on that face. |
|---|
| 88 | * |
|---|
| 89 | * @param face |
|---|
| 90 | * the face to set |
|---|
| 91 | * @param texture |
|---|
| 92 | * The texture for that side to assume. |
|---|
| 93 | * @throws IllegalArgumentException |
|---|
| 94 | * if face is null. |
|---|
| 95 | */ |
|---|
| 96 | public void setTexture(final Face face, final Texture texture) { |
|---|
| 97 | if (face == null) { |
|---|
| 98 | throw new IllegalArgumentException("Face can not be null."); |
|---|
| 99 | } |
|---|
| 100 | |
|---|
| 101 | _skyboxQuads[face.ordinal()].clearRenderState(RenderState.StateType.Texture); |
|---|
| 102 | setTexture(face, texture, 0); |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | /** |
|---|
| 106 | * Set the texture to be displayed on the given side of the skybox. Only replaces the texture at the index specified |
|---|
| 107 | * by textureUnit. |
|---|
| 108 | * |
|---|
| 109 | * @param face |
|---|
| 110 | * the face to set |
|---|
| 111 | * @param texture |
|---|
| 112 | * The texture for that side to assume. |
|---|
| 113 | * @param textureUnit |
|---|
| 114 | * The texture unite of the given side's TextureState the texture will assume. |
|---|
| 115 | */ |
|---|
| 116 | public void setTexture(final Face face, final Texture texture, final int textureUnit) { |
|---|
| 117 | // Validate |
|---|
| 118 | if (face == null) { |
|---|
| 119 | throw new IllegalArgumentException("Face can not be null."); |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | TextureState ts = (TextureState) _skyboxQuads[face.ordinal()] |
|---|
| 123 | .getLocalRenderState(RenderState.StateType.Texture); |
|---|
| 124 | if (ts == null) { |
|---|
| 125 | ts = new TextureState(); |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | // Initialize the texture state |
|---|
| 129 | ts.setTexture(texture, textureUnit); |
|---|
| 130 | ts.setEnabled(true); |
|---|
| 131 | |
|---|
| 132 | texture.setWrap(WrapMode.EdgeClamp); |
|---|
| 133 | |
|---|
| 134 | // Set the texture to the quad |
|---|
| 135 | _skyboxQuads[face.ordinal()].setRenderState(ts); |
|---|
| 136 | |
|---|
| 137 | return; |
|---|
| 138 | } |
|---|
| 139 | |
|---|
| 140 | public Texture getTexture(final Face face) { |
|---|
| 141 | if (face == null) { |
|---|
| 142 | throw new IllegalArgumentException("Face can not be null."); |
|---|
| 143 | } |
|---|
| 144 | return ((TextureState) _skyboxQuads[face.ordinal()].getLocalRenderState(RenderState.StateType.Texture)) |
|---|
| 145 | .getTexture(); |
|---|
| 146 | } |
|---|
| 147 | |
|---|
| 148 | public void initialize() { |
|---|
| 149 | |
|---|
| 150 | // Skybox consists of 6 sides |
|---|
| 151 | _skyboxQuads = new Quad[6]; |
|---|
| 152 | |
|---|
| 153 | // Create each of the quads |
|---|
| 154 | _skyboxQuads[Face.North.ordinal()] = new Quad("north", _xExtent * 2, _yExtent * 2); |
|---|
| 155 | _skyboxQuads[Face.North.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(180), 0)); |
|---|
| 156 | _skyboxQuads[Face.North.ordinal()].setTranslation(new Vector3(0, 0, _zExtent)); |
|---|
| 157 | _skyboxQuads[Face.South.ordinal()] = new Quad("south", _xExtent * 2, _yExtent * 2); |
|---|
| 158 | _skyboxQuads[Face.South.ordinal()].setTranslation(new Vector3(0, 0, -_zExtent)); |
|---|
| 159 | _skyboxQuads[Face.East.ordinal()] = new Quad("east", _zExtent * 2, _yExtent * 2); |
|---|
| 160 | _skyboxQuads[Face.East.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(90), 0)); |
|---|
| 161 | _skyboxQuads[Face.East.ordinal()].setTranslation(new Vector3(-_xExtent, 0, 0)); |
|---|
| 162 | _skyboxQuads[Face.West.ordinal()] = new Quad("west", _zExtent * 2, _yExtent * 2); |
|---|
| 163 | _skyboxQuads[Face.West.ordinal()].setRotation(new Matrix3().fromAngles(0, Math.toRadians(270), 0)); |
|---|
| 164 | _skyboxQuads[Face.West.ordinal()].setTranslation(new Vector3(_xExtent, 0, 0)); |
|---|
| 165 | _skyboxQuads[Face.Up.ordinal()] = new Quad("up", _xExtent * 2, _zExtent * 2); |
|---|
| 166 | _skyboxQuads[Face.Up.ordinal()] |
|---|
| 167 | .setRotation(new Matrix3().fromAngles(Math.toRadians(90), Math.toRadians(270), 0)); |
|---|
| 168 | _skyboxQuads[Face.Up.ordinal()].setTranslation(new Vector3(0, _yExtent, 0)); |
|---|
| 169 | _skyboxQuads[Face.Down.ordinal()] = new Quad("down", _xExtent * 2, _zExtent * 2); |
|---|
| 170 | _skyboxQuads[Face.Down.ordinal()].setRotation(new Matrix3().fromAngles(Math.toRadians(270), |
|---|
| 171 | Math.toRadians(270), 0)); |
|---|
| 172 | _skyboxQuads[Face.Down.ordinal()].setTranslation(new Vector3(0, -_yExtent, 0)); |
|---|
| 173 | |
|---|
| 174 | // We don't want the light to effect our skybox |
|---|
| 175 | getSceneHints().setLightCombineMode(LightCombineMode.Off); |
|---|
| 176 | |
|---|
| 177 | getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); |
|---|
| 178 | |
|---|
| 179 | final ZBufferState zbuff = new ZBufferState(); |
|---|
| 180 | zbuff.setEnabled(false); |
|---|
| 181 | setRenderState(zbuff); |
|---|
| 182 | |
|---|
| 183 | final FogState fs = new FogState(); |
|---|
| 184 | fs.setEnabled(false); |
|---|
| 185 | setRenderState(fs); |
|---|
| 186 | |
|---|
| 187 | // We don't want it making our skybox disapear, so force view |
|---|
| 188 | getSceneHints().setCullHint(CullHint.Never); |
|---|
| 189 | |
|---|
| 190 | for (int i = 0; i < 6; i++) { |
|---|
| 191 | // Make sure texture is only what is set. |
|---|
| 192 | _skyboxQuads[i].getSceneHints().setTextureCombineMode(TextureCombineMode.Replace); |
|---|
| 193 | |
|---|
| 194 | // Make sure no lighting on the skybox |
|---|
| 195 | _skyboxQuads[i].getSceneHints().setLightCombineMode(LightCombineMode.Off); |
|---|
| 196 | |
|---|
| 197 | // Make sure the quad is viewable |
|---|
| 198 | _skyboxQuads[i].getSceneHints().setCullHint(CullHint.Never); |
|---|
| 199 | |
|---|
| 200 | // Add to the prebucket. |
|---|
| 201 | _skyboxQuads[i].getSceneHints().setRenderBucketType(RenderBucketType.PreBucket); |
|---|
| 202 | |
|---|
| 203 | // And attach the skybox as a child |
|---|
| 204 | attachChild(_skyboxQuads[i]); |
|---|
| 205 | } |
|---|
| 206 | } |
|---|
| 207 | |
|---|
| 208 | /** |
|---|
| 209 | * Retrieve the quad indicated by the given side. |
|---|
| 210 | * |
|---|
| 211 | * @param face |
|---|
| 212 | * One of Skybox.Face.North, Skybox.Face.South, and so on... |
|---|
| 213 | * @return The Quad that makes up that side of the Skybox. |
|---|
| 214 | */ |
|---|
| 215 | public Quad getFace(final Face face) { |
|---|
| 216 | return _skyboxQuads[face.ordinal()]; |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | public void preloadTexture(final Face face, final Renderer r) { |
|---|
| 220 | final TextureState ts = (TextureState) _skyboxQuads[face.ordinal()] |
|---|
| 221 | .getLocalRenderState(RenderState.StateType.Texture); |
|---|
| 222 | if (ts != null) { |
|---|
| 223 | r.applyState(StateType.Texture, ts); |
|---|
| 224 | } |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | /** |
|---|
| 228 | * Force all of the textures to load. This prevents pauses later during the application as you pan around the world. |
|---|
| 229 | */ |
|---|
| 230 | public void preloadTextures(final Renderer r) { |
|---|
| 231 | for (int x = 0; x < 6; x++) { |
|---|
| 232 | final TextureState ts = (TextureState) _skyboxQuads[x].getLocalRenderState(RenderState.StateType.Texture); |
|---|
| 233 | if (ts != null) { |
|---|
| 234 | r.applyState(StateType.Texture, ts); |
|---|
| 235 | } |
|---|
| 236 | } |
|---|
| 237 | |
|---|
| 238 | } |
|---|
| 239 | |
|---|
| 240 | @Override |
|---|
| 241 | public void write(final OutputCapsule capsule) throws IOException { |
|---|
| 242 | super.write(capsule); |
|---|
| 243 | capsule.write(_xExtent, "xExtent", 0); |
|---|
| 244 | capsule.write(_yExtent, "yExtent", 0); |
|---|
| 245 | capsule.write(_zExtent, "zExtent", 0); |
|---|
| 246 | capsule.write(_skyboxQuads, "skyboxQuads", null); |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | @Override |
|---|
| 250 | public void read(final InputCapsule capsule) throws IOException { |
|---|
| 251 | super.read(capsule); |
|---|
| 252 | _xExtent = capsule.readFloat("xExtent", 0); |
|---|
| 253 | _yExtent = capsule.readFloat("yExtent", 0); |
|---|
| 254 | _zExtent = capsule.readFloat("zExtent", 0); |
|---|
| 255 | _skyboxQuads = CapsuleUtils.asArray(capsule.readSavableArray("skyboxQuads", null), Quad.class); |
|---|
| 256 | } |
|---|
| 257 | } |
|---|