/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import java.awt.Color;
import java.util.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import javax.media.opengl.*;
import static haven.Utils.c2fa;
public class Material extends GLState {
public final GLState[] states;
public static final GLState nofacecull = new GLState.StandAlone(Slot.Type.GEOM, PView.proj) {
public void apply(GOut g) {
g.gl.glDisable(GL.GL_CULL_FACE);
}
public void unapply(GOut g) {
g.gl.glEnable(GL.GL_CULL_FACE);
}
};
@ResName("nofacecull")
public static class $nofacecull implements ResCons {
public GLState cons(Resource res, Object... args) {return(nofacecull);}
}
public static final float[] defamb = {0.2f, 0.2f, 0.2f, 1.0f};
public static final float[] defdif = {0.8f, 0.8f, 0.8f, 1.0f};
public static final float[] defspc = {0.0f, 0.0f, 0.0f, 1.0f};
public static final float[] defemi = {0.0f, 0.0f, 0.0f, 1.0f};
public static final GLState.Slot<Colors> colors = new GLState.Slot<Colors>(Slot.Type.DRAW, Colors.class);
@ResName("col")
public static class Colors extends GLState {
public float[] amb, dif, spc, emi;
public float shine;
public Colors() {
amb = defamb;
dif = defdif;
spc = defspc;
emi = defemi;
}
private Colors(float[] amb, float[] dif, float[] spc, float[] emi, float shine) {
this.amb = amb;
this.dif = dif;
this.spc = spc;
this.emi = emi;
this.shine = shine;
}
private static float[] colmul(float[] c1, float[] c2) {
return(new float[] {c1[0] * c2[0], c1[1] * c2[1], c1[2] * c2[2], c1[3] * c2[3]});
}
private static float[] colblend(float[] in, float[] bl) {
float f1 = bl[3], f2 = 1.0f - f1;
return(new float[] {(in[0] * f2) + (bl[0] * f1),
(in[1] * f2) + (bl[1] * f1),
(in[2] * f2) + (bl[2] * f1),
in[3]});
}
public Colors(Color amb, Color dif, Color spc, Color emi, float shine) {
this(c2fa(amb), c2fa(dif), c2fa(spc), c2fa(emi), shine);
}
public Colors(Color amb, Color dif, Color spc, Color emi) {
this(amb, dif, spc, emi, 0);
}
public Colors(Color col) {
this(new Color((int)(col.getRed() * defamb[0]), (int)(col.getGreen() * defamb[1]), (int)(col.getBlue() * defamb[2]), col.getAlpha()),
new Color((int)(col.getRed() * defdif[0]), (int)(col.getGreen() * defdif[1]), (int)(col.getBlue() * defdif[2]), col.getAlpha()),
new Color(0, 0, 0, 0),
new Color(0, 0, 0, 0),
0);
}
public Colors(Resource res, Object... args) {
this((Color)args[0], (Color)args[1], (Color)args[2], (Color)args[3], (Float)args[4]);
}
public void apply(GOut g) {
GL2 gl = g.gl;
gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_AMBIENT, amb, 0);
gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, dif, 0);
gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, spc, 0);
gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_EMISSION, emi, 0);
gl.glMaterialf(GL.GL_FRONT_AND_BACK, GL2.GL_SHININESS, shine);
}
public void unapply(GOut g) {
GL2 gl = g.gl;
gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_AMBIENT, defamb, 0);
gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_DIFFUSE, defdif, 0);
gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, defspc, 0);
gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_EMISSION, defemi, 0);
gl.glMaterialf(GL.GL_FRONT_AND_BACK, GL2.GL_SHININESS, 0.0f);
}
public int capplyfrom(GLState from) {
if(from instanceof Colors)
return(5);
return(-1);
}
public void applyfrom(GOut g, GLState from) {
if(from instanceof Colors)
apply(g);
}
public void prep(Buffer buf) {
Colors p = buf.get(colors);
if(p != null)
buf.put(colors, p.combine(this));
else
buf.put(colors, this);
}
public Colors combine(Colors other) {
return(new Colors(colblend(other.amb, this.amb),
colblend(other.dif, this.dif),
colblend(other.spc, this.spc),
colblend(other.emi, this.emi),
other.shine));
}
public String toString() {
return(String.format("(%.1f, %.1f, %.1f), (%.1f, %.1f, %.1f), (%.1f, %.1f, %.1f @ %.1f)",
amb[0], amb[1], amb[2], dif[0], dif[1], dif[2], spc[0], spc[1], spc[2], shine));
}
}
@ResName("vcol")
public static class $vcol implements ResCons {
public GLState cons(Resource res, Object... args) {
return(new States.ColState((Color)args[0]));
}
}
@ResName("order")
public static class $order implements ResCons {
public GLState cons(Resource res, Object... args) {
String nm = (String)args[0];
if(nm.equals("first")) {
return(Rendered.first);
} else if(nm.equals("last")) {
return(Rendered.last);
} else if(nm.equals("pfx")) {
return(Rendered.postpfx);
} else if(nm.equals("eye")) {
return(Rendered.eyesort);
} else {
throw(new Resource.LoadException("Unknown draw order: " + nm, res));
}
}
}
public void apply(GOut g) {}
public void unapply(GOut g) {}
public Material(GLState... states) {
this.states = states;
}
public Material() {
this(Light.deflight, new Colors());
}
public Material(Color amb, Color dif, Color spc, Color emi, float shine) {
this(Light.deflight, new Colors(amb, dif, spc, emi, shine));
}
public Material(Color col) {
this(Light.deflight, new Colors(col));
}
public Material(Tex tex) {
this(Light.deflight, new Colors(), tex.draw(), tex.clip());
}
public Material(Tex tex, boolean bright) {
this(Light.deflight, new Colors(defamb, defdif, defspc, bright?new float[]{1.0f, 1.0f, 1.0f, 1.0f}:defemi, 0), tex.draw(), tex.clip());
}
public String toString() {
return(Arrays.asList(states).toString());
}
public void prep(Buffer buf) {
for(GLState st : states)
st.prep(buf);
}
public static class Res extends Resource.Layer implements Resource.IDLayer<Integer> {
public final int id;
private transient List<GLState> states = new LinkedList<GLState>();
private transient List<Resolver> left = new LinkedList<Resolver>();
private transient Material m;
private boolean mipmap = false, linear = false;
public interface Resolver {
public void resolve(Collection<GLState> buf);
}
public Res(Resource res, int id) {
res.super();
this.id = id;
}
public Material get() {
synchronized(this) {
if(m == null) {
for(Iterator<Resolver> i = left.iterator(); i.hasNext();) {
Resolver r = i.next();
r.resolve(states);
i.remove();
}
m = new Material(states.toArray(new GLState[0])) {
public String toString() {
return(super.toString() + "@" + getres().name);
}
};
}
return(m);
}
}
public void init() {
for(Resource.Image img : getres().layers(Resource.imgc, false)) {
TexGL tex = (TexGL)img.tex();
if(mipmap)
tex.mipmap();
if(linear)
tex.magfilter(GL.GL_LINEAR);
}
}
public Integer layerid() {
return(id);
}
}
@Resource.LayerName("mat")
public static class OldMat implements Resource.LayerFactory<Res> {
private static Color col(byte[] buf, int[] off) {
double r = Utils.floatd(buf, off[0]); off[0] += 5;
double g = Utils.floatd(buf, off[0]); off[0] += 5;
double b = Utils.floatd(buf, off[0]); off[0] += 5;
double a = Utils.floatd(buf, off[0]); off[0] += 5;
return(new Color((float)r, (float)g, (float)b, (float)a));
}
public Res cons(final Resource res, byte[] buf) {
int id = Utils.uint16d(buf, 0);
Res ret = new Res(res, id);
int[] off = {2};
GLState light = Light.deflight;
while(off[0] < buf.length) {
String thing = Utils.strd(buf, off).intern();
if(thing == "col") {
Color amb = col(buf, off);
Color dif = col(buf, off);
Color spc = col(buf, off);
double shine = Utils.floatd(buf, off[0]); off[0] += 5;
Color emi = col(buf, off);
ret.states.add(new Colors(amb, dif, spc, emi, (float)shine));
} else if(thing == "linear") {
ret.linear = true;
} else if(thing == "mipmap") {
ret.mipmap = true;
} else if(thing == "nofacecull") {
ret.states.add(nofacecull);
} else if(thing == "tex") {
final int tid = Utils.uint16d(buf, off[0]); off[0] += 2;
ret.left.add(new Res.Resolver() {
public void resolve(Collection<GLState> buf) {
for(Resource.Image img : res.layers(Resource.imgc)) {
if(img.id == tid) {
buf.add(img.tex().draw());
buf.add(img.tex().clip());
return;
}
}
throw(new RuntimeException(String.format("Specified texture %d not found in %s", tid, res)));
}
});
} else if(thing == "texlink") {
final String nm = Utils.strd(buf, off);
final int ver = Utils.uint16d(buf, off[0]); off[0] += 2;
final int tid = Utils.uint16d(buf, off[0]); off[0] += 2;
ret.left.add(new Res.Resolver() {
public void resolve(Collection<GLState> buf) {
Resource tres = Resource.load(nm, ver);
for(Resource.Image img : tres.layers(Resource.imgc)) {
if(img.id == tid) {
buf.add(img.tex().draw());
buf.add(img.tex().clip());
return;
}
}
throw(new RuntimeException(String.format("Specified texture %d for %s not found in %s", tid, res, tres)));
}
});
} else if(thing == "light") {
String l = Utils.strd(buf, off);
if(l.equals("pv")) {
light = Light.vlights;
} else if(l.equals("pp")) {
light = Light.plights;
} else if(l.equals("n")) {
light = null;
} else {
throw(new Resource.LoadException("Unknown lighting type: " + thing, res));
}
} else {
throw(new Resource.LoadException("Unknown material part: " + thing, res));
}
}
if(light != null)
ret.states.add(light);
return(ret);
}
}
@dolda.jglob.Discoverable
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResName {
public String value();
}
public interface ResCons {
public GLState cons(Resource res, Object... args);
}
public interface ResCons2 {
public void cons(Resource res, List<GLState> states, List<Res.Resolver> left, Object... args);
}
private static final Map<String, ResCons2> rnames = new TreeMap<String, ResCons2>();
static {
for(Class<?> cl : dolda.jglob.Loader.get(ResName.class).classes()) {
String nm = cl.getAnnotation(ResName.class).value();
if(ResCons.class.isAssignableFrom(cl)) {
final ResCons scons;
try {
scons = cl.asSubclass(ResCons.class).newInstance();
} catch(InstantiationException e) {
throw(new Error(e));
} catch(IllegalAccessException e) {
throw(new Error(e));
}
rnames.put(nm, new ResCons2() {
public void cons(Resource res, List<GLState> states, List<Res.Resolver> left, Object... args) {
GLState ret = scons.cons(res, args);
if(ret != null)
states.add(ret);
}
});
} else if(ResCons2.class.isAssignableFrom(cl)) {
try {
rnames.put(nm, cl.asSubclass(ResCons2.class).newInstance());
} catch(InstantiationException e) {
throw(new Error(e));
} catch(IllegalAccessException e) {
throw(new Error(e));
}
} else if(GLState.class.isAssignableFrom(cl)) {
final Constructor<? extends GLState> cons;
try {
cons = cl.asSubclass(GLState.class).getConstructor(Resource.class, Object[].class);
} catch(NoSuchMethodException e) {
throw(new Error("No proper constructor for res-consable GL state " + cl.getName(), e));
}
rnames.put(nm, new ResCons2() {
public void cons(Resource res, List<GLState> states, List<Res.Resolver> left, Object... args) {
states.add(Utils.construct(cons, res, args));
}
});
} else {
throw(new Error("Illegal material constructor class: " + cl));
}
}
}
@Resource.LayerName("mat2")
public static class NewMat implements Resource.LayerFactory<Res> {
public Res cons(Resource res, byte[] bbuf) {
Message buf = new Message(0, bbuf);
int id = buf.uint16();
Res ret = new Res(res, id);
while(!buf.eom()) {
String nm = buf.string();
Object[] args = buf.list();
if(nm.equals("linear")) {
/* XXX: These should very much be removed and
* specified directly in the texture layer
* instead. */
ret.linear = true;
} else if(nm.equals("mipmap")) {
ret.mipmap = true;
} else {
ResCons2 cons = rnames.get(nm);
if(cons == null)
throw(new Resource.LoadException("Unknown material part name: " + nm, res));
cons.cons(res, ret.states, ret.left, args);
}
}
return(ret);
}
}
}