/*
* Copyright (C) 1997-2001 Id Software, Inc.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 59 Temple
* Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
/* Modifications
Copyright 2003-2004 Bytonic Software
Copyright 2010 Google Inc.
*/
package com.googlecode.gwtquake.shared.common;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.Arrays;
import com.googlecode.gwtquake.shared.game.ConsoleVariable;
import com.googlecode.gwtquake.shared.game.MapSurface;
import com.googlecode.gwtquake.shared.game.Model;
import com.googlecode.gwtquake.shared.game.Plane;
import com.googlecode.gwtquake.shared.game.Trace;
import com.googlecode.gwtquake.shared.util.Lib;
import com.googlecode.gwtquake.shared.util.Math3D;
import com.googlecode.gwtquake.shared.util.Vargs;
import com.googlecode.gwtquake.shared.util.Vec3Cache;
public class CM {
public static class cnode_t {
Plane plane; // ptr
int children[] = { 0, 0 }; // negative numbers are leafs
}
public static class cbrushside_t {
Plane plane; // ptr
MapSurface surface; // ptr
}
public static class cleaf_t {
int contents;
int cluster;
int area;
// was unsigned short, but is ok (rst)
short firstleafbrush;
// was unsigned short, but is ok (rst)
short numleafbrushes;
}
public static class cbrush_t {
int contents;
int numsides;
int firstbrushside;
int checkcount; // to avoid repeated testings
}
public static class carea_t {
int numareaportals;
int firstareaportal;
int floodnum; // if two areas have equal floodnums, they are connected
int floodvalid;
}
static int checkcount;
static String map_name = "";
static int numbrushsides;
static cbrushside_t map_brushsides[] = new cbrushside_t[Constants.MAX_MAP_BRUSHSIDES];
static {
for (int n = 0; n < Constants.MAX_MAP_BRUSHSIDES; n++)
map_brushsides[n] = new cbrushside_t();
}
public static int numtexinfo;
public static MapSurface map_surfaces[] = new MapSurface[Constants.MAX_MAP_TEXINFO];
static {
for (int n = 0; n < Constants.MAX_MAP_TEXINFO; n++)
map_surfaces[n] = new MapSurface();
}
static int numplanes;
/** Extra for box hull ( +6) */
static Plane map_planes[] = new Plane[Constants.MAX_MAP_PLANES + 6];
static {
for (int n = 0; n < Constants.MAX_MAP_PLANES + 6; n++)
map_planes[n] = new Plane();
}
static int numnodes;
/** Extra for box hull ( +6) */
static cnode_t map_nodes[] = new cnode_t[Constants.MAX_MAP_NODES + 6];
static {
for (int n = 0; n < Constants.MAX_MAP_NODES + 6; n++)
map_nodes[n] = new cnode_t();
}
static int numleafs = 1; // allow leaf funcs to be called without a map
static cleaf_t map_leafs[] = new cleaf_t[Constants.MAX_MAP_LEAFS];
static {
for (int n = 0; n < Constants.MAX_MAP_LEAFS; n++)
map_leafs[n] = new cleaf_t();
}
static int emptyleaf, solidleaf;
static int numleafbrushes;
public static int map_leafbrushes[] = new int[Constants.MAX_MAP_LEAFBRUSHES];
public static int numcmodels;
public static Model map_cmodels[] = new Model[Constants.MAX_MAP_MODELS];
static {
for (int n = 0; n < Constants.MAX_MAP_MODELS; n++)
map_cmodels[n] = new Model();
}
public static int numbrushes;
public static cbrush_t map_brushes[] = new cbrush_t[Constants.MAX_MAP_BRUSHES];
static {
for (int n = 0; n < Constants.MAX_MAP_BRUSHES; n++)
map_brushes[n] = new cbrush_t();
}
public static int numvisibility;
public static byte map_visibility[] = new byte[Constants.MAX_MAP_VISIBILITY];
/** Main visibility data. */
public static QuakeFiles.dvis_t map_vis = new QuakeFiles.dvis_t(ByteBuffer
.wrap(map_visibility));
public static int numentitychars;
public static String map_entitystring;
public static int numareas = 1;
public static carea_t map_areas[] = new carea_t[Constants.MAX_MAP_AREAS];
static {
for (int n = 0; n < Constants.MAX_MAP_AREAS; n++)
map_areas[n] = new carea_t();
}
public static int numareaportals;
public static QuakeFiles.dareaportal_t map_areaportals[] = new QuakeFiles.dareaportal_t[Constants.MAX_MAP_AREAPORTALS];
static {
for (int n = 0; n < Constants.MAX_MAP_AREAPORTALS; n++)
map_areaportals[n] = new QuakeFiles.dareaportal_t();
}
public static int numclusters = 1;
public static MapSurface nullsurface = new MapSurface();
public static int floodvalid;
public static boolean portalopen[] = new boolean[Constants.MAX_MAP_AREAPORTALS];
public static ConsoleVariable map_noareas;
public static ByteBuffer cmod_base;
public static int checksum;
public static int last_checksum;
/**
* Determines whether the given map is already loaded (call this before
* CM_LoadMap()). On the server, it will also reset portals/areas if the
* map is to be reused.
*/
public static Model CM_IsMapLoaded(String name, boolean clientload) {
if (map_name.equals(name)
&& (clientload || 0 == ConsoleVariables.VariableValue("flushmap"))) {
if (!clientload) {
Arrays.fill(portalopen, false);
FloodAreaConnections();
}
return map_cmodels[0]; // still have the right version
}
return null;
}
public interface ModelCallback {
public void onSuccess(Model model);
}
public static void CM_LoadMap(String name, boolean clientload,
int checksum[]) {
CM_LoadMap(name, clientload, checksum, null);
}
/**
* Loads in the map and all submodels.
*/
public static void CM_LoadMap(final String name, final boolean clientload,
final int checksum[], final ModelCallback callback) {
Com.DPrintf("CM_LoadMap(" + name + ")...\n");
map_noareas = ConsoleVariables.Get("map_noareas", "0", 0);
if (map_name.equals(name)
&& (clientload || 0 == ConsoleVariables.VariableValue("flushmap"))) {
checksum[0] = last_checksum;
if (!clientload) {
Arrays.fill(portalopen, false);
FloodAreaConnections();
}
if (callback != null) {
callback.onSuccess(map_cmodels[0]); // still have the right version
}
return;
}
// free old stuff
numnodes = 0;
numleafs = 0;
numcmodels = 0;
numvisibility = 0;
numentitychars = 0;
map_entitystring = "";
map_name = "";
if (name == null || name.length() == 0) {
numleafs = 1;
numclusters = 1;
numareas = 1;
checksum[0] = 0;
if (callback != null) {
callback.onSuccess( map_cmodels[0]);
}
return;
// cinematic servers won't have anything at all
}
//
// load the file
//
ResourceLoader.loadResourceAsync(name, new ResourceLoader.Callback() {
public void onSuccess(ByteBuffer bbuf) {
int i;
last_checksum++; // = MD4.Com_BlockChecksum(buf, length);
checksum[0] = last_checksum;
QuakeFiles.dheader_t header = new QuakeFiles.dheader_t(bbuf.slice());
if (header.version != Constants.BSPVERSION)
Com.Error(Constants.ERR_DROP, "CMod_LoadBrushModel: " + name
+ " has wrong version number (" + header.version
+ " should be " + Constants.BSPVERSION + ")");
cmod_base = bbuf.order(ByteOrder.LITTLE_ENDIAN);
// load into heap
CMod_LoadSurfaces(header.lumps[Constants.LUMP_TEXINFO]); // ok
CMod_LoadLeafs(header.lumps[Constants.LUMP_LEAFS]);
CMod_LoadLeafBrushes(header.lumps[Constants.LUMP_LEAFBRUSHES]);
CMod_LoadPlanes(header.lumps[Constants.LUMP_PLANES]);
CMod_LoadBrushes(header.lumps[Constants.LUMP_BRUSHES]);
CMod_LoadBrushSides(header.lumps[Constants.LUMP_BRUSHSIDES]);
CMod_LoadSubmodels(header.lumps[Constants.LUMP_MODELS]);
CMod_LoadNodes(header.lumps[Constants.LUMP_NODES]);
CMod_LoadAreas(header.lumps[Constants.LUMP_AREAS]);
CMod_LoadAreaPortals(header.lumps[Constants.LUMP_AREAPORTALS]);
CMod_LoadVisibility(header.lumps[Constants.LUMP_VISIBILITY]);
CMod_LoadEntityString(header.lumps[Constants.LUMP_ENTITIES]);
CM_InitBoxHull();
Arrays.fill(portalopen, false);
FloodAreaConnections();
map_name = name;
if (callback != null) {
callback.onSuccess(map_cmodels[0]);
}
}
});
}
/** Loads Submodels. */
public static void CMod_LoadSubmodels(Lump l) {
Com.DPrintf("CMod_LoadSubmodels()\n");
QuakeFiles.dmodel_t in;
Model out;
int i, j, count;
if ((l.filelen % QuakeFiles.dmodel_t.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "CMod_LoadBmodel: funny lump size");
count = l.filelen / QuakeFiles.dmodel_t.SIZE;
if (count < 1)
Com.Error(Constants.ERR_DROP, "Map with no models");
if (count > Constants.MAX_MAP_MODELS)
Com.Error(Constants.ERR_DROP, "Map has too many models");
Com.DPrintf(" numcmodels=" + count + "\n");
numcmodels = count;
if (debugloadmap) {
Com.DPrintf("submodles(headnode, <origin>, <mins>, <maxs>)\n");
}
for (i = 0; i < count; i++) {
// in = new qfiles.dmodel_t(ByteBuffer.wrap(cmod_base, i
// * qfiles.dmodel_t.SIZE + l.fileofs, qfiles.dmodel_t.SIZE));
cmod_base.position(i * QuakeFiles.dmodel_t.SIZE + l.fileofs);
in = new QuakeFiles.dmodel_t(cmod_base);
out = map_cmodels[i];
for (j = 0; j < 3; j++) { // spread the mins / maxs by a pixel
out.mins[j] = in.mins[j] - 1;
out.maxs[j] = in.maxs[j] + 1;
out.origin[j] = in.origin[j];
}
out.headnode = in.headnode;
if (debugloadmap) {
Com
.DPrintf(
"|%6i|%8.2f|%8.2f|%8.2f| %8.2f|%8.2f|%8.2f| %8.2f|%8.2f|%8.2f|\n",
new Vargs().add(out.headnode)
.add(out.origin[0]).add(out.origin[1])
.add(out.origin[2]).add(out.mins[0])
.add(out.mins[1]).add(out.mins[2]).add(
out.maxs[0]).add(out.maxs[1])
.add(out.maxs[2]));
}
}
}
static boolean debugloadmap = false;
/** Loads surfaces. */
public static void CMod_LoadSurfaces(Lump l) {
Com.DPrintf("CMod_LoadSurfaces()\n");
TextureInfo in;
MapSurface out;
int i, count;
if ((l.filelen % TextureInfo.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size");
count = l.filelen / TextureInfo.SIZE;
if (count < 1)
Com.Error(Constants.ERR_DROP, "Map with no surfaces");
if (count > Constants.MAX_MAP_TEXINFO)
Com.Error(Constants.ERR_DROP, "Map has too many surfaces");
numtexinfo = count;
Com.DPrintf(" numtexinfo=" + count + "\n");
if (debugloadmap)
Com.DPrintf("surfaces:\n");
for (i = 0; i < count; i++) {
out = map_surfaces[i] = new MapSurface();
// in = new texinfo_t(cmod_base, l.fileofs + i * texinfo_t.SIZE,
// texinfo_t.SIZE);
cmod_base.position(l.fileofs + i * TextureInfo.SIZE);
in = new TextureInfo(cmod_base);
out.c.name = in.texture;
out.rname = in.texture;
out.c.flags = in.flags;
out.c.value = in.value;
if (debugloadmap) {
Com.DPrintf("|%20s|%20s|%6i|%6i|\n", new Vargs()
.add(out.c.name).add(out.rname).add(out.c.value).add(
out.c.flags));
}
}
}
/** Loads nodes. */
public static void CMod_LoadNodes(Lump l) {
Com.DPrintf("CMod_LoadNodes()\n");
QuakeFiles.dnode_t in;
int child;
cnode_t out;
int i, j, count;
if ((l.filelen % QuakeFiles.dnode_t.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size:"
+ l.fileofs + "," + QuakeFiles.dnode_t.SIZE);
count = l.filelen / QuakeFiles.dnode_t.SIZE;
if (count < 1)
Com.Error(Constants.ERR_DROP, "Map has no nodes");
if (count > Constants.MAX_MAP_NODES)
Com.Error(Constants.ERR_DROP, "Map has too many nodes");
numnodes = count;
Com.DPrintf(" numnodes=" + count + "\n");
if (debugloadmap) {
Com.DPrintf("nodes(planenum, child[0], child[1])\n");
}
for (i = 0; i < count; i++) {
// in = new qfiles.dnode_t(ByteBuffer.wrap(cmod_base,
// qfiles.dnode_t.SIZE * i + l.fileofs, qfiles.dnode_t.SIZE));
cmod_base.position(QuakeFiles.dnode_t.SIZE * i + l.fileofs);
in = new QuakeFiles.dnode_t(cmod_base);
out = map_nodes[i];
out.plane = map_planes[in.planenum];
for (j = 0; j < 2; j++) {
child = in.children[j];
out.children[j] = child;
}
if (debugloadmap) {
Com.DPrintf("|%6i| %6i| %6i|\n", new Vargs().add(in.planenum)
.add(out.children[0]).add(out.children[1]));
}
}
}
/** Loads brushes.*/
public static void CMod_LoadBrushes(Lump l) {
Com.DPrintf("CMod_LoadBrushes()\n");
QuakeFiles.dbrush_t in;
cbrush_t out;
int i, count;
if ((l.filelen % QuakeFiles.dbrush_t.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size");
count = l.filelen / QuakeFiles.dbrush_t.SIZE;
if (count > Constants.MAX_MAP_BRUSHES)
Com.Error(Constants.ERR_DROP, "Map has too many brushes");
numbrushes = count;
Com.DPrintf(" numbrushes=" + count + "\n");
if (debugloadmap) {
Com.DPrintf("brushes:(firstbrushside, numsides, contents)\n");
}
for (i = 0; i < count; i++) {
// in = new qfiles.dbrush_t(ByteBuffer.wrap(cmod_base, i
// * qfiles.dbrush_t.SIZE + l.fileofs, qfiles.dbrush_t.SIZE));
cmod_base.position(i * QuakeFiles.dbrush_t.SIZE + l.fileofs);
in = new QuakeFiles.dbrush_t(cmod_base);
out = map_brushes[i];
out.firstbrushside = in.firstside;
out.numsides = in.numsides;
out.contents = in.contents;
if (debugloadmap) {
Com
.DPrintf("| %6i| %6i| %8X|\n", new Vargs().add(
out.firstbrushside).add(out.numsides).add(
out.contents));
}
}
}
/** Loads leafs. */
public static void CMod_LoadLeafs(Lump l) {
Com.DPrintf("CMod_LoadLeafs()\n");
int i;
cleaf_t out;
QuakeFiles.dleaf_t in;
int count;
if ((l.filelen % QuakeFiles.dleaf_t.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size");
count = l.filelen / QuakeFiles.dleaf_t.SIZE;
if (count < 1)
Com.Error(Constants.ERR_DROP, "Map with no leafs");
// need to save space for box planes
if (count > Constants.MAX_MAP_PLANES)
Com.Error(Constants.ERR_DROP, "Map has too many planes");
Com.DPrintf(" numleafes=" + count + "\n");
numleafs = count;
numclusters = 0;
if (debugloadmap)
Com.DPrintf("cleaf-list:(contents, cluster, area, firstleafbrush, numleafbrushes)\n");
for (i = 0; i < count; i++) {
// in = new qfiles.dleaf_t(cmod_base, i * qfiles.dleaf_t.SIZE
// + l.fileofs, qfiles.dleaf_t.SIZE);
cmod_base.position(i * QuakeFiles.dleaf_t.SIZE + l.fileofs);
in = new QuakeFiles.dleaf_t(cmod_base);
out = map_leafs[i];
out.contents = in.contents;
out.cluster = in.cluster;
out.area = in.area;
out.firstleafbrush = (short) in.firstleafbrush;
out.numleafbrushes = (short) in.numleafbrushes;
if (out.cluster >= numclusters)
numclusters = out.cluster + 1;
if (debugloadmap) {
Com.DPrintf("|%8x|%6i|%6i|%6i|\n", new Vargs()
.add(out.contents).add(out.cluster).add(out.area).add(
out.firstleafbrush).add(out.numleafbrushes));
}
}
Com.DPrintf(" numclusters=" + numclusters + "\n");
if (map_leafs[0].contents != Constants.CONTENTS_SOLID)
Com.Error(Constants.ERR_DROP, "Map leaf 0 is not CONTENTS_SOLID");
solidleaf = 0;
emptyleaf = -1;
for (i = 1; i < numleafs; i++) {
if (map_leafs[i].contents == 0) {
emptyleaf = i;
break;
}
}
if (emptyleaf == -1)
Com.Error(Constants.ERR_DROP, "Map does not have an empty leaf");
}
/** Loads planes. */
public static void CMod_LoadPlanes(Lump l) {
Com.DPrintf("CMod_LoadPlanes()\n");
int i, j;
Plane out;
QuakeFiles.dplane_t in;
int count;
int bits;
if ((l.filelen % QuakeFiles.dplane_t.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size");
count = l.filelen / QuakeFiles.dplane_t.SIZE;
if (count < 1)
Com.Error(Constants.ERR_DROP, "Map with no planes");
// need to save space for box planes
if (count > Constants.MAX_MAP_PLANES)
Com.Error(Constants.ERR_DROP, "Map has too many planes");
Com.DPrintf(" numplanes=" + count + "\n");
numplanes = count;
if (debugloadmap) {
Com
.DPrintf("cplanes(normal[0],normal[1],normal[2], dist, type, signbits)\n");
}
for (i = 0; i < count; i++) {
// in = new qfiles.dplane_t(ByteBuffer.wrap(cmod_base, i
// * qfiles.dplane_t.SIZE + l.fileofs, qfiles.dplane_t.SIZE));
cmod_base.position(i * QuakeFiles.dplane_t.SIZE + l.fileofs);
in = new QuakeFiles.dplane_t(cmod_base);
out = map_planes[i];
bits = 0;
for (j = 0; j < 3; j++) {
out.normal[j] = in.normal[j];
if (out.normal[j] < 0)
bits |= 1 << j;
}
out.dist = in.dist;
out.type = (byte) in.type;
out.signbits = (byte) bits;
if (debugloadmap) {
Com.DPrintf("|%6.2f|%6.2f|%6.2f| %10.2f|%3i| %1i|\n",
new Vargs().add(out.normal[0]).add(out.normal[1]).add(
out.normal[2]).add(out.dist).add(out.type).add(
out.signbits));
}
}
}
/** Loads leaf brushes. */
public static void CMod_LoadLeafBrushes(Lump l) {
Com.DPrintf("CMod_LoadLeafBrushes()\n");
int i;
int out[];
int count;
if ((l.filelen % 2) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size");
count = l.filelen / 2;
Com.DPrintf(" numbrushes=" + count + "\n");
if (count < 1)
Com.Error(Constants.ERR_DROP, "Map with no planes");
// need to save space for box planes
if (count > Constants.MAX_MAP_LEAFBRUSHES)
Com.Error(Constants.ERR_DROP, "Map has too many leafbrushes");
out = map_leafbrushes;
numleafbrushes = count;
// ByteBuffer bb = ByteBuffer.wrap(cmod_base, l.fileofs, count * 2).order(
// ByteOrder.LITTLE_ENDIAN);
ByteBuffer bb = cmod_base;
cmod_base.position(l.fileofs);
if (debugloadmap) {
Com.DPrintf("map_brushes:\n");
}
for (i = 0; i < count; i++) {
out[i] = bb.getShort();
if (debugloadmap) {
Com.DPrintf("|%6i|%6i|\n", new Vargs().add(i).add(out[i]));
}
}
}
/** Loads brush sides. */
public static void CMod_LoadBrushSides(Lump l) {
Com.DPrintf("CMod_LoadBrushSides()\n");
int i, j;
cbrushside_t out;
QuakeFiles.dbrushside_t in;
int count;
int num;
if ((l.filelen % QuakeFiles.dbrushside_t.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size");
count = l.filelen / QuakeFiles.dbrushside_t.SIZE;
// need to save space for box planes
if (count > Constants.MAX_MAP_BRUSHSIDES)
Com.Error(Constants.ERR_DROP, "Map has too many planes");
numbrushsides = count;
Com.DPrintf(" numbrushsides=" + count + "\n");
if (debugloadmap) {
Com.DPrintf("brushside(planenum, surfacenum):\n");
}
for (i = 0; i < count; i++) {
// in = new qfiles.dbrushside_t(ByteBuffer.wrap(cmod_base, i
// * qfiles.dbrushside_t.SIZE + l.fileofs,
// qfiles.dbrushside_t.SIZE));
cmod_base.position(i * QuakeFiles.dbrushside_t.SIZE + l.fileofs);
in = new QuakeFiles.dbrushside_t(cmod_base);
out = map_brushsides[i];
num = in.planenum;
out.plane = map_planes[num]; // pointer
j = in.texinfo;
if (j >= numtexinfo)
Com.Error(Constants.ERR_DROP, "Bad brushside texinfo");
// java specific handling of -1
if (j == -1)
out.surface = new MapSurface(); // just for safety
else
out.surface = map_surfaces[j];
if (debugloadmap) {
Com.DPrintf("| %6i| %6i|\n", new Vargs().add(num).add(j));
}
}
}
/** Loads areas. */
public static void CMod_LoadAreas(Lump l) {
Com.DPrintf("CMod_LoadAreas()\n");
int i;
carea_t out;
QuakeFiles.darea_t in;
int count;
if ((l.filelen % QuakeFiles.darea_t.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size");
count = l.filelen / QuakeFiles.darea_t.SIZE;
if (count > Constants.MAX_MAP_AREAS)
Com.Error(Constants.ERR_DROP, "Map has too many areas");
Com.DPrintf(" numareas=" + count + "\n");
numareas = count;
if (debugloadmap) {
Com.DPrintf("areas(numportals, firstportal)\n");
}
for (i = 0; i < count; i++) {
// in = new qfiles.darea_t(ByteBuffer.wrap(cmod_base, i
// * qfiles.darea_t.SIZE + l.fileofs, qfiles.darea_t.SIZE));
cmod_base.position(i * QuakeFiles.darea_t.SIZE + l.fileofs);
in = new QuakeFiles.darea_t(cmod_base);
out = map_areas[i];
out.numareaportals = in.numareaportals;
out.firstareaportal = in.firstareaportal;
out.floodvalid = 0;
out.floodnum = 0;
if (debugloadmap) {
Com.DPrintf("| %6i| %6i|\n", new Vargs()
.add(out.numareaportals).add(out.firstareaportal));
}
}
}
/** Loads area portals. */
public static void CMod_LoadAreaPortals(Lump l) {
Com.DPrintf("CMod_LoadAreaPortals()\n");
int i;
QuakeFiles.dareaportal_t out;
QuakeFiles.dareaportal_t in;
int count;
if ((l.filelen % QuakeFiles.dareaportal_t.SIZE) != 0)
Com.Error(Constants.ERR_DROP, "MOD_LoadBmodel: funny lump size");
count = l.filelen / QuakeFiles.dareaportal_t.SIZE;
if (count > Constants.MAX_MAP_AREAS)
Com.Error(Constants.ERR_DROP, "Map has too many areas");
numareaportals = count;
Com.DPrintf(" numareaportals=" + count + "\n");
if (debugloadmap) {
Com.DPrintf("areaportals(portalnum, otherarea)\n");
}
for (i = 0; i < count; i++) {
// in = new qfiles.dareaportal_t(ByteBuffer.wrap(cmod_base, i
// * qfiles.dareaportal_t.SIZE + l.fileofs,
// qfiles.dareaportal_t.SIZE));
cmod_base.position(i * QuakeFiles.dareaportal_t.SIZE + l.fileofs);
in = new QuakeFiles.dareaportal_t(cmod_base);
out = map_areaportals[i];
out.portalnum = in.portalnum;
out.otherarea = in.otherarea;
if (debugloadmap) {
Com.DPrintf("|%6i|%6i|\n", new Vargs().add(out.portalnum).add(
out.otherarea));
}
}
}
/** Loads visibility data. */
public static void CMod_LoadVisibility(Lump l) {
Com.DPrintf("CMod_LoadVisibility()\n");
numvisibility = l.filelen;
Com.DPrintf(" numvisibility=" + numvisibility + "\n");
if (l.filelen > Constants.MAX_MAP_VISIBILITY)
Com.Error(Constants.ERR_DROP, "Map has too large visibility lump");
// System.arraycopy(cmod_base, l.fileofs, map_visibility, 0, l.filelen);
// ByteBuffer bb = ByteBuffer.wrap(map_visibility, 0, l.filelen);
// bb.order(ByteOrder.LITTLE_ENDIAN);
// map_vis = new qfiles.dvis_t(bb);
cmod_base.position(l.fileofs);
cmod_base.get(map_visibility, 0, l.filelen);
// TODO(jgw): can we avoid the extra ByteBuffer here? Seems kind of silly.
ByteBuffer bb = ByteBuffer.wrap(map_visibility, 0, l.filelen);
bb.order(ByteOrder.LITTLE_ENDIAN);
map_vis = new QuakeFiles.dvis_t(bb);
}
/** Loads entity strings. */
public static void CMod_LoadEntityString(Lump l) {
Com.DPrintf("CMod_LoadEntityString()\n");
numentitychars = l.filelen;
if (l.filelen > Constants.MAX_MAP_ENTSTRING)
Com.Error(Constants.ERR_DROP, "Map has too large entity lump");
int x = 0;
// for (; x < l.filelen && cmod_base[x + l.fileofs] != 0; x++);
// map_entitystring = new String(Util.toCharArray(cmod_base, l.fileofs, x)).trim();
cmod_base.position(l.fileofs);
map_entitystring = "";
while (true) {
x = cmod_base.get();
if (x == 0) {
break;
}
map_entitystring += (char)x;
}
Com.dprintln("entitystring=" + map_entitystring.length() +
" bytes, [" + map_entitystring.substring(0, Math.min (
map_entitystring.length(), 15)) + "...]" );
}
/** Returns the model with a given id "*" + <number> */
public static Model InlineModel(String name) {
int num;
if (name == null || name.charAt(0) != '*')
Com.Error(Constants.ERR_DROP, "CM_InlineModel: bad name");
num = Lib.atoi(name.substring(1));
if (num < 1 || num >= numcmodels)
Com.Error(Constants.ERR_DROP, "CM_InlineModel: bad number: " + num + " not in range [1.." +numcmodels+"[; name: " + name);
return map_cmodels[num];
}
public static int CM_NumClusters() {
return numclusters;
}
public static int CM_NumInlineModels() {
return numcmodels;
}
public static String CM_EntityString() {
return map_entitystring;
}
public static int CM_LeafContents(int leafnum) {
if (leafnum < 0 || leafnum >= numleafs)
Com.Error(Constants.ERR_DROP, "CM_LeafContents: bad number");
return map_leafs[leafnum].contents;
}
public static int CM_LeafCluster(int leafnum) {
if (leafnum < 0 || leafnum >= numleafs)
Com.Error(Constants.ERR_DROP, "CM_LeafCluster: bad number");
return map_leafs[leafnum].cluster;
}
public static int CM_LeafArea(int leafnum) {
if (leafnum < 0 || leafnum >= numleafs)
Com.Error(Constants.ERR_DROP, "CM_LeafArea: bad number");
return map_leafs[leafnum].area;
}
static Plane box_planes[];
static int box_headnode;
static cbrush_t box_brush;
static cleaf_t box_leaf;
/** Set up the planes and nodes so that the six floats of a bounding box can
* just be stored out and get a proper clipping hull structure.
*/
public static void CM_InitBoxHull() {
int i;
int side;
cnode_t c;
Plane p;
cbrushside_t s;
box_headnode = numnodes; //rst: still room for 6 brushes left?
box_planes = new Plane[] { map_planes[numplanes],
map_planes[numplanes + 1], map_planes[numplanes + 2],
map_planes[numplanes + 3], map_planes[numplanes + 4],
map_planes[numplanes + 5], map_planes[numplanes + 6],
map_planes[numplanes + 7], map_planes[numplanes + 8],
map_planes[numplanes + 9], map_planes[numplanes + 10],
map_planes[numplanes + 11], map_planes[numplanes + 12] };
if (numnodes + 6 > Constants.MAX_MAP_NODES
|| numbrushes + 1 > Constants.MAX_MAP_BRUSHES
|| numleafbrushes + 1 > Constants.MAX_MAP_LEAFBRUSHES
|| numbrushsides + 6 > Constants.MAX_MAP_BRUSHSIDES
|| numplanes + 12 > Constants.MAX_MAP_PLANES)
Com.Error(Constants.ERR_DROP, "Not enough room for box tree");
box_brush = map_brushes[numbrushes];
box_brush.numsides = 6;
box_brush.firstbrushside = numbrushsides;
box_brush.contents = Constants.CONTENTS_MONSTER;
box_leaf = map_leafs[numleafs];
box_leaf.contents = Constants.CONTENTS_MONSTER;
box_leaf.firstleafbrush = (short) numleafbrushes;
box_leaf.numleafbrushes = 1;
map_leafbrushes[numleafbrushes] = numbrushes;
for (i = 0; i < 6; i++) {
side = i & 1;
// brush sides
s = map_brushsides[numbrushsides + i];
s.plane = map_planes[(numplanes + i * 2 + side)];
s.surface = nullsurface;
// nodes
c = map_nodes[box_headnode + i];
c.plane = map_planes[(numplanes + i * 2)];
c.children[side] = -1 - emptyleaf;
if (i != 5)
c.children[side ^ 1] = box_headnode + i + 1;
else
c.children[side ^ 1] = -1 - numleafs;
// planes
p = box_planes[i * 2];
p.type = (byte) (i >> 1);
p.signbits = 0;
Math3D.VectorClear(p.normal);
p.normal[i >> 1] = 1;
p = box_planes[i * 2 + 1];
p.type = (byte) (3 + (i >> 1));
p.signbits = 0;
Math3D.VectorClear(p.normal);
p.normal[i >> 1] = -1;
}
}
/** To keep everything totally uniform, bounding boxes are turned into small
* BSP trees instead of being compared directly. */
public static int HeadnodeForBox(float[] mins, float[] maxs) {
box_planes[0].dist = maxs[0];
box_planes[1].dist = -maxs[0];
box_planes[2].dist = mins[0];
box_planes[3].dist = -mins[0];
box_planes[4].dist = maxs[1];
box_planes[5].dist = -maxs[1];
box_planes[6].dist = mins[1];
box_planes[7].dist = -mins[1];
box_planes[8].dist = maxs[2];
box_planes[9].dist = -maxs[2];
box_planes[10].dist = mins[2];
box_planes[11].dist = -mins[2];
return box_headnode;
}
/** Recursively searches the leaf number that contains the 3d point. */
private static int CM_PointLeafnum_r(float[] p, int num) {
float d;
cnode_t node;
Plane plane;
while (num >= 0) {
node = map_nodes[num];
plane = node.plane;
if (plane.type < 3)
d = p[plane.type] - plane.dist;
else
d = Math3D.DotProduct(plane.normal, p) - plane.dist;
if (d < 0)
num = node.children[1];
else
num = node.children[0];
}
Globals.c_pointcontents++; // optimize counter
return -1 - num;
}
/** Searches the leaf number that contains the 3d point. */
public static int CM_PointLeafnum(float[] p) {
// sound may call this without map loaded
if (numplanes == 0)
return 0;
return CM_PointLeafnum_r(p, 0);
}
private static int leaf_count, leaf_maxcount;
private static int leaf_list[];
private static float leaf_mins[], leaf_maxs[];
private static int leaf_topnode;
/** Recursively fills in a list of all the leafs touched. */
private static void CM_BoxLeafnums_r(int nodenum) {
Plane plane;
cnode_t node;
int s;
while (true) {
if (nodenum < 0) {
if (leaf_count >= leaf_maxcount) {
// TODO(jgw): Had to turn this off, because it gets called incessantly.
// Com.DPrintf("CM_BoxLeafnums_r: overflow\n");
return;
}
leaf_list[leaf_count++] = -1 - nodenum;
return;
}
node = map_nodes[nodenum];
plane = node.plane;
s = Math3D.BoxOnPlaneSide(leaf_mins, leaf_maxs, plane);
if (s == 1)
nodenum = node.children[0];
else if (s == 2)
nodenum = node.children[1];
else {
// go down both
if (leaf_topnode == -1)
leaf_topnode = nodenum;
CM_BoxLeafnums_r(node.children[0]);
nodenum = node.children[1];
}
}
}
/** Fills in a list of all the leafs touched and starts with the head node. */
private static int CM_BoxLeafnums_headnode(float[] mins, float[] maxs,
int list[], int listsize, int headnode, int topnode[]) {
leaf_list = list;
leaf_count = 0;
leaf_maxcount = listsize;
leaf_mins = mins;
leaf_maxs = maxs;
leaf_topnode = -1;
CM_BoxLeafnums_r(headnode);
if (topnode != null)
topnode[0] = leaf_topnode;
return leaf_count;
}
/** Fills in a list of all the leafs touched. */
public static int CM_BoxLeafnums(float[] mins, float[] maxs, int list[],
int listsize, int topnode[]) {
return CM_BoxLeafnums_headnode(mins, maxs, list, listsize,
map_cmodels[0].headnode, topnode);
}
/** Returns a tag that describes the content of the point. */
public static int PointContents(float[] p, int headnode) {
int l;
if (numnodes == 0) // map not loaded
return 0;
l = CM_PointLeafnum_r(p, headnode);
return map_leafs[l].contents;
}
/*
* ================== CM_TransformedPointContents
*
* Handles offseting and rotation of the end points for moving and rotating
* entities ==================
*/
public static int TransformedPointContents(float[] p, int headnode,
float[] origin, float[] angles) {
float[] p_l = { 0, 0, 0 };
float[] temp = { 0, 0, 0 };
float[] forward = { 0, 0, 0 }, right = { 0, 0, 0 }, up = { 0, 0, 0 };
int l;
// subtract origin offset
Math3D.VectorSubtract(p, origin, p_l);
// rotate start and end into the models frame of reference
if (headnode != box_headnode
&& (angles[0] != 0 || angles[1] != 0 || angles[2] != 0)) {
Math3D.AngleVectors(angles, forward, right, up);
Math3D.VectorCopy(p_l, temp);
p_l[0] = Math3D.DotProduct(temp, forward);
p_l[1] = -Math3D.DotProduct(temp, right);
p_l[2] = Math3D.DotProduct(temp, up);
}
l = CM_PointLeafnum_r(p_l, headnode);
return map_leafs[l].contents;
}
/*
* ===============================================================================
*
* BOX TRACING
*
* ===============================================================================
*/
// 1/32 epsilon to keep floating point happy
private static final float DIST_EPSILON = 0.03125f;
private static float[] trace_start = { 0, 0, 0 }, trace_end = { 0, 0, 0 };
private static float[] trace_mins = { 0, 0, 0 }, trace_maxs = { 0, 0, 0 };
private static float[] trace_extents = { 0, 0, 0 };
private static Trace trace_trace = new Trace();
private static int trace_contents;
private static boolean trace_ispoint; // optimized case
/*
* ================ CM_ClipBoxToBrush ================
*/
public static void CM_ClipBoxToBrush(float[] mins, float[] maxs,
float[] p1, float[] p2, Trace trace, cbrush_t brush) {
int i, j;
Plane plane, clipplane;
float dist;
float enterfrac, leavefrac;
float[] ofs = { 0, 0, 0 };
float d1, d2;
boolean getout, startout;
float f;
cbrushside_t side, leadside;
enterfrac = -1;
leavefrac = 1;
clipplane = null;
if (brush.numsides == 0)
return;
Globals.c_brush_traces++;
getout = false;
startout = false;
leadside = null;
for (i = 0; i < brush.numsides; i++) {
side = map_brushsides[brush.firstbrushside + i];
plane = side.plane;
// FIXME: special case for axial
if (!trace_ispoint) { // general box case
// push the plane out apropriately for mins/maxs
// FIXME: use signbits into 8 way lookup for each mins/maxs
for (j = 0; j < 3; j++) {
if (plane.normal[j] < 0)
ofs[j] = maxs[j];
else
ofs[j] = mins[j];
}
dist = Math3D.DotProduct(ofs, plane.normal);
dist = plane.dist - dist;
} else { // special point case
dist = plane.dist;
}
d1 = Math3D.DotProduct(p1, plane.normal) - dist;
d2 = Math3D.DotProduct(p2, plane.normal) - dist;
if (d2 > 0)
getout = true; // endpoint is not in solid
if (d1 > 0)
startout = true;
// if completely in front of face, no intersection
if (d1 > 0 && d2 >= d1)
return;
if (d1 <= 0 && d2 <= 0)
continue;
// crosses face
if (d1 > d2) { // enter
f = (d1 - DIST_EPSILON) / (d1 - d2);
if (f > enterfrac) {
enterfrac = f;
clipplane = plane;
leadside = side;
}
} else { // leave
f = (d1 + DIST_EPSILON) / (d1 - d2);
if (f < leavefrac)
leavefrac = f;
}
}
if (!startout) { // original point was inside brush
trace.startsolid = true;
if (!getout)
trace.allsolid = true;
return;
}
if (enterfrac < leavefrac) {
if (enterfrac > -1 && enterfrac < trace.fraction) {
if (enterfrac < 0)
enterfrac = 0;
trace.fraction = enterfrac;
// copy
trace.plane.set(clipplane);
trace.surface = leadside.surface.c;
trace.contents = brush.contents;
}
}
}
/*
* ================ CM_TestBoxInBrush ================
*/
public static void CM_TestBoxInBrush(float[] mins, float[] maxs,
float[] p1, Trace trace, cbrush_t brush) {
int i, j;
Plane plane;
float dist;
float[] ofs = { 0, 0, 0 };
float d1;
cbrushside_t side;
if (brush.numsides == 0)
return;
for (i = 0; i < brush.numsides; i++) {
side = map_brushsides[brush.firstbrushside + i];
plane = side.plane;
// FIXME: special case for axial
// general box case
// push the plane out apropriately for mins/maxs
// FIXME: use signbits into 8 way lookup for each mins/maxs
for (j = 0; j < 3; j++) {
if (plane.normal[j] < 0)
ofs[j] = maxs[j];
else
ofs[j] = mins[j];
}
dist = Math3D.DotProduct(ofs, plane.normal);
dist = plane.dist - dist;
d1 = Math3D.DotProduct(p1, plane.normal) - dist;
// if completely in front of face, no intersection
if (d1 > 0)
return;
}
// inside this brush
trace.startsolid = trace.allsolid = true;
trace.fraction = 0;
trace.contents = brush.contents;
}
/*
* ================ CM_TraceToLeaf ================
*/
public static void CM_TraceToLeaf(int leafnum) {
int k;
int brushnum;
cleaf_t leaf;
cbrush_t b;
leaf = map_leafs[leafnum];
if (0 == (leaf.contents & trace_contents))
return;
// trace line against all brushes in the leaf
for (k = 0; k < leaf.numleafbrushes; k++) {
brushnum = map_leafbrushes[leaf.firstleafbrush + k];
b = map_brushes[brushnum];
if (b.checkcount == checkcount)
continue; // already checked this brush in another leaf
b.checkcount = checkcount;
if (0 == (b.contents & trace_contents))
continue;
CM_ClipBoxToBrush(trace_mins, trace_maxs, trace_start, trace_end,
trace_trace, b);
if (0 == trace_trace.fraction)
return;
}
}
/*
* ================ CM_TestInLeaf ================
*/
public static void CM_TestInLeaf(int leafnum) {
int k;
int brushnum;
cleaf_t leaf;
cbrush_t b;
leaf = map_leafs[leafnum];
if (0 == (leaf.contents & trace_contents))
return;
// trace line against all brushes in the leaf
for (k = 0; k < leaf.numleafbrushes; k++) {
brushnum = map_leafbrushes[leaf.firstleafbrush + k];
b = map_brushes[brushnum];
if (b.checkcount == checkcount)
continue; // already checked this brush in another leaf
b.checkcount = checkcount;
if (0 == (b.contents & trace_contents))
continue;
CM_TestBoxInBrush(trace_mins, trace_maxs, trace_start, trace_trace,
b);
if (0 == trace_trace.fraction)
return;
}
}
/*
* ================== CM_RecursiveHullCheck ==================
*/
public static void CM_RecursiveHullCheck(int num, float p1f, float p2f,
float[] p1, float[] p2) {
cnode_t node;
Plane plane;
float t1, t2, offset;
float frac, frac2;
float idist;
int i;
int side;
float midf;
if (trace_trace.fraction <= p1f)
return; // already hit something nearer
// if < 0, we are in a leaf node
if (num < 0) {
CM_TraceToLeaf(-1 - num);
return;
}
//
// find the point distances to the seperating plane
// and the offset for the size of the box
//
node = map_nodes[num];
plane = node.plane;
if (plane.type < 3) {
t1 = p1[plane.type] - plane.dist;
t2 = p2[plane.type] - plane.dist;
offset = trace_extents[plane.type];
} else {
t1 = Math3D.DotProduct(plane.normal, p1) - plane.dist;
t2 = Math3D.DotProduct(plane.normal, p2) - plane.dist;
if (trace_ispoint)
offset = 0;
else
offset = Math.abs(trace_extents[0] * plane.normal[0])
+ Math.abs(trace_extents[1] * plane.normal[1])
+ Math.abs(trace_extents[2] * plane.normal[2]);
}
// see which sides we need to consider
if (t1 >= offset && t2 >= offset) {
CM_RecursiveHullCheck(node.children[0], p1f, p2f, p1, p2);
return;
}
if (t1 < -offset && t2 < -offset) {
CM_RecursiveHullCheck(node.children[1], p1f, p2f, p1, p2);
return;
}
// put the crosspoint DIST_EPSILON pixels on the near side
if (t1 < t2) {
idist = 1.0f / (t1 - t2);
side = 1;
frac2 = (t1 + offset + DIST_EPSILON) * idist;
frac = (t1 - offset + DIST_EPSILON) * idist;
} else if (t1 > t2) {
idist = 1.0f / (t1 - t2);
side = 0;
frac2 = (t1 - offset - DIST_EPSILON) * idist;
frac = (t1 + offset + DIST_EPSILON) * idist;
} else {
side = 0;
frac = 1;
frac2 = 0;
}
// move up to the node
if (frac < 0)
frac = 0;
if (frac > 1)
frac = 1;
midf = p1f + (p2f - p1f) * frac;
float[] mid = Vec3Cache.get();
for (i = 0; i < 3; i++)
mid[i] = p1[i] + frac * (p2[i] - p1[i]);
CM_RecursiveHullCheck(node.children[side], p1f, midf, p1, mid);
// go past the node
if (frac2 < 0)
frac2 = 0;
if (frac2 > 1)
frac2 = 1;
midf = p1f + (p2f - p1f) * frac2;
for (i = 0; i < 3; i++)
mid[i] = p1[i] + frac2 * (p2[i] - p1[i]);
CM_RecursiveHullCheck(node.children[side ^ 1], midf, p2f, mid, p2);
Vec3Cache.release();
}
//======================================================================
/*
* ================== CM_BoxTrace ==================
*/
public static Trace BoxTrace(float[] start, float[] end, float[] mins,
float[] maxs, int headnode, int brushmask) {
// for multi-check avoidance
checkcount++;
// for statistics, may be zeroed
Globals.c_traces++;
// fill in a default trace
//was: memset(& trace_trace, 0, sizeof(trace_trace));
trace_trace = new Trace();
trace_trace.fraction = 1;
trace_trace.surface = nullsurface.c;
if (numnodes == 0) {
// map not loaded
return trace_trace;
}
trace_contents = brushmask;
Math3D.VectorCopy(start, trace_start);
Math3D.VectorCopy(end, trace_end);
Math3D.VectorCopy(mins, trace_mins);
Math3D.VectorCopy(maxs, trace_maxs);
//
// check for position test special case
//
if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2]) {
int leafs[] = new int[1024];
int i, numleafs;
float[] c1 = { 0, 0, 0 }, c2 = { 0, 0, 0 };
int topnode = 0;
Math3D.VectorAdd(start, mins, c1);
Math3D.VectorAdd(start, maxs, c2);
for (i = 0; i < 3; i++) {
c1[i] -= 1;
c2[i] += 1;
}
int tn[] = { topnode };
numleafs = CM_BoxLeafnums_headnode(c1, c2, leafs, 1024, headnode,
tn);
topnode = tn[0];
for (i = 0; i < numleafs; i++) {
CM_TestInLeaf(leafs[i]);
if (trace_trace.allsolid)
break;
}
Math3D.VectorCopy(start, trace_trace.endpos);
return trace_trace;
}
//
// check for point special case
//
if (mins[0] == 0 && mins[1] == 0 && mins[2] == 0 && maxs[0] == 0
&& maxs[1] == 0 && maxs[2] == 0) {
trace_ispoint = true;
Math3D.VectorClear(trace_extents);
} else {
trace_ispoint = false;
trace_extents[0] = -mins[0] > maxs[0] ? -mins[0] : maxs[0];
trace_extents[1] = -mins[1] > maxs[1] ? -mins[1] : maxs[1];
trace_extents[2] = -mins[2] > maxs[2] ? -mins[2] : maxs[2];
}
//
// general sweeping through world
//
CM_RecursiveHullCheck(headnode, 0, 1, start, end);
if (trace_trace.fraction == 1) {
Math3D.VectorCopy(end, trace_trace.endpos);
} else {
for (int i = 0; i < 3; i++)
trace_trace.endpos[i] = start[i] + trace_trace.fraction
* (end[i] - start[i]);
}
return trace_trace;
}
/*
* ================== CM_TransformedBoxTrace
*
* Handles offseting and rotation of the end points for moving and rotating
* entities ==================
*/
public static Trace TransformedBoxTrace(float[] start, float[] end,
float[] mins, float[] maxs, int headnode, int brushmask,
float[] origin, float[] angles) {
Trace trace;
float[] start_l = { 0, 0, 0 }, end_l = { 0, 0, 0 };
float[] a = { 0, 0, 0 };
float[] forward = { 0, 0, 0 }, right = { 0, 0, 0 }, up = { 0, 0, 0 };
float[] temp = { 0, 0, 0 };
boolean rotated;
// subtract origin offset
Math3D.VectorSubtract(start, origin, start_l);
Math3D.VectorSubtract(end, origin, end_l);
// rotate start and end into the models frame of reference
if (headnode != box_headnode
&& (angles[0] != 0 || angles[1] != 0 || angles[2] != 0))
rotated = true;
else
rotated = false;
if (rotated) {
Math3D.AngleVectors(angles, forward, right, up);
Math3D.VectorCopy(start_l, temp);
start_l[0] = Math3D.DotProduct(temp, forward);
start_l[1] = -Math3D.DotProduct(temp, right);
start_l[2] = Math3D.DotProduct(temp, up);
Math3D.VectorCopy(end_l, temp);
end_l[0] = Math3D.DotProduct(temp, forward);
end_l[1] = -Math3D.DotProduct(temp, right);
end_l[2] = Math3D.DotProduct(temp, up);
}
// sweep the box through the model
trace = BoxTrace(start_l, end_l, mins, maxs, headnode, brushmask);
if (rotated && trace.fraction != 1.0) {
// FIXME: figure out how to do this with existing angles
Math3D.VectorNegate(angles, a);
Math3D.AngleVectors(a, forward, right, up);
Math3D.VectorCopy(trace.plane.normal, temp);
trace.plane.normal[0] = Math3D.DotProduct(temp, forward);
trace.plane.normal[1] = -Math3D.DotProduct(temp, right);
trace.plane.normal[2] = Math3D.DotProduct(temp, up);
}
trace.endpos[0] = start[0] + trace.fraction * (end[0] - start[0]);
trace.endpos[1] = start[1] + trace.fraction * (end[1] - start[1]);
trace.endpos[2] = start[2] + trace.fraction * (end[2] - start[2]);
return trace;
}
/*
* ===============================================================================
* PVS / PHS
* ===============================================================================
*/
/*
* =================== CM_DecompressVis ===================
*/
public static void CM_DecompressVis(byte in[], int offset, byte out[]) {
int c;
int row;
row = (numclusters + 7) >> 3;
int outp = 0;
int inp = offset;
if (in == null || numvisibility == 0) { // no vis info, so make all
// visible
while (row != 0) {
out[outp++] = (byte) 0xFF;
row--;
}
return;
}
do {
if (in[inp] != 0) {
out[outp++] = in[inp++];
continue;
}
c = in[inp + 1] & 0xFF;
inp += 2;
if (outp + c > row) {
c = row - (outp);
Com.DPrintf("warning: Vis decompression overrun\n");
}
while (c != 0) {
out[outp++] = 0;
c--;
}
} while (outp < row);
}
public static byte pvsrow[] = new byte[Constants.MAX_MAP_LEAFS / 8];
public static byte phsrow[] = new byte[Constants.MAX_MAP_LEAFS / 8];
public static byte[] CM_ClusterPVS(int cluster) {
if (cluster == -1)
Arrays.fill(pvsrow, 0, (numclusters + 7) >> 3, (byte) 0);
else
CM_DecompressVis(map_visibility,
map_vis.bitofs[cluster][Constants.DVIS_PVS], pvsrow);
return pvsrow;
}
public static byte[] CM_ClusterPHS(int cluster) {
if (cluster == -1)
Arrays.fill(phsrow, 0, (numclusters + 7) >> 3, (byte) 0);
else
CM_DecompressVis(map_visibility,
map_vis.bitofs[cluster][Constants.DVIS_PHS], phsrow);
return phsrow;
}
/*
* ===============================================================================
* AREAPORTALS
* ===============================================================================
*/
public static void FloodArea_r(carea_t area, int floodnum) {
//Com.Printf("FloodArea_r(" + floodnum + ")...\n");
int i;
QuakeFiles.dareaportal_t p;
if (area.floodvalid == floodvalid) {
if (area.floodnum == floodnum)
return;
Com.Error(Constants.ERR_DROP, "FloodArea_r: reflooded");
}
area.floodnum = floodnum;
area.floodvalid = floodvalid;
for (i = 0; i < area.numareaportals; i++) {
p = map_areaportals[area.firstareaportal + i];
if (portalopen[p.portalnum])
FloodArea_r(map_areas[p.otherarea], floodnum);
}
}
/*
* ==================== FloodAreaConnections ====================
*/
public static void FloodAreaConnections() {
Com.DPrintf("FloodAreaConnections...\n");
int i;
carea_t area;
int floodnum;
// all current floods are now invalid
floodvalid++;
floodnum = 0;
// area 0 is not used
for (i = 1; i < numareas; i++) {
area = map_areas[i];
if (area.floodvalid == floodvalid)
continue; // already flooded into
floodnum++;
FloodArea_r(area, floodnum);
}
}
/*
* ================= CM_SetAreaPortalState =================
*/
public static void CM_SetAreaPortalState(int portalnum, boolean open) {
if (portalnum > numareaportals)
Com.Error(Constants.ERR_DROP, "areaportal > numareaportals");
portalopen[portalnum] = open;
FloodAreaConnections();
}
/*
* ================= CM_AreasConnected =================
*/
public static boolean CM_AreasConnected(int area1, int area2) {
if (map_noareas.value != 0)
return true;
if (area1 > numareas || area2 > numareas)
Com.Error(Constants.ERR_DROP, "area > numareas");
if (map_areas[area1].floodnum == map_areas[area2].floodnum)
return true;
return false;
}
/*
* ================= CM_WriteAreaBits
*
* Writes a length byte followed by a bit vector of all the areas that area
* in the same flood as the area parameter
*
* This is used by the client refreshes to cull visibility =================
*/
public static int CM_WriteAreaBits(byte buffer[], int area) {
int i;
int floodnum;
int bytes;
bytes = (numareas + 7) >> 3;
if (map_noareas.value != 0) { // for debugging, send everything
Arrays.fill(buffer, 0, bytes, (byte) 255);
} else {
Arrays.fill(buffer, 0, bytes, (byte) 0);
floodnum = map_areas[area].floodnum;
for (i = 0; i < numareas; i++) {
if (map_areas[i].floodnum == floodnum || area == 0)
buffer[i >> 3] |= 1 << (i & 7);
}
}
return bytes;
}
/*
* =================== CM_WritePortalState
*
* Writes the portal state to a savegame file ===================
*/
public static void CM_WritePortalState(RandomAccessFile os) {
//was: fwrite(portalopen, sizeof(portalopen), 1, f);
try {
for (int n = 0; n < portalopen.length; n++)
if (portalopen[n])
os.writeInt(1);
else
os.writeInt(0);
} catch (Exception e) {
Com.Printf("ERROR:" + e);
e.printStackTrace();
}
}
/*
* =================== CM_ReadPortalState
*
* Reads the portal state from a savegame file and recalculates the area
* connections ===================
*/
public static void CM_ReadPortalState(RandomAccessFile f) {
//was: FS_Read(portalopen, sizeof(portalopen), f);
int len = portalopen.length * 4;
byte buf[] = new byte[len];
QuakeFileSystem.Read(buf, len, f);
ByteBuffer bb = ByteBuffer.wrap(buf);
IntBuffer ib = bb.asIntBuffer();
for (int n = 0; n < portalopen.length; n++)
portalopen[n] = ib.get() != 0;
FloodAreaConnections();
}
/*
* ============= CM_HeadnodeVisible
*
* Returns true if any leaf under headnode has a cluster that is potentially
* visible =============
*/
public static boolean CM_HeadnodeVisible(int nodenum, byte visbits[]) {
int leafnum;
int cluster;
cnode_t node;
if (nodenum < 0) {
leafnum = -1 - nodenum;
cluster = map_leafs[leafnum].cluster;
if (cluster == -1)
return false;
if (0 != (visbits[cluster >>> 3] & (1 << (cluster & 7))))
return true;
return false;
}
node = map_nodes[nodenum];
if (CM_HeadnodeVisible(node.children[0], visbits))
return true;
return CM_HeadnodeVisible(node.children[1], visbits);
}
}