339 lines
10 KiB
C++
339 lines
10 KiB
C++
/* Copyright (C) 2016 Eric Wasylishen
|
|
|
|
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
|
|
|
|
See file, 'COPYING', for details.
|
|
*/
|
|
|
|
#include <light/light.h>
|
|
#include <embree2/rtcore.h>
|
|
#include <embree2/rtcore_ray.h>
|
|
#include <unordered_set>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <cassert>
|
|
#include <limits>
|
|
|
|
/**
|
|
* i is between 0 and face->numedges - 1
|
|
*/
|
|
static int VertAtIndex(const bsp2_t *bsp, const bsp2_dface_t *face, const int i)
|
|
{
|
|
int edge = bsp->dsurfedges[face->firstedge + i];
|
|
int vert = (edge >= 0) ? bsp->dedges[edge].v[0] : bsp->dedges[-edge].v[1];
|
|
return vert;
|
|
}
|
|
|
|
static const miptex_t *
|
|
MiptexForFace(const bsp2_t *bsp, const bsp2_dface_t *face)
|
|
{
|
|
const texinfo_t *tex;
|
|
dmiptexlump_t *miplump = bsp->dtexdata.header;
|
|
const miptex_t *miptex;
|
|
tex = &bsp->texinfo[face->texinfo];
|
|
|
|
int dataofs = miplump->dataofs[tex->miptex];
|
|
if (dataofs == -1)
|
|
return NULL;
|
|
|
|
miptex = (miptex_t*)(bsp->dtexdata.base + dataofs);
|
|
return miptex;
|
|
}
|
|
|
|
class sceneinfo {
|
|
public:
|
|
unsigned geomID;
|
|
|
|
std::vector<const bsp2_dface_t *> triToFace;
|
|
};
|
|
|
|
sceneinfo
|
|
CreateGeometry(const bsp2_t *bsp, RTCScene scene, const std::vector<const bsp2_dface_t *> &faces)
|
|
{
|
|
// count triangles
|
|
int numtris = 0;
|
|
for (const bsp2_dface_t *face : faces) {
|
|
if (face->numedges < 3)
|
|
continue;
|
|
numtris += (face->numedges - 2);
|
|
}
|
|
|
|
unsigned geomID = rtcNewTriangleMesh(scene, RTC_GEOMETRY_STATIC, numtris, bsp->numvertexes);
|
|
|
|
struct Vertex { float point[4]; }; //4th element is padding
|
|
struct Triangle { int v0, v1, v2; };
|
|
|
|
// fill in vertices
|
|
Vertex* vertices = (Vertex*) rtcMapBuffer(scene, geomID, RTC_VERTEX_BUFFER);
|
|
for (int i=0; i<bsp->numvertexes; i++) {
|
|
const dvertex_t *dvertex = &bsp->dvertexes[i];
|
|
Vertex *vert = &vertices[i];
|
|
for (int j=0; j<3; j++) {
|
|
vert->point[j] = dvertex->point[j];
|
|
}
|
|
}
|
|
rtcUnmapBuffer(scene, geomID, RTC_VERTEX_BUFFER);
|
|
|
|
sceneinfo s;
|
|
s.geomID = geomID;
|
|
|
|
// fill in triangles
|
|
Triangle* triangles = (Triangle*) rtcMapBuffer(scene, geomID, RTC_INDEX_BUFFER);
|
|
int tri_index = 0;
|
|
for (const bsp2_dface_t *face : faces) {
|
|
if (face->numedges < 3)
|
|
continue;
|
|
|
|
for (int j = 2; j < face->numedges; j++) {
|
|
Triangle *tri = &triangles[tri_index];
|
|
tri->v0 = VertAtIndex(bsp, face, j-1);
|
|
tri->v1 = VertAtIndex(bsp, face, j);
|
|
tri->v2 = VertAtIndex(bsp, face, 0);
|
|
tri_index++;
|
|
|
|
s.triToFace.push_back(face);
|
|
}
|
|
}
|
|
rtcUnmapBuffer(scene, geomID, RTC_INDEX_BUFFER);
|
|
|
|
return s;
|
|
}
|
|
|
|
RTCScene scene;
|
|
/* global shadow casters */
|
|
sceneinfo skygeom, solidgeom;
|
|
|
|
/* self-shadow models for all bmodels which are NOT global shadow casters.
|
|
* these are only used for:
|
|
* - dirtmapping
|
|
* - making bmodels self-shadow if it's explicitly set with the _shadow flag
|
|
*/
|
|
std::unordered_map<const dmodel_t *, RTCScene> selfshadowSceneForDModel;
|
|
|
|
void ErrorCallback(const RTCError code, const char* str)
|
|
{
|
|
printf("RTC Error %d: %s\n", code, str);
|
|
}
|
|
|
|
void
|
|
MakeTnodes_embree(const bsp2_t *bsp)
|
|
{
|
|
assert(tracelist != NULL);
|
|
|
|
std::vector<const bsp2_dface_t *> skyfaces, solidfaces;
|
|
|
|
/* Check against the list of global shadow casters */
|
|
for (const modelinfo_t *const *model = tracelist; *model; model++) {
|
|
for (int i=0; i<(*model)->model->numfaces; i++) {
|
|
const bsp2_dface_t *face = &bsp->dfaces[(*model)->model->firstface + i];
|
|
const miptex_t *miptex = MiptexForFace(bsp, face);
|
|
|
|
if (miptex != NULL && !strncmp("sky", miptex->name, 3)) {
|
|
skyfaces.push_back(face);
|
|
} else {
|
|
solidfaces.push_back(face);
|
|
}
|
|
}
|
|
}
|
|
|
|
rtcInit(NULL);
|
|
rtcSetErrorFunction(ErrorCallback);
|
|
|
|
scene = rtcNewScene(RTC_SCENE_STATIC | RTC_SCENE_COHERENT | RTC_SCENE_HIGH_QUALITY | RTC_SCENE_ROBUST, RTC_INTERSECT1);
|
|
skygeom = CreateGeometry(bsp, scene, skyfaces);
|
|
solidgeom = CreateGeometry(bsp, scene, solidfaces);
|
|
|
|
rtcCommit (scene);
|
|
|
|
printf("MakeTnodes: created sky geometry with %d faces and solid with %d faces\n",
|
|
(int)skyfaces.size(),
|
|
(int)solidfaces.size());
|
|
|
|
/* Create self-shadow models */
|
|
|
|
for (int i=0; i<bsp->nummodels; i++) {
|
|
const dmodel_t *model = &bsp->dmodels[i];
|
|
|
|
// N.B. All faces are considered opaque for the selfshadow models
|
|
|
|
std::vector<const bsp2_dface_t *> faces;
|
|
for (int j=0; j<model->numfaces; j++) {
|
|
const bsp2_dface_t *face = &bsp->dfaces[model->firstface + j];
|
|
faces.push_back(face);
|
|
}
|
|
|
|
RTCScene selfshadowscene = rtcNewScene(RTC_SCENE_STATIC | RTC_SCENE_COHERENT | RTC_SCENE_HIGH_QUALITY | RTC_SCENE_ROBUST, RTC_INTERSECT1);
|
|
CreateGeometry(bsp, selfshadowscene, faces);
|
|
rtcCommit (selfshadowscene);
|
|
selfshadowSceneForDModel[model] = selfshadowscene;
|
|
}
|
|
}
|
|
|
|
static RTCRay SetupRay(const vec3_t start, const vec3_t dir, vec_t dist)
|
|
{
|
|
RTCRay ray;
|
|
VectorCopy(start, ray.org);
|
|
VectorCopy(dir, ray.dir); // can be un-normalized
|
|
ray.tnear = 0.f;
|
|
ray.tfar = dist;
|
|
ray.geomID = RTC_INVALID_GEOMETRY_ID;
|
|
ray.primID = RTC_INVALID_GEOMETRY_ID;
|
|
ray.instID = RTC_INVALID_GEOMETRY_ID;
|
|
ray.mask = 0xFFFFFFFF;
|
|
ray.time = 0.f;
|
|
return ray;
|
|
}
|
|
|
|
qboolean
|
|
TestLight_embree(const vec3_t start, const vec3_t dir, vec_t dist, const modelinfo_t *model)
|
|
{
|
|
RTCRay ray = SetupRay(start, dir, dist);
|
|
rtcOccluded(scene, ray);
|
|
|
|
if (ray.geomID != RTC_INVALID_GEOMETRY_ID)
|
|
return false; //hit
|
|
|
|
if (model->shadowself) {
|
|
// check the selfshadow model
|
|
RTCScene selfshadowscene = selfshadowSceneForDModel[model->model];
|
|
ray = SetupRay(start, dir, dist);
|
|
rtcOccluded(selfshadowscene, ray);
|
|
|
|
if (ray.geomID != RTC_INVALID_GEOMETRY_ID)
|
|
return false; //hit
|
|
}
|
|
|
|
// no obstruction
|
|
return true;
|
|
}
|
|
|
|
qboolean
|
|
TestSky_embree(const vec3_t start, const vec3_t dirn, const modelinfo_t *model)
|
|
{
|
|
// trace from the sample point towards the sun, and
|
|
// return true if we hit a sky poly.
|
|
|
|
RTCRay ray = SetupRay(start, dirn, std::numeric_limits<float>::infinity());
|
|
rtcIntersect(scene, ray);
|
|
|
|
qboolean hit_sky = (ray.geomID == skygeom.geomID);
|
|
|
|
if (hit_sky && model->shadowself) {
|
|
// sky is visible, check for an obstruction from the selfshadow
|
|
RTCScene selfshadowscene = selfshadowSceneForDModel[model->model];
|
|
ray = SetupRay(start, dirn, std::numeric_limits<float>::infinity());
|
|
rtcIntersect(selfshadowscene, ray);
|
|
|
|
hit_sky = (ray.geomID == RTC_INVALID_GEOMETRY_ID);
|
|
}
|
|
|
|
return hit_sky;
|
|
}
|
|
|
|
/*
|
|
* ============
|
|
* DirtTrace
|
|
*
|
|
* returns true if the trace from start to stop hits something solid,
|
|
* or if it started in the void.
|
|
* ============
|
|
*/
|
|
qboolean
|
|
DirtTrace_embree(const vec3_t start, const vec3_t dir, vec_t dist, vec_t *hitdist, vec_t *normal, const modelinfo_t *model)
|
|
{
|
|
RTCRay ray = SetupRay(start, dir, dist);
|
|
rtcIntersect(scene, ray);
|
|
|
|
if (ray.geomID != solidgeom.geomID) {
|
|
// don't re-check the world's self-shadow model because it's already part of 'scene'
|
|
if (model->model != tracelist[0]->model) {
|
|
RTCScene selfshadowscene = selfshadowSceneForDModel[model->model];
|
|
ray = SetupRay(start, dir, dist);
|
|
rtcIntersect(selfshadowscene, ray);
|
|
|
|
if (ray.geomID == RTC_INVALID_GEOMETRY_ID) {
|
|
return false; //no hit
|
|
}
|
|
} else {
|
|
// no self shadow model. no hit
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// check if the ray was coming from behind the hit surface (it started in the void)
|
|
{
|
|
VectorNormalize(ray.Ng);
|
|
vec_t rayHitPlaneAngle = DotProduct(ray.Ng, dir);
|
|
if (rayHitPlaneAngle > 0) { // pointing in the same direction
|
|
ray.tfar = 0;
|
|
}
|
|
}
|
|
|
|
if (hitdist)
|
|
*hitdist = ray.tfar;
|
|
if (normal)
|
|
{
|
|
VectorCopy(ray.Ng, normal);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
qboolean
|
|
FaceTrace_embree(const vec3_t start, const vec3_t dir, vec3_t hitpoint, const bsp2_dface_t **hitface)
|
|
{
|
|
RTCRay ray = SetupRay(start, dir, 512);
|
|
rtcIntersect(scene, ray);
|
|
|
|
if (ray.geomID != solidgeom.geomID) {
|
|
return false;
|
|
}
|
|
|
|
VectorMA(start, ray.tfar, dir, hitpoint);
|
|
*hitface = solidgeom.triToFace[ray.primID];
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* ============
|
|
* CalcPointsTrace
|
|
*
|
|
* returns true if the trace from start to stop hits something solid.
|
|
* ============
|
|
*/
|
|
qboolean
|
|
CalcPointsTrace_embree(const vec3_t start, const vec3_t dir, vec_t dist, vec_t *hitdist, vec_t *normal, const modelinfo_t *model)
|
|
{
|
|
RTCScene selfshadowscene = selfshadowSceneForDModel[model->model];
|
|
RTCRay ray = SetupRay(start, dir, dist);
|
|
rtcIntersect(selfshadowscene, ray);
|
|
|
|
// if there is no hit, but we were tracning on a submodel, also test against the world.
|
|
if (ray.geomID == RTC_INVALID_GEOMETRY_ID
|
|
&& model->model != tracelist[0]->model) {
|
|
ray = SetupRay(start, dir, dist);
|
|
rtcIntersect(scene, ray);
|
|
}
|
|
|
|
if (hitdist)
|
|
*hitdist = ray.tfar;
|
|
if (normal) {
|
|
VectorCopy(ray.Ng, normal);
|
|
VectorNormalize(normal);
|
|
}
|
|
|
|
return ray.geomID != RTC_INVALID_GEOMETRY_ID;
|
|
}
|