/*
** License Applicability. Except to the extent portions of this file are
** made subject to an alternative license as permitted in the SGI Free
** Software License B, Version 2.0 (the "License"), the contents of this
** file are subject only to the provisions of the License. You may not use
** this file except in compliance with the License. You may obtain a copy
** of the License at Silicon Graphics, Inc., attn: Legal Services, 1600
** Amphitheatre Parkway, Mountain View, CA 94043-1351, or at:
**
** http://oss.sgi.com/projects/FreeB
**
** Note that, as provided in the License, the Software is distributed on an
** "AS IS" basis, with ALL EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS
** DISCLAIMED, INCLUDING, WITHOUT LIMITATION, ANY IMPLIED WARRANTIES AND
** CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A
** PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
**
** NOTE: The Original Code (as defined below) has been licensed to Sun
** Microsystems, Inc. ("Sun") under the SGI Free Software License B
** (Version 1.1), shown above ("SGI License"). Pursuant to Section
** 3.2(3) of the SGI License, Sun is distributing the Covered Code to
** you under an alternative license ("Alternative License"). This
** Alternative License includes all of the provisions of the SGI License
** except that Section 2.2 and 11 are omitted. Any differences between
** the Alternative License and the SGI License are offered solely by Sun
** and not by SGI.
**
** Original Code. The Original Code is: OpenGL Sample Implementation,
** Version 1.2.1, released January 26, 2000, developed by Silicon Graphics,
** Inc. The Original Code is Copyright (c) 1991-2000 Silicon Graphics, Inc.
** Copyright in any portions created by third parties is as indicated
** elsewhere herein. All Rights Reserved.
**
** Additional Notice Provisions: The application programming interfaces
** established by SGI in conjunction with the Original Code are The
** OpenGL(R) Graphics System: A Specification (Version 1.2.1), released
** April 1, 1999; The OpenGL(R) Graphics System Utility Library (Version
** 1.3), released November 4, 1998; and OpenGL(R) Graphics with the X
** Window System(R) (Version 1.3), released October 19, 1998. This software
** was created using the OpenGL(R) version 1.2.1 Sample Implementation
** published by SGI, but has not been independently verified as being
** compliant with the OpenGL(R) version 1.2.1 Specification.
**
** $Date: 2009-03-04 17:23:34 -0800 (Wed, 04 Mar 2009) $ $Revision: 1856 $
** $Header$
*/
/*
* Copyright (c) 2002-2004 LWJGL Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'LWJGL' nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
* MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
* ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
* DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for use
* in the design, construction, operation or maintenance of any nuclear
* facility.
*/
package net.sf.openrocket.gui.figure3d.photo.exhaust;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.Random;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLProfile;
import javax.media.opengl.fixedfunc.GLLightingFunc;
import javax.media.opengl.fixedfunc.GLMatrixFunc;
import javax.media.opengl.glu.GLU;
import net.sf.openrocket.motor.Motor;
import net.sf.openrocket.util.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureData;
import com.jogamp.opengl.util.texture.TextureIO;
public final class FlameRenderer {
public interface FlameSettings {
public double getExhaustScale();
public boolean isFlame();
public Color getFlameColor();
public boolean isSmoke();
public Color getSmokeColor();
public double getSmokeAlpha();
public double getFlameAspectRatio();
public boolean isSparks();
public double getSparkConcentration();
public double getSparkWeight();
}
private static final Logger log = LoggerFactory.getLogger(FlameRenderer.class);
private FlameRenderer() {
}
public static void drawExhaust(GL2 gl, FlameSettings fs, Motor motor) {
final float s = (float) Math.max(.5, Math.sqrt(motor.getAverageThrustEstimate()) / 4.0)
* (float) fs.getExhaustScale();
gl.glScalef(s, s, s);
gl.glRotated(90, 0, 1, 0);
gl.glTranslated(0, 0, 0);
gl.glDisable(GLLightingFunc.GL_LIGHTING);
if (fs.isSparks() && fs.isFlame()) {
sparks(gl, fs);
}
gl.glEnable(GL.GL_BLEND);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE);
gl.glDepthMask(false);
if (fs.isSmoke()) {
final float LEN = 10;
final float MAX_R = .15f;
final int P = 5;
final Func radius = new Func() {
@Override
public float f(double d) {
return (float) (Math.atan(d) / (Math.PI / 2.0)) * MAX_R + 0.001f;
}
};
final Func dZ = new Func() {
@Override
public float f(double z) {
return radius.f(z);
}
};
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
gl.glActiveTexture(GL.GL_TEXTURE0);
smokeT.bind(gl);
gl.glActiveTexture(GL.GL_TEXTURE1);
smokeN.bind(gl);
gl.glUseProgram(shaderprogram);
setUniform1i(gl, shaderprogram, "uSmoke", 0);
setUniform1i(gl, shaderprogram, "uNormal", 1);
trail(gl, radius, dZ, //
new Const(0.025f * (fs.getSmokeColor().getAlpha() / 255f)), //
LEN, P, fs.getSmokeColor(), s);
gl.glUseProgram(0);
smokeN.disable(gl);
gl.glActiveTexture(GL.GL_TEXTURE0);
}
if (fs.isFlame()) {
gl.glScalef(1, 1, (float) fs.getFlameAspectRatio());
gl.glActiveTexture(GL.GL_TEXTURE0);
flameT.enable(gl);
flameT.bind(gl);
final float FLEN = 0.3f;
final int FP = 6;
final Func fr = new Func() {
@Override
public float f(double z) {
z = z / FLEN;
z = 1 - z;
return (float) (z * z - z * z * z) * .06f;
}
};
final Func fdZ = new Func() {
@Override
public float f(double z) {
return 0.002f;
}
};
final Func alpha = new Func() {
@Override
public float f(double z) {
return 0.2f * (float) Math.pow((1.0f - (float) z / FLEN), 4);
};
};
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
trail(gl, fr, fdZ, alpha, FLEN, FP, fs.getFlameColor(), s);
flameT.disable(gl);
}
gl.glEnable(GLLightingFunc.GL_LIGHTING);
gl.glDepthMask(true);
}
private static void convertColor(Color color, float[] out) {
if (color == null) {
out[0] = 1;
out[1] = 1;
out[2] = 0;
} else {
out[0] = (float) color.getRed() / 255f;
out[1] = (float) color.getGreen() / 255f;
out[2] = (float) color.getBlue() / 255f;
}
}
static Texture flameT;
static Texture smokeT;
static Texture smokeN;
static int shaderprogram;
private static interface Func {
float f(double d);
}
private static final class Const implements Func {
final float val;
public Const(final float val) {
this.val = val;
}
@Override
public float f(final double d) {
return val;
}
}
public static void init(GL2 gl) {
try {
log.debug("Loading Textures");
TextureData data = TextureIO.newTextureData(GLProfile.getDefault(),
FlameRenderer.class.getResourceAsStream("/datafiles/flame/c-color.png"), GL.GL_RGBA, GL.GL_RGBA,
true, null);
smokeT = TextureIO.newTexture(data);
data = TextureIO.newTextureData(GLProfile.getDefault(),
FlameRenderer.class.getResourceAsStream("/datafiles/flame/c-normal.png"), GL.GL_RGBA, GL.GL_RGBA,
true, null);
smokeN = TextureIO.newTexture(data);
data = TextureIO.newTextureData(GLProfile.getDefault(),
FlameRenderer.class.getResourceAsStream("/datafiles/flame/smoke2.png"), GL.GL_RGBA, GL.GL_RGBA,
true, null);
flameT = TextureIO.newTexture(data);
log.debug("Loading Shader");
String line;
shaderprogram = gl.glCreateProgram();
/*
* int v = gl.glCreateShader(GL2.GL_FRAGMENT_SHADER); BufferedReader
* brv = new BufferedReader(new
* InputStreamReader(FlameRenderer.class
* .getResourceAsStream("smokeVertex.glsl"))); String vsrc = "";
* while ((line = brv.readLine()) != null) { vsrc += line + "\n"; }
* gl.glShaderSource(v, 1, new String[] { vsrc }, (int[]) null, 0);
* gl.glAttachShader(shaderprogram, v); gl.glCompileShader(v);
*/
int f = gl.glCreateShader(GL2.GL_FRAGMENT_SHADER);
BufferedReader brf = new BufferedReader(new InputStreamReader(
FlameRenderer.class.getResourceAsStream("/datafiles/flame/smokeShader.glsl")));
String fsrc = "";
while ((line = brf.readLine()) != null) {
fsrc += line + "\n";
}
gl.glShaderSource(f, 1, new String[] { fsrc }, (int[]) null, 0);
gl.glCompileShader(f);
int statusFragmentShader[] = new int[1];
gl.glGetShaderiv(f, GL2.GL_COMPILE_STATUS, IntBuffer.wrap(statusFragmentShader));
if (statusFragmentShader[0] == GL2.GL_FALSE) {
int infoLogLenght[] = new int[1];
gl.glGetShaderiv(f, GL2.GL_INFO_LOG_LENGTH, IntBuffer.wrap(infoLogLenght));
ByteBuffer infoLog = Buffers.newDirectByteBuffer(infoLogLenght[0]);
gl.glGetShaderInfoLog(f, infoLogLenght[0], null, infoLog);
byte[] infoBytes = new byte[infoLogLenght[0]];
infoLog.get(infoBytes);
String out = new String(infoBytes);
System.err.println("Fragment shader error:\n" + out);
}
gl.glAttachShader(shaderprogram, f);
gl.glLinkProgram(shaderprogram);
gl.glValidateProgram(shaderprogram);
} catch (Exception e1) {
e1.printStackTrace();
}
}
public static void dispose(GL2 gl) {
log.debug("Destroying Textures");
smokeT.destroy(gl);
smokeN.destroy(gl);
flameT.destroy(gl);
smokeT = null;
smokeN = null;
flameT = null;
log.debug("Deleting Shader {}", shaderprogram);
//gl.glDeleteShader(shaderprogram); TODO Why is this broken?
shaderprogram = 0;
}
private static void trail(GL2 gl, Func radius, Func dZ, Func alpha, float LEN, int P, Color color, float scale) {
float[] c = new float[4];
convertColor(color, c);
// Figure out if the flame and smoke is point "in" or "out" of the
// screen
// in order to draw the particles in the right Z order
final boolean startAtTop;
{
final double[] mvmatrix = new double[16];
final double[] projmatrix = new double[16];
final int[] viewport = new int[4];
gl.glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
gl.glGetDoublev(GLMatrixFunc.GL_MODELVIEW_MATRIX, mvmatrix, 0);
gl.glGetDoublev(GLMatrixFunc.GL_PROJECTION_MATRIX, projmatrix, 0);
final double out[] = new double[4];
final double out2[] = new double[4];
(new GLU()).gluProject(0, 0, 0, mvmatrix, 0, projmatrix, 0, viewport, 0, out, 0);
(new GLU()).gluProject(0, 0, 0.01f, mvmatrix, 0, projmatrix, 0, viewport, 0, out2, 0);
startAtTop = out2[2] < out[2];
}
final float start;
final float len;
final float mult;
if (startAtTop) {
start = 0.002f;
len = LEN;
mult = 1;
} else {
start = LEN;
len = 0.002f;
mult = -1;
}
// Use the same seed every time
Random r = new Random(0);
// Loop forwards or backwards. Technically the dZ is applied differently
// in either direction, but the difference should be vanishingly small.
for (float z = start; mult * z < mult * len; z += mult * dZ.f(z)) {
gl.glPushMatrix();
gl.glTranslatef(0, 0, z);
c[3] = alpha.f(z);
gl.glColor4fv(c, 0);
int[] ii = {0};
gl.glGetIntegerv(GL2.GL_CURRENT_PROGRAM, ii, 0);
if ( ii[0] == shaderprogram )
setUniform1f(gl, shaderprogram, "z", z);
for (int i = 0; i < P; i++) {
gl.glPushMatrix();
float rx = radius.f(z) - ((float) r.nextFloat() * radius.f(z) * 2.0f);
float ry = radius.f(z) - ((float) r.nextFloat() * radius.f(z) * 2.0f);
float rz = radius.f(z) - ((float) r.nextFloat() * radius.f(z) * 2.0f);
gl.glTranslatef(rx, ry, rz);
final double[] mvmatrix = new double[16];
gl.glGetDoublev(GLMatrixFunc.GL_MODELVIEW_MATRIX, mvmatrix, 0);
mvmatrix[0] = mvmatrix[5] = mvmatrix[10] = 1;
mvmatrix[1] = mvmatrix[2] = mvmatrix[4] = mvmatrix[6] = mvmatrix[8] = mvmatrix[9] = 0;
gl.glLoadMatrixd(mvmatrix, 0);
// Add a random rotation to prevent artifacts from texture.
gl.glRotatef(r.nextFloat() * 45f, 0, 0, 1);
gl.glBegin(GL.GL_TRIANGLE_FAN);
float d = radius.f(z) * scale * 2;
gl.glTexCoord2f(0, 0);
gl.glVertex3f(-d, -d, 0);
gl.glTexCoord2f(0, 1);
gl.glVertex3f(-d, d, 0);
gl.glTexCoord2f(1, 1);
gl.glVertex3f(d, d, 0);
gl.glTexCoord2f(1, 0);
gl.glVertex3f(d, -d, 0);
gl.glEnd();
gl.glPopMatrix();
}
gl.glPopMatrix();
}
}
private static void setUniform1i(GL2 inGL, int inProgramID, String inName, int inValue) {
int tUniformLocation = inGL.glGetUniformLocation(inProgramID, inName);
if (tUniformLocation != -1) {
inGL.glUniform1i(tUniformLocation, inValue);
} else {
log.warn("UNIFORM COULD NOT BE FOUND! NAME={}", inName);
}
}
private static void setUniform1f(GL2 inGL, int inProgramID, String inName, float inValue) {
int tUniformLocation = inGL.glGetUniformLocation(inProgramID, inName);
if (tUniformLocation != -1) {
inGL.glUniform1f(tUniformLocation, inValue);
} else {
log.warn("UNIFORM COULD NOT BE FOUND! NAME={}", inName);
}
}
private static void sparks(GL2 gl, FlameSettings fs) {
// Use the same seed every time
Random r = new Random(0);
float[] c = new float[4];
float[] c2 = new float[4];
convertColor(fs.getFlameColor(), c);
for (int i = 0; i < 3; i++) {
c[i] = c2[i] = c[i] * .2f + .8f;
}
c[3] = 1;
c2[3] = 1;
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnable(GL.GL_BLEND);
gl.glLineWidth(1 + (float)fs.getSparkWeight() * 1.0f);
for (int i = 0; i < 4000 * fs.getSparkConcentration(); i++) {
final float z = 0.01f + 2 * (r.nextFloat() * r.nextFloat() * r.nextFloat());
final float x = z * (r.nextFloat() - 0.5f);
final float y = z * (r.nextFloat() - 0.5f);
//gl.glPointSize(1);
gl.glBegin(GL.GL_LINES);
gl.glColor4fv(c, 0);
gl.glVertex3f(x, y, z * 2);
gl.glColor4fv(c2, 0);
gl.glVertex3f(x * 1.02f, y * 1.02f, z * 2 + 0.01f + ((float)fs.getSparkWeight() / 20));
gl.glEnd();
}
}
}