mirror of https://github.com/aap/librw.git
started implementing tristrip; implemented camera frustum
This commit is contained in:
parent
c2780d56e9
commit
4941711964
36
Makefile
36
Makefile
|
@ -1,33 +1,11 @@
|
|||
# null, opengl
|
||||
BUILD=null
|
||||
|
||||
# e.g. null -> -DRW_NULL
|
||||
BUILDDEF:=$(shell echo $(BUILD) | tr a-z A-Z | sed 's/^/-DRW_/')
|
||||
BUILDDIR=build-$(BUILD)
|
||||
SRCDIR=src
|
||||
#SRC := $(patsubst %.cpp,$(SRCDIR)/%.cpp, $(wildcard *.cpp))
|
||||
SRC := $(wildcard $(SRCDIR)/*.cpp $(SRCDIR)/*/*.cpp)
|
||||
OBJ := $(patsubst $(SRCDIR)/%.cpp,$(BUILDDIR)/%.o,$(SRC))
|
||||
DEP := $(patsubst $(SRCDIR)/%.cpp,$(BUILDDIR)/%.d,$(SRC))
|
||||
INC := -I/usr/local/include
|
||||
CFLAGS=-Wall -Wextra -g $(BUILDDEF) -fno-diagnostics-show-caret \
|
||||
# null, gl3, ps2, d3d9
|
||||
BUILD := null
|
||||
TARGET := librw-$(BUILD).a
|
||||
CFLAGS := -Wall -Wextra -g -fno-diagnostics-show-caret \
|
||||
-Wno-parentheses -Wno-invalid-offsetof \
|
||||
-Wno-unused-parameter -Wno-sign-compare
|
||||
LIB=librw-$(BUILD).a
|
||||
|
||||
$(LIB): $(OBJ)
|
||||
include Make.common
|
||||
|
||||
$(TARGET): $(OBJ)
|
||||
ar scr $@ $(OBJ)
|
||||
|
||||
$(BUILDDIR)/%.o: $(SRCDIR)/%.cpp
|
||||
@mkdir -p $(@D)
|
||||
$(CXX) $(CFLAGS) $(INC) -c $< -o $@
|
||||
|
||||
$(BUILDDIR)/%.d: $(SRCDIR)/%.cpp
|
||||
@mkdir -p $(@D)
|
||||
$(CXX) -MM -MT '$(patsubst $(SRCDIR)/%.cpp,$(BUILDDIR)/%.o,$<)' $(CFLAGS) $(INC) $< > $@
|
||||
|
||||
clean:
|
||||
echo $(SRC)
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
-include $(DEP)
|
||||
|
|
93
src/base.cpp
93
src/base.cpp
|
@ -184,9 +184,9 @@ V3d
|
|||
Matrix::transPoint(const V3d &p)
|
||||
{
|
||||
V3d res = this->pos;
|
||||
res = add(res, scale(this->right, p.x));
|
||||
res = add(res, scale(this->up, p.y));
|
||||
res = add(res, scale(this->at, p.z));
|
||||
res = add(res, rw::scale(this->right, p.x));
|
||||
res = add(res, rw::scale(this->up, p.y));
|
||||
res = add(res, rw::scale(this->at, p.z));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -194,9 +194,9 @@ V3d
|
|||
Matrix::transVec(const V3d &v)
|
||||
{
|
||||
V3d res;
|
||||
res = scale(this->right, v.x);
|
||||
res = add(res, scale(this->up, v.y));
|
||||
res = add(res, scale(this->at, v.z));
|
||||
res = rw::scale(this->right, v.x);
|
||||
res = add(res, rw::scale(this->up, v.y));
|
||||
res = add(res, rw::scale(this->at, v.z));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -251,6 +251,87 @@ Matrix::transpose(Matrix *m1, Matrix *m2)
|
|||
matrixTranspose((float32*)m1, (float32*)m2);
|
||||
}
|
||||
|
||||
void
|
||||
Matrix::rotate(V3d *axis, float32 angle, CombineOp op)
|
||||
{
|
||||
Matrix tmp;
|
||||
V3d v = normalize(*axis);
|
||||
angle = angle*M_PI/180.0f;
|
||||
float32 s = sin(angle);
|
||||
float32 c = cos(angle);
|
||||
float32 t = 1.0f - cos(angle);
|
||||
|
||||
Matrix rot = identMat;
|
||||
rot.right.x = c + v.x*v.x*t;
|
||||
rot.right.y = v.x*v.y*t + v.z*s;
|
||||
rot.right.z = v.z*v.x*t - v.y*s;
|
||||
rot.up.x = v.x*v.y*t - v.z*s;
|
||||
rot.up.y = c + v.y*v.y*t;
|
||||
rot.up.z = v.y*v.z*t + v.x*s;
|
||||
rot.at.x = v.z*v.x*t + v.y*s;
|
||||
rot.at.y = v.y*v.z*t - v.x*s;
|
||||
rot.at.z = c + v.z*v.z*t;
|
||||
|
||||
switch(op){
|
||||
case COMBINEREPLACE:
|
||||
*this = rot;
|
||||
break;
|
||||
case COMBINEPRECONCAT:
|
||||
mult(&tmp, this, &rot);
|
||||
*this = tmp;
|
||||
break;
|
||||
case COMBINEPOSTCONCAT:
|
||||
mult(&tmp, &rot, this);
|
||||
*this = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Matrix::translate(V3d *translation, CombineOp op)
|
||||
{
|
||||
Matrix tmp;
|
||||
Matrix trans = identMat;
|
||||
trans.pos = *translation;
|
||||
switch(op){
|
||||
case COMBINEREPLACE:
|
||||
*this = trans;
|
||||
break;
|
||||
case COMBINEPRECONCAT:
|
||||
mult(&tmp, this, &trans);
|
||||
*this = tmp;
|
||||
break;
|
||||
case COMBINEPOSTCONCAT:
|
||||
mult(&tmp, &trans, this);
|
||||
*this = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Matrix::scale(V3d *scale, CombineOp op)
|
||||
{
|
||||
Matrix tmp;
|
||||
Matrix scl = identMat;
|
||||
scl.right.x = scale->x;
|
||||
scl.right.y = scale->y;
|
||||
scl.right.z = scale->z;
|
||||
switch(op){
|
||||
case COMBINEREPLACE:
|
||||
*this = scl;
|
||||
break;
|
||||
case COMBINEPRECONCAT:
|
||||
mult(&tmp, this, &scl);
|
||||
*this = tmp;
|
||||
break;
|
||||
case COMBINEPOSTCONCAT:
|
||||
mult(&tmp, &scl, this);
|
||||
*this = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Matrix3
|
||||
Matrix3::makeRotation(const Quat &q)
|
||||
|
|
136
src/camera.cpp
136
src/camera.cpp
|
@ -26,6 +26,136 @@ defaultEndUpdateCB(Camera *cam)
|
|||
engine->endUpdate(cam);
|
||||
}
|
||||
|
||||
static void
|
||||
buildPlanes(Camera *cam)
|
||||
{
|
||||
V3d *c = cam->frustumCorners;
|
||||
FrustumPlane *p = cam->frustumPlanes;
|
||||
V3d v51 = sub(c[1], c[5]);
|
||||
V3d v73 = sub(c[3], c[7]);
|
||||
|
||||
/* Far plane */
|
||||
p[0].plane.normal = cam->getFrame()->getLTM()->at;
|
||||
p[0].plane.distance = dot(p[0].plane.normal, c[4]);
|
||||
p[0].closestX = p[0].plane.normal.x < 0.0f ? 0 : 1;
|
||||
p[0].closestY = p[0].plane.normal.y < 0.0f ? 0 : 1;
|
||||
p[0].closestZ = p[0].plane.normal.z < 0.0f ? 0 : 1;
|
||||
|
||||
/* Near plane */
|
||||
p[1].plane.normal = neg(p[0].plane.normal);
|
||||
p[1].plane.distance = dot(p[1].plane.normal, c[0]);
|
||||
p[1].closestX = p[1].plane.normal.x < 0.0f ? 0 : 1;
|
||||
p[1].closestY = p[1].plane.normal.y < 0.0f ? 0 : 1;
|
||||
p[1].closestZ = p[1].plane.normal.z < 0.0f ? 0 : 1;
|
||||
|
||||
/* Right plane */
|
||||
p[2].plane.normal = normalize(cross(v51,
|
||||
sub(c[6], c[5])));
|
||||
p[2].plane.distance = dot(p[2].plane.normal, c[1]);
|
||||
p[2].closestX = p[2].plane.normal.x < 0.0f ? 0 : 1;
|
||||
p[2].closestY = p[2].plane.normal.y < 0.0f ? 0 : 1;
|
||||
p[2].closestZ = p[2].plane.normal.z < 0.0f ? 0 : 1;
|
||||
|
||||
/* Top plane */
|
||||
p[3].plane.normal = normalize(cross(sub(c[4], c[5]),
|
||||
v51));
|
||||
p[3].plane.distance = dot(p[3].plane.normal, c[1]);
|
||||
p[3].closestX = p[3].plane.normal.x < 0.0f ? 0 : 1;
|
||||
p[3].closestY = p[3].plane.normal.y < 0.0f ? 0 : 1;
|
||||
p[3].closestZ = p[3].plane.normal.z < 0.0f ? 0 : 1;
|
||||
|
||||
/* Left plane */
|
||||
p[4].plane.normal = normalize(cross(v73,
|
||||
sub(c[4], c[7])));
|
||||
p[4].plane.distance = dot(p[4].plane.normal, c[3]);
|
||||
p[4].closestX = p[4].plane.normal.x < 0.0f ? 0 : 1;
|
||||
p[4].closestY = p[4].plane.normal.y < 0.0f ? 0 : 1;
|
||||
p[4].closestZ = p[4].plane.normal.z < 0.0f ? 0 : 1;
|
||||
|
||||
/* Bottom plane */
|
||||
p[5].plane.normal = normalize(cross(sub(c[6], c[7]),
|
||||
v73));
|
||||
p[5].plane.distance = dot(p[5].plane.normal, c[3]);
|
||||
p[5].closestX = p[5].plane.normal.x < 0.0f ? 0 : 1;
|
||||
p[5].closestY = p[5].plane.normal.y < 0.0f ? 0 : 1;
|
||||
p[5].closestZ = p[5].plane.normal.z < 0.0f ? 0 : 1;
|
||||
}
|
||||
|
||||
static void
|
||||
buildClipPersp(Camera *cam)
|
||||
{
|
||||
Matrix *ltm = cam->getFrame()->getLTM();
|
||||
|
||||
/* First we calculate the 4 points on the view window. */
|
||||
V3d up = scale(ltm->up, cam->viewWindow.y);
|
||||
V3d left = scale(ltm->right, cam->viewWindow.x);
|
||||
V3d *c = cam->frustumCorners;
|
||||
c[0] = add(add(ltm->at, up), left); // top left
|
||||
c[1] = sub(add(ltm->at, up), left); // top right
|
||||
c[2] = sub(sub(ltm->at, up), left); // bottom right
|
||||
c[3] = add(sub(ltm->at, up), left); // bottom left
|
||||
|
||||
/* Now Calculate near and far corners. */
|
||||
V3d off = sub(scale(ltm->up, cam->viewOffset.y),
|
||||
scale(ltm->right, cam->viewOffset.x));
|
||||
for(int32 i = 0; i < 4; i++){
|
||||
V3d corner = sub(cam->frustumCorners[i], off);
|
||||
V3d pos = add(ltm->pos, off);
|
||||
c[i] = add(scale(corner, cam->nearPlane), pos);
|
||||
c[i+4] = add(scale(corner, cam->farPlane), pos);
|
||||
}
|
||||
|
||||
buildPlanes(cam);
|
||||
}
|
||||
|
||||
static void
|
||||
buildClipParallel(Camera *cam)
|
||||
{
|
||||
Matrix *ltm = cam->getFrame()->getLTM();
|
||||
float32 nearoffx = -(1.0f - cam->nearPlane)*cam->viewOffset.x;
|
||||
float32 nearoffy = (1.0f - cam->nearPlane)*cam->viewOffset.y;
|
||||
float32 faroffx = -(1.0f - cam->farPlane)*cam->viewOffset.x;
|
||||
float32 faroffy = (1.0f - cam->farPlane)*cam->viewOffset.y;
|
||||
|
||||
V3d *c = cam->frustumCorners;
|
||||
c[0].x = nearoffx + cam->viewWindow.x;
|
||||
c[0].y = nearoffy + cam->viewWindow.y;
|
||||
c[0].z = cam->nearPlane;
|
||||
|
||||
c[1].x = nearoffx - cam->viewWindow.x;
|
||||
c[1].y = nearoffy + cam->viewWindow.y;
|
||||
c[1].z = cam->nearPlane;
|
||||
|
||||
c[2].x = nearoffx - cam->viewWindow.x;
|
||||
c[2].y = nearoffy - cam->viewWindow.y;
|
||||
c[2].z = cam->nearPlane;
|
||||
|
||||
c[3].x = nearoffx + cam->viewWindow.x;
|
||||
c[3].y = nearoffy - cam->viewWindow.y;
|
||||
c[3].z = cam->nearPlane;
|
||||
|
||||
c[4].x = faroffx + cam->viewWindow.x;
|
||||
c[4].y = faroffy + cam->viewWindow.y;
|
||||
c[4].z = cam->farPlane;
|
||||
|
||||
c[5].x = faroffx - cam->viewWindow.x;
|
||||
c[5].y = faroffy + cam->viewWindow.y;
|
||||
c[5].z = cam->farPlane;
|
||||
|
||||
c[6].x = faroffx - cam->viewWindow.x;
|
||||
c[6].y = faroffy - cam->viewWindow.y;
|
||||
c[6].z = cam->farPlane;
|
||||
|
||||
c[7].x = faroffx + cam->viewWindow.x;
|
||||
c[7].y = faroffy - cam->viewWindow.y;
|
||||
c[7].z = cam->farPlane;
|
||||
|
||||
for(int32 i = 0; i < 8; i++)
|
||||
c[i] = ltm->transPoint(c[i]);
|
||||
|
||||
buildPlanes(cam);
|
||||
}
|
||||
|
||||
static void
|
||||
cameraSync(ObjectWithFrame *obj)
|
||||
{
|
||||
|
@ -85,6 +215,8 @@ cameraSync(ObjectWithFrame *obj)
|
|||
proj.at.y = -proj.pos.y + 0.5f;
|
||||
proj.at.z = 1.0f;
|
||||
proj.atw = 1.0f;
|
||||
Matrix::mult(&cam->viewMatrix, &proj, &inv);
|
||||
buildClipPersp(cam);
|
||||
}else{
|
||||
proj.at.x = cam->viewOffset.x*xscl;
|
||||
proj.at.y = -cam->viewOffset.y*yscl;
|
||||
|
@ -95,8 +227,10 @@ cameraSync(ObjectWithFrame *obj)
|
|||
proj.pos.y = -proj.at.y + 0.5f;
|
||||
proj.pos.z = 0.0f;
|
||||
proj.posw = 1.0f;
|
||||
}
|
||||
Matrix::mult(&cam->viewMatrix, &proj, &inv);
|
||||
buildClipParallel(cam);
|
||||
}
|
||||
cam->frustumBoundBox.calculate(cam->frustumCorners, 8);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -316,8 +316,10 @@ Clump::render(void)
|
|||
//
|
||||
|
||||
static void
|
||||
atomicSync(ObjectWithFrame*)
|
||||
atomicSync(ObjectWithFrame *obj)
|
||||
{
|
||||
// TODO: interpolate
|
||||
obj->object.privateFlags |= Atomic::WORLDBOUNDDIRTY;
|
||||
}
|
||||
|
||||
|
||||
|
@ -339,13 +341,17 @@ Atomic::create(void)
|
|||
atomic->object.object.init(Atomic::ID, 0);
|
||||
atomic->object.syncCB = atomicSync;
|
||||
atomic->geometry = nil;
|
||||
atomic->boundingSphere.center.set(0.0f, 0.0f, 0.0f);
|
||||
atomic->boundingSphere.radius = 0.0f;
|
||||
atomic->worldBoundingSphere.center.set(0.0f, 0.0f, 0.0f);
|
||||
atomic->worldBoundingSphere.radius = 0.0f;
|
||||
atomic->setFrame(nil);
|
||||
atomic->object.object.privateFlags |= WORLDBOUNDDIRTY;
|
||||
atomic->clump = nil;
|
||||
atomic->pipeline = nil;
|
||||
atomic->renderCB = Atomic::defaultRenderCB;
|
||||
atomic->object.object.flags = Atomic::COLLISIONTEST | Atomic::RENDER;
|
||||
// TODO: interpolator
|
||||
|
||||
// World extension
|
||||
atomic->world = nil;
|
||||
|
@ -363,9 +369,9 @@ Atomic::clone()
|
|||
if(atomic == nil)
|
||||
return nil;
|
||||
atomic->object.object.copy(&this->object.object);
|
||||
atomic->object.object.privateFlags |= 1;
|
||||
atomic->object.object.privateFlags |= WORLDBOUNDDIRTY;
|
||||
if(this->geometry)
|
||||
atomic->setGeometry(this->geometry);
|
||||
atomic->setGeometry(this->geometry, 0);
|
||||
atomic->pipeline = this->pipeline;
|
||||
s_plglist.copy(atomic, this);
|
||||
return atomic;
|
||||
|
@ -393,28 +399,34 @@ Atomic::removeFromClump(void)
|
|||
}
|
||||
|
||||
void
|
||||
Atomic::setGeometry(Geometry *geo)
|
||||
Atomic::setGeometry(Geometry *geo, uint32 flags)
|
||||
{
|
||||
if(this->geometry)
|
||||
this->geometry->destroy();
|
||||
if(geo)
|
||||
geo->refCount++;
|
||||
this->geometry = geo;
|
||||
// TODO: bounding stuff
|
||||
if(flags & SAMEBOUNDINGSPHERE)
|
||||
return;
|
||||
if(geo){
|
||||
this->boundingSphere = geo->morphTargets[0].boundingSphere;
|
||||
if(this->getFrame()) // TODO: && getWorld???
|
||||
this->getFrame()->updateObjects();
|
||||
}
|
||||
}
|
||||
|
||||
Sphere*
|
||||
Atomic::getWorldBoundingSphere(void)
|
||||
{
|
||||
Sphere *s = &this->worldBoundingSphere;
|
||||
// TODO: if we ever support morphing, check interpolation
|
||||
if(!this->getFrame()->dirty() &&
|
||||
(this->object.object.privateFlags & WORLDBOUNDDIRTY) == 0)
|
||||
return s;
|
||||
Matrix *ltm = this->getFrame()->getLTM();
|
||||
// TODO: support scaling
|
||||
// TODO: if we ever support morphing, fix this:
|
||||
s->center = ltm->transPoint(this->geometry->morphTargets[0].boundingSphere.center);
|
||||
s->radius = this->geometry->morphTargets[0].boundingSphere.radius;
|
||||
s->center = ltm->transPoint(this->boundingSphere.center);
|
||||
s->radius = this->boundingSphere.radius;
|
||||
this->object.object.privateFlags &= ~WORLDBOUNDDIRTY;
|
||||
return s;
|
||||
}
|
||||
|
@ -445,10 +457,10 @@ Atomic::streamReadClump(Stream *stream,
|
|||
g = Geometry::streamRead(stream);
|
||||
if(g == nil)
|
||||
goto fail;
|
||||
atomic->setGeometry(g);
|
||||
atomic->setGeometry(g, 0);
|
||||
g->destroy();
|
||||
}else
|
||||
atomic->setGeometry(geometryList[buf[1]]);
|
||||
atomic->setGeometry(geometryList[buf[1]], 0);
|
||||
atomic->object.object.flags = buf[2];
|
||||
|
||||
atomicRights[0] = 0;
|
||||
|
|
|
@ -248,6 +248,27 @@ Frame::syncDirty(void)
|
|||
Frame::dirtyList.init();
|
||||
}
|
||||
|
||||
void
|
||||
Frame::rotate(V3d *axis, float32 angle, CombineOp op)
|
||||
{
|
||||
this->matrix.rotate(axis, angle, op);
|
||||
updateObjects();
|
||||
}
|
||||
|
||||
void
|
||||
Frame::translate(V3d *trans, CombineOp op)
|
||||
{
|
||||
this->matrix.translate(trans, op);
|
||||
updateObjects();
|
||||
}
|
||||
|
||||
void
|
||||
Frame::scale(V3d *scl, CombineOp op)
|
||||
{
|
||||
this->matrix.scale(scl, op);
|
||||
updateObjects();
|
||||
}
|
||||
|
||||
void
|
||||
Frame::updateObjects(void)
|
||||
{
|
||||
|
|
|
@ -431,9 +431,26 @@ Geometry::generateTriangles(int8 *adc)
|
|||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dumpMesh(Mesh *m)
|
||||
{
|
||||
for(int32 i = 0; i < m->numIndices-2; i++)
|
||||
// if(i % 2)
|
||||
// printf("%3d %3d %3d\n",
|
||||
// m->indices[i+1],
|
||||
// m->indices[i],
|
||||
// m->indices[i+2]);
|
||||
// else
|
||||
printf("%d %d %d\n",
|
||||
m->indices[i],
|
||||
m->indices[i+1],
|
||||
m->indices[i+2]);
|
||||
}
|
||||
|
||||
void
|
||||
Geometry::buildMeshes(void)
|
||||
{
|
||||
//dumpMesh(this->meshHeader->mesh);
|
||||
delete this->meshHeader;
|
||||
|
||||
Triangle *tri;
|
||||
|
@ -467,9 +484,8 @@ Geometry::buildMeshes(void)
|
|||
h->mesh[tri->matId].numIndices = idx;
|
||||
tri++;
|
||||
}
|
||||
}else{
|
||||
assert(0 && "can't tristrip\n");
|
||||
}
|
||||
}else
|
||||
this->buildTristrips();
|
||||
}
|
||||
|
||||
/* The idea is that even in meshes where winding is not preserved
|
||||
|
@ -508,9 +524,11 @@ Geometry::correctTristripWinding(void)
|
|||
/* Entering strip now,
|
||||
* make sure winding is correct */
|
||||
inStrip = 1;
|
||||
if(newmesh->numIndices % 2)
|
||||
newmesh->indices[newmesh->numIndices++] =
|
||||
if(newmesh->numIndices % 2){
|
||||
newmesh->indices[newmesh->numIndices] =
|
||||
newmesh->indices[newmesh->numIndices-1];
|
||||
newmesh->numIndices++;
|
||||
}
|
||||
}
|
||||
newmesh->indices[newmesh->numIndices++] = mesh->indices[j];
|
||||
}
|
||||
|
|
|
@ -200,8 +200,8 @@ matfxRenderCB(Atomic *atomic, InstanceDataHeader *header)
|
|||
InstanceData *inst = header->inst;
|
||||
int32 n = header->numMeshes;
|
||||
|
||||
rw::setRenderState(ALPHATESTFUNC, 1);
|
||||
rw::setRenderState(ALPHATESTREF, 50);
|
||||
// rw::setRenderState(ALPHATESTFUNC, 1);
|
||||
// rw::setRenderState(ALPHATESTREF, 50);
|
||||
|
||||
int32 fx;
|
||||
while(n--){
|
||||
|
@ -460,8 +460,8 @@ skinRenderCB(Atomic *atomic, InstanceDataHeader *header)
|
|||
InstanceData *inst = header->inst;
|
||||
int32 n = header->numMeshes;
|
||||
|
||||
rw::setRenderState(ALPHATESTFUNC, 1);
|
||||
rw::setRenderState(ALPHATESTREF, 50);
|
||||
// rw::setRenderState(ALPHATESTFUNC, 1);
|
||||
// rw::setRenderState(ALPHATESTREF, 50);
|
||||
|
||||
skinShader->use();
|
||||
|
||||
|
|
|
@ -84,8 +84,8 @@ defaultRenderCB(Atomic *atomic, InstanceDataHeader *header)
|
|||
InstanceData *inst = header->inst;
|
||||
int32 n = header->numMeshes;
|
||||
|
||||
rw::setRenderState(ALPHATESTFUNC, 1);
|
||||
rw::setRenderState(ALPHATESTREF, 50);
|
||||
// rw::setRenderState(ALPHATESTFUNC, 1);
|
||||
// rw::setRenderState(ALPHATESTREF, 50);
|
||||
|
||||
simpleShader->use();
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "rwbase.h"
|
||||
#include "rwerror.h"
|
||||
#include "rwplg.h"
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
#include "rwengine.h"
|
||||
|
||||
#define PLUGIN_ID 0
|
||||
|
||||
namespace rw {
|
||||
|
||||
void
|
||||
BBox::calculate(V3d *points, int32 n)
|
||||
{
|
||||
this->inf = points[0];
|
||||
this->sup = points[0];
|
||||
while(--n){
|
||||
points++;
|
||||
if(points->x < this->inf.x)
|
||||
this->inf.x = points->x;
|
||||
if(points->y < this->inf.y)
|
||||
this->inf.x = points->y;
|
||||
if(points->z < this->inf.z)
|
||||
this->inf.x = points->z;
|
||||
if(points->x > this->sup.x)
|
||||
this->sup.x = points->x;
|
||||
if(points->y > this->sup.y)
|
||||
this->sup.x = points->y;
|
||||
if(points->z > this->sup.z)
|
||||
this->sup.x = points->z;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
26
src/rwbase.h
26
src/rwbase.h
|
@ -89,6 +89,7 @@ struct V2d
|
|||
this->x = x; this->y = y; }
|
||||
};
|
||||
|
||||
inline V2d neg(const V2d &a) { return V2d(-a.x, -a.y); }
|
||||
inline V2d add(const V2d &a, const V2d &b) { return V2d(a.x+b.x, a.y+b.y); }
|
||||
inline V2d sub(const V2d &a, const V2d &b) { return V2d(a.x-b.x, a.y-b.y); }
|
||||
inline V2d scale(const V2d &a, float32 r) { return V2d(a.x*r, a.y*r); }
|
||||
|
@ -104,6 +105,7 @@ struct V3d
|
|||
this->x = x; this->y = y; this->z = z; }
|
||||
};
|
||||
|
||||
inline V3d neg(const V3d &a) { return V3d(-a.x, -a.y, -a.z); }
|
||||
inline V3d add(const V3d &a, const V3d &b) { return V3d(a.x+b.x, a.y+b.y, a.z+b.z); }
|
||||
inline V3d sub(const V3d &a, const V3d &b) { return V3d(a.x-b.x, a.y-b.y, a.z-b.z); }
|
||||
inline V3d scale(const V3d &a, float32 r) { return V3d(a.x*r, a.y*r, a.z*r); }
|
||||
|
@ -146,6 +148,12 @@ inline V3d rotate(const V3d &v, const Quat &q) { return mult(mult(q, Quat(v)), c
|
|||
Quat lerp(const Quat &q, const Quat &p, float32 r);
|
||||
Quat slerp(const Quat &q, const Quat &p, float32 a);
|
||||
|
||||
enum CombineOp
|
||||
{
|
||||
COMBINEREPLACE,
|
||||
COMBINEPRECONCAT,
|
||||
COMBINEPOSTCONCAT,
|
||||
};
|
||||
|
||||
struct Matrix
|
||||
{
|
||||
|
@ -171,6 +179,10 @@ struct Matrix
|
|||
static bool32 invert(Matrix *m1, Matrix *m2);
|
||||
static void invertOrthonormal(Matrix *m1, Matrix *m2);
|
||||
static void transpose(Matrix *m1, Matrix *m2);
|
||||
// some more RW like helpers
|
||||
void rotate(V3d *axis, float32 angle, CombineOp op);
|
||||
void translate(V3d *translation, CombineOp op);
|
||||
void scale(V3d *scl, CombineOp op);
|
||||
};
|
||||
|
||||
struct Matrix3
|
||||
|
@ -204,6 +216,20 @@ struct Sphere
|
|||
float32 radius;
|
||||
};
|
||||
|
||||
struct Plane
|
||||
{
|
||||
V3d normal;
|
||||
float32 distance;
|
||||
};
|
||||
|
||||
struct BBox
|
||||
{
|
||||
V3d sup;
|
||||
V3d inf;
|
||||
|
||||
void calculate(V3d *points, int32 n);
|
||||
};
|
||||
|
||||
enum PrimitiveType
|
||||
{
|
||||
PRIMTYPENONE = 0,
|
||||
|
|
|
@ -123,6 +123,9 @@ struct Frame : PluginBase<Frame>
|
|||
bool32 dirty(void) {
|
||||
return !!(this->root->object.privateFlags & HIERARCHYSYNC); }
|
||||
Matrix *getLTM(void);
|
||||
void rotate(V3d *axis, float32 angle, CombineOp op);
|
||||
void translate(V3d *trans, CombineOp op);
|
||||
void scale(V3d *trans, CombineOp op);
|
||||
void updateObjects(void);
|
||||
|
||||
|
||||
|
@ -409,6 +412,7 @@ struct Geometry : PluginBase<Geometry>
|
|||
void allocateData(void);
|
||||
void generateTriangles(int8 *adc = nil);
|
||||
void buildMeshes(void);
|
||||
void buildTristrips(void);
|
||||
void correctTristripWinding(void);
|
||||
void removeUnusedMaterials(void);
|
||||
|
||||
|
@ -447,9 +451,13 @@ struct Atomic : PluginBase<Atomic>
|
|||
// private
|
||||
WORLDBOUNDDIRTY = 0x01
|
||||
};
|
||||
enum {
|
||||
SAMEBOUNDINGSPHERE = 0x01, // for setGeometry
|
||||
};
|
||||
|
||||
ObjectWithFrame object;
|
||||
Geometry *geometry;
|
||||
Sphere boundingSphere;
|
||||
Sphere worldBoundingSphere;
|
||||
Clump *clump;
|
||||
LLLink inClump;
|
||||
|
@ -470,7 +478,7 @@ struct Atomic : PluginBase<Atomic>
|
|||
static Atomic *fromClump(LLLink *lnk){
|
||||
return LLLinkGetData(lnk, Atomic, inClump); }
|
||||
void removeFromClump(void);
|
||||
void setGeometry(Geometry *geo);
|
||||
void setGeometry(Geometry *geo, uint32 flags);
|
||||
Sphere *getWorldBoundingSphere(void);
|
||||
ObjPipeline *getPipeline(void);
|
||||
void render(void) { this->renderCB(this); }
|
||||
|
@ -518,6 +526,8 @@ struct Light : PluginBase<Light>
|
|||
float32 getAngle(void);
|
||||
void setColor(float32 r, float32 g, float32 b);
|
||||
int32 getType(void){ return this->object.object.subType; }
|
||||
void setFlags(uint32 flags) { this->object.object.flags = flags; }
|
||||
uint32 getFlags(void) { return this->object.object.flags; }
|
||||
static Light *streamRead(Stream *stream);
|
||||
bool streamWrite(Stream *stream);
|
||||
uint32 streamGetSize(void);
|
||||
|
@ -535,6 +545,17 @@ struct Light : PluginBase<Light>
|
|||
};
|
||||
};
|
||||
|
||||
struct FrustumPlane
|
||||
{
|
||||
Plane plane;
|
||||
/* Used for BBox tests:
|
||||
* 0 = inf is closer to normal direction
|
||||
* 1 = sup is closer to normal direction */
|
||||
uint8 closestX;
|
||||
uint8 closestY;
|
||||
uint8 closestZ;
|
||||
};
|
||||
|
||||
struct Camera : PluginBase<Camera>
|
||||
{
|
||||
enum { ID = 4 };
|
||||
|
@ -553,6 +574,10 @@ struct Camera : PluginBase<Camera>
|
|||
Matrix viewMatrix;
|
||||
float32 zScale, zShift;
|
||||
|
||||
FrustumPlane frustumPlanes[6];
|
||||
V3d frustumCorners[8];
|
||||
BBox frustumBoundBox;
|
||||
|
||||
// clump link handled by plugin in RW
|
||||
Clump *clump;
|
||||
LLLink inClump;
|
||||
|
|
|
@ -0,0 +1,452 @@
|
|||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
#include "rwbase.h"
|
||||
#include "rwerror.h"
|
||||
#include "rwplg.h"
|
||||
#include "rwpipeline.h"
|
||||
#include "rwobjects.h"
|
||||
|
||||
#define PLUGIN_ID 2
|
||||
|
||||
namespace rw {
|
||||
|
||||
struct GraphEdge
|
||||
{
|
||||
int32 node; /* index of the connected node */
|
||||
uint32 isConnected : 1; /* is connected to other node */
|
||||
uint32 otherEdge : 2; /* edge number on connected node */
|
||||
uint32 isStrip : 1; /* is strip edge */
|
||||
};
|
||||
|
||||
struct StripNode
|
||||
{
|
||||
uint16 v[3]; /* vertex indices */
|
||||
uint8 parent : 2; /* tunnel parent node (edge index) */
|
||||
uint8 visited : 1; /* visited in breadth first search */
|
||||
uint8 stripVisited : 1; /* strip starting at this node was visited during search */
|
||||
uint8 isEnd : 1; /* is in end list */
|
||||
GraphEdge e[3];
|
||||
int32 stripId; /* index of start node */
|
||||
// int asdf;
|
||||
LLLink inlist;
|
||||
};
|
||||
|
||||
struct StripMesh
|
||||
{
|
||||
int32 numNodes;
|
||||
StripNode *nodes;
|
||||
LinkList loneNodes; /* nodes not connected to any others */
|
||||
LinkList endNodes; /* strip start/end nodes */
|
||||
};
|
||||
|
||||
static void
|
||||
printNode(StripMesh *sm, StripNode *n)
|
||||
{
|
||||
printf("%3ld: %3d %3d.%d %3d.%d %3d.%d || %3d %3d %3d\n",
|
||||
n - sm->nodes,
|
||||
n->stripId,
|
||||
n->e[0].node,
|
||||
n->e[0].isStrip,
|
||||
n->e[1].node,
|
||||
n->e[1].isStrip,
|
||||
n->e[2].node,
|
||||
n->e[2].isStrip,
|
||||
n->v[0],
|
||||
n->v[1],
|
||||
n->v[2]);
|
||||
}
|
||||
|
||||
static void
|
||||
printLone(StripMesh *sm)
|
||||
{
|
||||
FORLIST(lnk, sm->loneNodes)
|
||||
printNode(sm, LLLinkGetData(lnk, StripNode, inlist));
|
||||
}
|
||||
|
||||
static void
|
||||
printEnds(StripMesh *sm)
|
||||
{
|
||||
FORLIST(lnk, sm->endNodes)
|
||||
printNode(sm, LLLinkGetData(lnk, StripNode, inlist));
|
||||
}
|
||||
|
||||
static void
|
||||
printSmesh(StripMesh *sm)
|
||||
{
|
||||
for(int32 i = 0; i < sm->numNodes; i++)
|
||||
printNode(sm, &sm->nodes[i]);
|
||||
}
|
||||
|
||||
static void
|
||||
collectFaces(Geometry *geo, StripMesh *sm, uint16 m)
|
||||
{
|
||||
StripNode *n;
|
||||
Triangle *t;
|
||||
sm->numNodes = 0;
|
||||
for(int32 i = 0; i < geo->numTriangles; i++){
|
||||
t = &geo->triangles[i];
|
||||
if(t->matId == m){
|
||||
n = &sm->nodes[sm->numNodes++];
|
||||
n->v[0] = t->v[0];
|
||||
n->v[1] = t->v[1];
|
||||
n->v[2] = t->v[2];
|
||||
n->e[0].node = 0;
|
||||
n->e[1].node = 0;
|
||||
n->e[2].node = 0;
|
||||
n->e[0].isConnected = 0;
|
||||
n->e[1].isConnected = 0;
|
||||
n->e[2].isConnected = 0;
|
||||
n->e[0].isStrip = 0;
|
||||
n->e[1].isStrip = 0;
|
||||
n->e[2].isStrip = 0;
|
||||
n->parent = 0;
|
||||
n->visited = 0;
|
||||
n->stripVisited = 0;
|
||||
n->isEnd = 0;
|
||||
n->stripId = -1;
|
||||
n->inlist.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Find Triangle that has edge e. */
|
||||
static GraphEdge
|
||||
findEdge(StripMesh *sm, int32 e[2])
|
||||
{
|
||||
StripNode *n;
|
||||
GraphEdge ge = { 0, 0, 0, 0 };
|
||||
for(int32 i = 0; i < sm->numNodes; i++){
|
||||
n = &sm->nodes[i];
|
||||
for(int32 j = 0; j < 3; j++){
|
||||
if(n->e[j].isConnected)
|
||||
continue;
|
||||
if(e[0] == n->v[j] &&
|
||||
e[1] == n->v[(j+1) % 3]){
|
||||
ge.node = i;
|
||||
ge.isConnected = 1;
|
||||
ge.otherEdge = j;
|
||||
return ge;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ge;
|
||||
}
|
||||
|
||||
/* Connect nodes sharing an edge, preserving winding */
|
||||
static void
|
||||
connectNodes(StripMesh *sm)
|
||||
{
|
||||
StripNode *n, *nn;
|
||||
int32 e[2];
|
||||
GraphEdge ge;
|
||||
for(int32 i = 0; i < sm->numNodes; i++){
|
||||
n = &sm->nodes[i];
|
||||
for(int32 j = 0; j < 3; j++){
|
||||
if(n->e[j].isConnected)
|
||||
continue;
|
||||
|
||||
/* flip edge and search for node */
|
||||
e[1] = n->v[j];
|
||||
e[0] = n->v[(j+1) % 3];
|
||||
ge = findEdge(sm, e);
|
||||
/* found node, now connect */
|
||||
if(ge.isConnected){
|
||||
n->e[j].node = ge.node;
|
||||
n->e[j].isConnected = 1;
|
||||
n->e[j].otherEdge = ge.otherEdge;
|
||||
n->e[j].isStrip = 0;
|
||||
nn = &sm->nodes[ge.node];
|
||||
nn->e[ge.otherEdge].node = i;
|
||||
nn->e[ge.otherEdge].isConnected = 1;
|
||||
nn->e[ge.otherEdge].otherEdge = j;
|
||||
nn->e[ge.otherEdge].isStrip = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int32
|
||||
numConnections(StripNode *n)
|
||||
{
|
||||
return n->e[0].isConnected +
|
||||
n->e[1].isConnected +
|
||||
n->e[2].isConnected;
|
||||
}
|
||||
|
||||
static int32
|
||||
numStripEdges(StripNode *n)
|
||||
{
|
||||
return n->e[0].isStrip +
|
||||
n->e[1].isStrip +
|
||||
n->e[2].isStrip;
|
||||
}
|
||||
|
||||
#define IsEnd(n) (numConnections(n) > 0 && numStripEdges(n) < 2)
|
||||
|
||||
static void
|
||||
complementEdge(StripMesh *sm, GraphEdge *e)
|
||||
{
|
||||
e->isStrip = !e->isStrip;
|
||||
e = &sm->nodes[e->node].e[e->otherEdge];
|
||||
e->isStrip = !e->isStrip;
|
||||
}
|
||||
|
||||
/* While possible extend a strip from a starting node until
|
||||
* we find a node already in a strip. N.B. this function does
|
||||
* make no attempts to connect to an already existing strip. */
|
||||
static void
|
||||
extendStrip(StripMesh *sm, StripNode *start)
|
||||
{
|
||||
StripNode *n, *nn;
|
||||
n = start;
|
||||
if(numConnections(n) == 0){
|
||||
sm->loneNodes.append(&n->inlist);
|
||||
return;
|
||||
}
|
||||
sm->endNodes.append(&n->inlist);
|
||||
n->isEnd = 1;
|
||||
tail:
|
||||
/* Find the next node to connect to on any of the three edges */
|
||||
for(int32 i = 0; i < 3; i++){
|
||||
if(!n->e[i].isConnected)
|
||||
continue;
|
||||
nn = &sm->nodes[n->e[i].node];
|
||||
if(nn->stripId >= 0)
|
||||
continue;
|
||||
|
||||
/* found one */
|
||||
nn->stripId = n->stripId;
|
||||
complementEdge(sm, &n->e[i]);
|
||||
n = nn;
|
||||
goto tail;
|
||||
}
|
||||
if(n != start){
|
||||
sm->endNodes.append(&n->inlist);
|
||||
n->isEnd = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
buildStrips(StripMesh *sm)
|
||||
{
|
||||
StripNode *n;
|
||||
for(int32 i = 0; i < sm->numNodes; i++){
|
||||
n = &sm->nodes[i];
|
||||
if(n->stripId >= 0)
|
||||
continue;
|
||||
n->stripId = i;
|
||||
extendStrip(sm, n);
|
||||
}
|
||||
}
|
||||
|
||||
static StripNode*
|
||||
findTunnel(StripMesh *sm, StripNode *n)
|
||||
{
|
||||
LinkList searchNodes;
|
||||
StripNode *nn;
|
||||
int edgetype;
|
||||
int isEnd;
|
||||
|
||||
searchNodes.init();
|
||||
edgetype = 0;
|
||||
for(;;){
|
||||
for(int32 i = 0; i < 3; i++){
|
||||
/* Find a node connected by the right edgetype */
|
||||
if(!n->e[i].isConnected ||
|
||||
n->e[i].isStrip != edgetype)
|
||||
continue;
|
||||
nn = &sm->nodes[n->e[i].node];
|
||||
|
||||
/* If the node has been visited already,
|
||||
* there's a shorter path. */
|
||||
if(nn->visited)
|
||||
continue;
|
||||
|
||||
/* Don't allow non-strip edge between nodes of the same
|
||||
* strip to prevent loops.
|
||||
* Actually these edges are allowed under certain
|
||||
* circumstances, but they require complex checks. */
|
||||
if(edgetype == 0 &&
|
||||
n->stripId == nn->stripId)
|
||||
continue;
|
||||
|
||||
isEnd = IsEnd(nn);
|
||||
|
||||
/* Can't add end nodes to two lists, so skip. */
|
||||
if(isEnd && edgetype == 1)
|
||||
continue;
|
||||
|
||||
nn->parent = n->e[i].otherEdge;
|
||||
nn->visited = 1;
|
||||
sm->nodes[nn->stripId].stripVisited = 1;
|
||||
|
||||
/* Search complete. */
|
||||
if(isEnd && edgetype == 0)
|
||||
return nn;
|
||||
|
||||
/* Found a valid node. */
|
||||
searchNodes.append(&nn->inlist);
|
||||
}
|
||||
if(searchNodes.isEmpty())
|
||||
return nil;
|
||||
n = LLLinkGetData(searchNodes.link.next, StripNode, inlist);
|
||||
n->inlist.remove();
|
||||
edgetype = !edgetype;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
resetGraph(StripMesh *sm)
|
||||
{
|
||||
StripNode *n;
|
||||
for(int32 i = 0; i < sm->numNodes; i++){
|
||||
n = &sm->nodes[i];
|
||||
n->visited = 0;
|
||||
n->stripVisited = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static StripNode*
|
||||
walkStrip(StripMesh *sm, StripNode *start)
|
||||
{
|
||||
StripNode *n, *nn;
|
||||
int32 last;
|
||||
|
||||
//printf("stripend: ");
|
||||
//printNode(sm, start);
|
||||
|
||||
n = start;
|
||||
last = -1;
|
||||
for(;;n = nn){
|
||||
n->visited = 0;
|
||||
n->stripVisited = 0;
|
||||
if(n->isEnd)
|
||||
n->inlist.remove();
|
||||
n->isEnd = 0;
|
||||
|
||||
if(IsEnd(n) && n != start)
|
||||
return n;
|
||||
|
||||
/* find next node */
|
||||
nn = nil;
|
||||
for(int32 i = 0; i < 3; i++){
|
||||
if(!n->e[i].isStrip || i == last)
|
||||
continue;
|
||||
nn = &sm->nodes[n->e[i].node];
|
||||
last = n->e[i].otherEdge;
|
||||
nn->stripId = n->stripId;
|
||||
break;
|
||||
}
|
||||
//printf(" next: ");
|
||||
//printNode(sm, nn);
|
||||
if(nn == nil)
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
applyTunnel(StripMesh *sm, StripNode *end, StripNode *start)
|
||||
{
|
||||
LinkList tmplist;
|
||||
StripNode *n, *nn;
|
||||
|
||||
for(n = end; n != start; n = &sm->nodes[n->e[n->parent].node]){
|
||||
//printf(" ");
|
||||
//printNode(sm, n);
|
||||
complementEdge(sm, &n->e[n->parent]);
|
||||
}
|
||||
//printf(" ");
|
||||
//printNode(sm, start);
|
||||
|
||||
//printSmesh(sm);
|
||||
//printf("-------\n");
|
||||
tmplist.init();
|
||||
while(!sm->endNodes.isEmpty()){
|
||||
n = LLLinkGetData(sm->endNodes.link.next, StripNode, inlist);
|
||||
/* take out of end list */
|
||||
n->inlist.remove();
|
||||
n->isEnd = 0;
|
||||
/* no longer an end node */
|
||||
if(!IsEnd(n))
|
||||
continue;
|
||||
// TODO: only walk strip if it was touched
|
||||
/* set new id, walk strip and find other end */
|
||||
n->stripId = n - sm->nodes;
|
||||
nn = walkStrip(sm, n);
|
||||
tmplist.append(&n->inlist);
|
||||
n->isEnd = 1;
|
||||
if(nn && n != nn){
|
||||
tmplist.append(&nn->inlist);
|
||||
nn->isEnd = 1;
|
||||
}
|
||||
}
|
||||
/* Move new end nodes to the real list. */
|
||||
sm->endNodes = tmplist;
|
||||
sm->endNodes.link.next->prev = &sm->endNodes.link;
|
||||
sm->endNodes.link.prev->next = &sm->endNodes.link;
|
||||
}
|
||||
|
||||
static void
|
||||
tunnel(StripMesh *sm)
|
||||
{
|
||||
StripNode *n, *nn;
|
||||
|
||||
again:
|
||||
FORLIST(lnk, sm->endNodes){
|
||||
n = LLLinkGetData(lnk, StripNode, inlist);
|
||||
// printf("searching %p %d\n", n, numStripEdges(n));
|
||||
nn = findTunnel(sm, n);
|
||||
// printf(" %p %p\n", n, nn);
|
||||
|
||||
if(nn){
|
||||
applyTunnel(sm, nn, n);
|
||||
resetGraph(sm);
|
||||
/* applyTunnel changes sm->endNodes, so we have to
|
||||
* jump out of the loop. */
|
||||
goto again;
|
||||
}
|
||||
resetGraph(sm);
|
||||
}
|
||||
printf("tunneling done!\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* For each material:
|
||||
* 1. build dual graph (collectFaces, connectNodes)
|
||||
* 2. make some simple strip (buildStrips)
|
||||
* 3. apply tunnel operator (tunnel)
|
||||
*/
|
||||
void
|
||||
Geometry::buildTristrips(void)
|
||||
{
|
||||
StripMesh smesh;
|
||||
|
||||
printf("%ld\n", sizeof(StripNode));
|
||||
|
||||
smesh.nodes = new StripNode[this->numTriangles];
|
||||
for(int32 i = 0; i < this->numMaterials; i++){
|
||||
smesh.loneNodes.init();
|
||||
smesh.endNodes.init();
|
||||
collectFaces(this, &smesh, i);
|
||||
connectNodes(&smesh);
|
||||
buildStrips(&smesh);
|
||||
printSmesh(&smesh);
|
||||
printf("-------\n");
|
||||
//printLone(&smesh);
|
||||
//printf("-------\n");
|
||||
//printEnds(&smesh);
|
||||
//printf("-------\n");
|
||||
tunnel(&smesh);
|
||||
//printf("-------\n");
|
||||
//printEnds(&smesh);
|
||||
}
|
||||
delete[] smesh.nodes;
|
||||
|
||||
exit(1);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue