/* ** 2013 December 22 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: ** May you do good and not evil. ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. */ package info.ata4.bspsrc.modules.texture; import info.ata4.bsplib.app.SourceAppID; import info.ata4.bsplib.struct.BrushFlag; import info.ata4.bsplib.struct.BspData; import info.ata4.bsplib.struct.DBrush; import info.ata4.bsplib.struct.DBrushSide; import info.ata4.bsplib.struct.DTexData; import info.ata4.bsplib.struct.DTexInfo; import info.ata4.bsplib.struct.SurfaceFlag; import info.ata4.bsplib.vector.Vector3f; import info.ata4.log.LogUtils; import java.util.EnumSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * A builder to create Texture objects. * * @author Nico Bergemann <barracuda415 at yahoo.de> */ public class TextureBuilder { private static final Logger L = LogUtils.getLogger(); private static final float EPS_PERP = 0.02f; // surface/brush flags private static EnumSet<SurfaceFlag> SURF_FLAGS_CLIP = EnumSet.of(SurfaceFlag.SURF_NOLIGHT, SurfaceFlag.SURF_NODRAW); private static EnumSet<SurfaceFlag> SURF_FLAGS_AREAPORTAL = EnumSet.of(SurfaceFlag.SURF_NOLIGHT); private static EnumSet<BrushFlag> BRUSH_FLAGS_AREAPORTAL = EnumSet.of(BrushFlag.CONTENTS_AREAPORTAL); private final BspData bsp; private final TextureSource texsrc; private Texture texture; private Vector3f origin; private Vector3f angles; private Vector3f normal; private DTexInfo texinfo; private DTexData texdata; // indices private short itexinfo = DTexInfo.TEXINFO_NODE; private int ibrush = -1; private int ibrushside = -1; TextureBuilder(TextureSource texsrc, BspData bsp) { this.texsrc = texsrc; this.bsp = bsp; } public Texture build() { texture = new Texture(); texture.setOriginalTexture(ToolTexture.SKIP); // align to preset axes fixPerpendicular(); // no texinfo if (itexinfo == DTexInfo.TEXINFO_NODE) { return texture; } try { texinfo = bsp.texinfos.get(itexinfo); } catch (ArrayIndexOutOfBoundsException ex) { L.log(Level.WARNING, "Invalid texinfo index: {0}", itexinfo); return texture; } try { texdata = bsp.texdatas.get(texinfo.texdata); texture.setData(texdata); } catch (ArrayIndexOutOfBoundsException ex) { L.log(Level.WARNING, "Invalid texdata index: {0}", texinfo.texdata); return texture; } // get texture paths String textureOriginal = ToolTexture.SKIP; try { textureOriginal = bsp.texnames.get(texdata.texname); } catch (ArrayIndexOutOfBoundsException ex) { L.log(Level.WARNING, "Invalid texname index: {0}", texdata.texname); } String textureOverride = texsrc.getFixedTextureNames().get(texdata.texname); if (texsrc.isFixToolTextures()) { String textureFix = fixToolTexture(); if (textureFix != null) { textureOverride = textureFix; } } // assign texture paths texture.setOriginalTexture(textureOriginal); texture.setOverrideTexture(textureOverride); // some calculations buildLightmapScale(); buildUV(); fixPerpendicular(); return texture; } private String fixToolTexture() { if (ibrush == -1 || ibrushside == -1) { return null; } DBrush brush = bsp.brushes.get(ibrush); DBrushSide brushSide = bsp.brushSides.get(ibrushside); Set<SurfaceFlag> surfFlags; if (brushSide.texinfo == DTexInfo.TEXINFO_NODE) { surfFlags = EnumSet.noneOf(SurfaceFlag.class); } else { surfFlags = bsp.texinfos.get(brushSide.texinfo).flags; } Set<BrushFlag> brushFlags = brush.contents; // fix clip textures if (surfFlags.equals(SURF_FLAGS_CLIP)) { if (brush.isDetail()) { // clip if (brush.isPlayerClip() && brush.isNpcClip()) { return ToolTexture.CLIP; } // player clip if (brush.isPlayerClip()) { return ToolTexture.PLAYERCLIP; } // NPC clip if (brush.isNpcClip()) { return ToolTexture.NPCCLIP; } // block line of sight if (brush.isBlockLos()) { return ToolTexture.BLOCKLOS; } } // nodraw return ToolTexture.NODRAW; } // fix areaportal textures if (brushFlags.equals(BRUSH_FLAGS_AREAPORTAL) && surfFlags.equals(SURF_FLAGS_AREAPORTAL)) { return ToolTexture.AREAPORTAL; } return null; } /** * Checks for texture axes that are perpendicular to the normal and fixes them. */ private void fixPerpendicular() { if (normal == null) { return; } Vector3f texNorm = texture.getVAxis().axis.cross(texture.getUAxis().axis); if (Math.abs(normal.dot(texNorm)) >= EPS_PERP) { return; } // z is z-direction unit vector Vector3f udir = new Vector3f(0, 0, 1); // if angle of normal to z axis is less than ~25 degrees if (Math.abs(udir.dot(normal)) > Math.sin(45)) { // use x unit-vector as basis udir = new Vector3f(1, 0, 0); } Vector3f tv1 = udir.cross(normal).normalize(); // 1st tex vector Vector3f tv2 = tv1.cross(normal).normalize(); // 2nd tex vector texture.setUAxis(new TextureAxis(tv1)); texture.setVAxis(new TextureAxis(tv2)); } private void buildLightmapScale() { // extract lightmap vectors float[][] lvec = texinfo.lightmapVecsLuxels; Vector3f uaxis = new Vector3f(lvec[0]); Vector3f vaxis = new Vector3f(lvec[1]); float ls = (uaxis.length() + vaxis.length()) / 2.0f; if (ls > 0.001f) { texture.setLightmapScale(Math.round(1.0f / ls)); } } private void buildUV() { // extract texture vectors float[][] tvec = texinfo.textureVecsTexels; Vector3f uaxis = new Vector3f(tvec[0]); Vector3f vaxis = new Vector3f(tvec[1]); float utw = 1.0f / uaxis.length(); float vtw = 1.0f / vaxis.length(); uaxis = uaxis.scalar(utw); vaxis = vaxis.scalar(vtw); float ushift = tvec[0][3]; float vshift = tvec[1][3]; // translate to origin if (origin != null) { ushift -= origin.dot(uaxis) / utw; vshift -= origin.dot(vaxis) / vtw; } // rotate texture axes if (angles != null) { uaxis = uaxis.rotate(angles); vaxis = vaxis.rotate(angles); // calculate the shift in U/V space due to this rotation Vector3f shift = Vector3f.NULL; if (origin != null) { shift = shift.sub(origin); } shift = shift.rotate(angles); if (origin != null) { shift = shift.add(origin); } ushift -= shift.dot(uaxis) / utw; vshift -= shift.dot(vaxis) / vtw; } // normalize shift values if (texdata.width != 0) { ushift %= texdata.width; } if (texdata.height != 0) { vshift %= texdata.height; } // round scales to 4 decimal digits to fix round-off errors // (e.g.: 0.25000018 -> 0.25) utw = Math.round(utw * 10000) / 10000f; vtw = Math.round(vtw * 10000) / 10000f; // create texture axes texture.setUAxis(new TextureAxis(uaxis, Math.round(ushift), utw)); texture.setVAxis(new TextureAxis(vaxis, Math.round(vshift), vtw)); } public void setOrigin(Vector3f origin) { this.origin = origin; } public void setAngles(Vector3f angles) { this.angles = angles; } public void setNormal(Vector3f normal) { this.normal = normal; } public void setBrushIndex(int ibrush) { this.ibrush = ibrush; } public void setBrushSideIndex(int ibrushside) { this.ibrushside = ibrushside; } public void setTexinfoIndex(short itexinfo) { this.itexinfo = itexinfo; } }