/*
* 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 haven.glsl.*;
import static haven.glsl.Cons.*;
import static haven.glsl.Type.*;
import javax.media.opengl.*;
public class Outlines implements Rendered {
private boolean symmetric;
public void draw(GOut g) {}
private final static Uniform snrm = new Uniform(SAMPLER2D);
private final static Uniform sdep = new Uniform(SAMPLER2D);
private final static Uniform msnrm = new Uniform(SAMPLER2DMS);
private final static Uniform msdep = new Uniform(SAMPLER2DMS);
private final static ShaderMacro[][] shaders = new ShaderMacro[4][];
private static ShaderMacro shader(final boolean symmetric, final boolean ms) {
return(new ShaderMacro() {
Color color = Color.BLACK;
Coord[] points = {
new Coord(-1, 0),
new Coord( 1, 0),
new Coord( 0, -1),
new Coord( 0, 1),
};
Expression sample(boolean nrm, Expression c, Expression s, Coord o) {
if(ms) {
Expression ctc = ivec2(floor(mul(c, MiscLib.screensize.ref())));
if(!o.equals(Coord.z))
ctc = add(ctc, ivec2(o));
return(texelFetch((nrm?msnrm:msdep).ref(), ctc, s));
} else {
Expression ctc = c;
if(!o.equals(Coord.z))
ctc = add(c, mul(vec2(o), MiscLib.pixelpitch.ref()));
return(texture2D((nrm?snrm:sdep).ref(), ctc));
}
}
Function ofac = new Function.Def(FLOAT) {{
Expression sample = param(PDir.IN, INT).ref();
Expression tc = Tex2D.texcoord.ref();
LValue ret = code.local(FLOAT, l(0.0)).ref();
Expression lnrm = code.local(VEC3, mul(sub(pick(sample(true, tc, sample, Coord.z), "rgb"), l(0.5)), l(2.0))).ref();
Expression ldep = code.local(FLOAT, pick(sample(false, tc, sample, Coord.z), "z")).ref();
/* XXX: Current depth detection doesn't work well
* with frustum projections, perhaps because of
* the lack of precision in the depth buffer
* (though I'm not sure I buy that explanation
* yet). */
LValue dh = code.local(FLOAT, l(0.0002)).ref(), dl = code.local(FLOAT, l(-0.0002)).ref();
for(int i = 0; i < points.length; i++) {
Expression cdep = pick(sample(false, tc, sample, points[i]), "z");
cdep = sub(ldep, cdep);
cdep = code.local(FLOAT, cdep).ref();
code.add(stmt(ass(dh, max(dh, cdep))));
code.add(stmt(ass(dl, min(dl, cdep))));
}
if(symmetric)
code.add(aadd(ret, smoothstep(l(5.0), l(6.0), max(div(dh, neg(dl)), div(dl, neg(dh))))));
else
code.add(aadd(ret, smoothstep(l(5.0), l(6.0), div(dh, neg(dl)))));
for(int i = 0; i < points.length; i++) {
Expression cnrm = mul(sub(pick(sample(true, tc, sample, points[i]), "rgb"), l(0.5)), l(2.0));
if(symmetric) {
code.add(aadd(ret, sub(l(1.0), abs(dot(lnrm, cnrm)))));
} else {
cnrm = code.local(VEC3, cnrm).ref();
code.add(new If(gt(pick(cross(lnrm, cnrm), "z"), l(0.0)),
stmt(aadd(ret, sub(l(1.0), abs(dot(lnrm, cnrm)))))));
}
}
code.add(new Return(smoothstep(l(0.4), l(0.6), min(ret, l(1.0)))));
}};
Function msfac = new Function.Def(FLOAT) {{
LValue ret = code.local(FLOAT, l(0.0)).ref();
LValue i = code.local(INT, null).ref();
code.add(new For(ass(i, l(0)), lt(i, FBConfig.numsamples.ref()), linc(i),
stmt(aadd(ret, ofac.call(i)))));
code.add(new Return(div(ret, FBConfig.numsamples.ref())));
}};
public void modify(ProgramContext prog) {
prog.fctx.fragcol.mod(new Macro1<Expression>() {
public Expression expand(Expression in) {
Expression of = (!ms)?ofac.call(l(-1)):msfac.call();
return(vec4(col3(color), mix(l(0.0), l(1.0), of)));
}
}, 0);
}
});
}
static {
/* XXX: It would be good to have some kind of more convenient
* shader internation. */
shaders[0] = new ShaderMacro[] {shader(false, false)};
shaders[1] = new ShaderMacro[] {shader(false, true)};
shaders[2] = new ShaderMacro[] {shader(true, false)};
shaders[3] = new ShaderMacro[] {shader(true, true)};
}
public Outlines(final boolean symmetric) {
this.symmetric = symmetric;
}
public boolean setup(RenderList rl) {
final PView.ConfContext ctx = (PView.ConfContext)rl.state().get(PView.ctx);
final RenderedNormals nrm = ctx.data(RenderedNormals.id);
final boolean ms = ctx.cfg.ms > 1;
ctx.cfg.tdepth = true;
ctx.cfg.add(nrm);
rl.prepc(Rendered.postfx);
rl.add(new Rendered.ScreenQuad(), new States.AdHoc(shaders[(symmetric?2:0) | (ms?1:0)]) {
private TexUnit tnrm;
private TexUnit tdep;
public void reapply(GOut g) {
GL2 gl = g.gl;
gl.glUniform1i(g.st.prog.uniform(!ms?snrm:msnrm), tnrm.id);
gl.glUniform1i(g.st.prog.uniform(!ms?sdep:msdep), tdep.id);
}
public void apply(GOut g) {
GL gl = g.gl;
if(!ms) {
tnrm = g.st.texalloc(g, ((GLFrameBuffer.Attach2D)nrm.tex).tex);
tdep = g.st.texalloc(g, ((GLFrameBuffer.Attach2D)ctx.cur.depth).tex);
} else {
tnrm = g.st.texalloc(g, ((GLFrameBuffer.AttachMS)nrm.tex).tex);
tdep = g.st.texalloc(g, ((GLFrameBuffer.AttachMS)ctx.cur.depth).tex);
}
reapply(g);
}
public void unapply(GOut g) {
GL gl = g.gl;
tnrm.ufree(); tnrm = null;
tdep.ufree(); tdep = null;
}
});
return(false);
}
}