Origin Brushes

Discuss the construction of maps and the tools to create maps for 3D games.
Post Reply
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Origin Brushes

Post by Baker »

Here is just a look at how one map compiler handles origin brushes in the bsp compiler. The compiler is "Zoner's tools" for Half-Life. The source code is here.

This code should also be in the Quake 2 map compiler somewhere, but I don't know where to find the Quake 2 map compiler source code. I looked around at ftp.idsoftware.com and so far haven't been able to find the q2bsp source.

How origin brushes appear to be handled

It looks like origin brushes are treated specially just like water, sky, clip textures, etc.
bspfile.h in Zoner Tools hlbsp wrote:typedef enum
{
CONTENTS_EMPTY = -1,
CONTENTS_SOLID = -2,
CONTENTS_WATER = -3,
CONTENTS_SLIME = -4,
CONTENTS_LAVA = -5,
CONTENTS_SKY = -6,
CONTENTS_ORIGIN = -7, // removed at csg time
CONTENTS_CLIP = -8, // changed to contents_solid

CONTENTS_CURRENT_0 = -9,
CONTENTS_CURRENT_90 = -10,
CONTENTS_CURRENT_180 = -11,
CONTENTS_CURRENT_270 = -12,
CONTENTS_CURRENT_UP = -13,
CONTENTS_CURRENT_DOWN = -14,

CONTENTS_TRANSLUCENT = -15,
CONTENTS_HINT = -16, // Filters down to CONTENTS_EMPTY by bsp, ENGINE SHOULD NEVER SEE THIS

#ifdef ZHLT_NULLTEX
CONTENTS_NULL = -17, // AJM // removed in csg and bsp, VIS or RAD shouldnt have to deal with this, only clip planes!
#endif

#ifdef ZHLT_DETAIL // AJM
CONTENTS_DETAIL = -18,
#endif
}
It appears the texture name must be "origin". Like how "clip" is a specially designated texture name.
static contents_t TextureContents(const char* const name)
{
if (!strncasecmp(name, "sky", 3))
return CONTENTS_SKY;

// =====================================================================================
//Cpt_Andrew - Env_Sky Check
// =====================================================================================
if (!strncasecmp(name, "env_sky", 3))
return CONTENTS_SKY;
// =====================================================================================

if (!strncasecmp(name + 1, "!lava", 5))
return CONTENTS_LAVA;

if (!strncasecmp(name + 1, "!slime", 6))
return CONTENTS_SLIME;

if (name[0] == '!') //optimized -- don't check for current unless it's liquid (KGP)
{
if (!strncasecmp(name, "!cur_90", 7))
return CONTENTS_CURRENT_90;
if (!strncasecmp(name, "!cur_0", 6))
return CONTENTS_CURRENT_0;
if (!strncasecmp(name, "!cur_270", 8))
return CONTENTS_CURRENT_270;
if (!strncasecmp(name, "!cur_180", 8))
return CONTENTS_CURRENT_180;
if (!strncasecmp(name, "!cur_up", 7))
return CONTENTS_CURRENT_UP;
if (!strncasecmp(name, "!cur_dwn", 8))
return CONTENTS_CURRENT_DOWN;
return CONTENTS_WATER; //default for liquids
}

if (!strncasecmp(name, "origin", 6))
return CONTENTS_ORIGIN;
A function that returns a string version of the name of the brush type
// =====================================================================================
// ContentsToString
// =====================================================================================
const char* ContentsToString(const contents_t type)
{
switch (type)
{
case CONTENTS_EMPTY:
return "EMPTY";
case CONTENTS_SOLID:
return "SOLID";
case CONTENTS_WATER:
return "WATER";
case CONTENTS_SLIME:
return "SLIME";
case CONTENTS_LAVA:
return "LAVA";
case CONTENTS_SKY:
return "SKY";
case CONTENTS_ORIGIN:
return "ORIGIN";
Ok, looks like the map compiler better reject any brush that isn't an entity. So an origin brush can't be part of the "world", must be a func_something or other entity type.
// check to make sure we dont have an origin brush as part of worldspawn
if ((b->entitynum == 0) || (strcmp("func_group", ValueForKey(&g_entities[b->entitynum], "classname"))==0))
{
if (contents == CONTENTS_ORIGIN)
{
Fatal(assume_BRUSH_NOT_ALLOWED_IN_WORLD, "Entity %i, Brush %i: %s brushes not allowed in world\n(did you forget to tie this origin brush to a rotating entity?)", b->entitynum, b->brushnum, ContentsToString(contents));

}
}
And here, for completeness is the part where the brush gets an all clear if it is an entity texture.
// otherwise its not worldspawn, therefore its an entity. check to make sure this brush is allowed
// to be an entity.
switch (contents)
{
case CONTENTS_SOLID:
case CONTENTS_WATER:
case CONTENTS_SLIME:
case CONTENTS_LAVA:
case CONTENTS_ORIGIN:
case CONTENTS_CLIP:
#ifdef ZHLT_NULLTEX // AJM
case CONTENTS_NULL:
break;
#endif
default:
Fatal(assume_BRUSH_NOT_ALLOWED_IN_ENTITY, "Entity %i, Brush %i: %s brushes not allowed in entity", b->entitynum, b->brushnum, ContentsToString(contents));
break;
}
The brush isn't part of the clipping hull. I think I understand the concept of a clipping hull, but I'm still learning about the bsp so I don't claim to know completely what is going on here:
void CreateBrush(const int brushnum)
{
brush_t* b;
int contents;
int h;

b = &g_mapbrushes[brushnum];

contents = b->contents;

if (contents == CONTENTS_ORIGIN)
return;


// HULL 0
MakeBrushPlanes(b);
MakeHullFaces(b, &b->hulls[0]);

// these brush types do not need to be represented in the clipping hull
switch (contents)
{
case CONTENTS_LAVA:
case CONTENTS_SLIME:
case CONTENTS_WATER:
case CONTENTS_TRANSLUCENT:
case CONTENTS_HINT:
return;
}
This is in parse_brush. I am thinking it probably is identifying the center point for rotation but I really don't know for sure.
static contents_t ParseBrush(entity_t* mapent) { ... wrote:

Code: Select all

    if (contents == CONTENTS_ORIGIN)
    {
        char            string[MAXTOKEN];
        vec3_t          origin;

        b->contents = CONTENTS_SOLID;
        CreateBrush(mapent->firstbrush + b->brushnum);     // to get sizes
        b->contents = contents;

        for (i = 0; i < NUM_HULLS; i++)
        {
            b->hulls[i].faces = NULL;
        }

        if (b->entitynum != 0)  // Ignore for WORLD (code elsewhere enforces no ORIGIN in world message)
        {
            VectorAdd(b->hulls[0].bounds.m_Mins, b->hulls[0].bounds.m_Maxs, origin);
            VectorScale(origin, 0.5, origin);
    
            safe_snprintf(string, MAXTOKEN, "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2]);
            SetKeyValue(&g_entities[b->entitynum], "origin", string);
        }
    }
Treat origin brushes like clip brushes, it seems:
bool ParseMapEntity() wrote:

Code: Select all

            if ((contents != CONTENTS_CLIP) && (contents != CONTENTS_ORIGIN))
                all_clip = false;
Apparently, an origin brush is actually part of the rotating object entity:

Code: Select all

    // if the given entity only has one brush and its an origin brush
    if ((mapent->numbrushes == 1) && (g_mapbrushes[mapent->firstbrush].contents == CONTENTS_ORIGIN))
    {
        brushhull_t*    hull = g_mapbrushes[mapent->firstbrush].hulls;

        Error("Entity %i, contains ONLY an origin brush near (%.0f,%.0f,%.0f)\n",
              this_entity, hull->bounds.m_Mins[0], hull->bounds.m_Mins[1], hull->bounds.m_Mins[2]);
    }
And maybe something again about identifying the center of the origin brush:

Code: Select all

// =====================================================================================
//  SetModelCenters
// =====================================================================================
static void     SetModelCenters(int entitynum)
{
    int             i;
    int             last;
    char            string[MAXTOKEN];
    entity_t*       e = &g_entities[entitynum];
    BoundingBox     bounds;
    vec3_t          center;

    if ((entitynum == 0) || (e->numbrushes == 0)) // skip worldspawn and point entities
        return;

    if (!*ValueForKey(e, "light_origin")) // skip if its not a zhlt_flags light_origin
        return;

    for (i = e->firstbrush, last = e->firstbrush + e->numbrushes; i < last; i++)
    {
        if (g_mapbrushes[i].contents != CONTENTS_ORIGIN)
        {
            bounds.add(g_mapbrushes[i].hulls->bounds);
        }
    }

    VectorAdd(bounds.m_Mins, bounds.m_Maxs, center);
    VectorScale(center, 0.5, center);

    safe_snprintf(string, MAXTOKEN, "%i %i %i", (int)center[0], (int)center[1], (int)center[2]);
    SetKeyValue(e, "model_center", string);
}
I'm probably going to try to track down the q2bsp source code. I imagine this all works the same. The license for Zoner Tools is a little weird and goes like this:

Code: Select all

II. LICENCE

0) This code is protected by the GPL, a link to the GPL can be found at the end of this page.

1) In addition to the GPL, the Valve SDK 2.3 EULA overrides any rights you may have obtained in the GPL, when needed.

2) The iD Quake 2 Licence overrides portions of both the Valve EULA, and the GPL where needed, please contact iD for information on this subject.
leileilol
Posts: 2783
Joined: Fri Oct 15, 2004 3:23 am

Post by leileilol »

Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Thank you. I'd very much rather use pure GPL code than something with a very strange license.
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

Interesting, thanks.

I can see that neither treeqbsp nor txqbsp support any "weird" content types, only clip. Oh well, after reading your post, a couple light bulbs went on.

How much does the engine have to support? I guess quite a bit more?

Hopefully, we can switch RMQ totally to origin brushes instead of hiprotate one day. We'll have to include the map compiler as well as the engine, but there's not really a problem with that.

:twisted:

Treat origin brushes like clip brushes, it seems:

Yeah, or like hint, skip... what have you.

Apparently, an origin brush is actually part of the rotating object entity:

Yes, that was clear to me before, because of the way you construct them in a map editor. You select both the origin brush and the (door, whatever) and turn both into an entity. Yep. And when the map loads, all that remains from the origin brush is its ... origin, I guess, because it's treated like a special content type. Or maybe it's indeed stripped by the map compiler. Too tired to think about that now.

identifying the center point for rotation

Yeah, that must be done so the engine knows what to rotate the brush around - otherwise it rotates it around '0 0 0'.

It's all a big hack, but a much better one than the hiprotate business. Just thinking about func_movewalls makes me sick.
metlslime
Posts: 316
Joined: Tue Feb 05, 2008 11:03 pm

Post by metlslime »

well, if you want to get rid of func_movewalls, you need more than just implement origin brushes, you also need collision of AABBs against rotated BSPs hulls.

However, even with the collision hackery, origin brushes would still be a quality of life improvement for mappers.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

goldenboy wrote:How much does the engine have to support? I guess quite a bit more?
The engine and QuakeC side looks easy:

http://www.quake-1.com/docs/quakesrc.org/129.html

The map compiler is the missing link.

I believe DarkPlaces and FTE support origin brushes and both support Half-Life .bsp, which supports origin brushes and can be compiled from Quake .map files (i.e. Worldcraft 3.3 or Hammer, but you have to use, say, Zoner's tools instead of aguirRe's txqbsp obviously).

Really if someone made a workable sample map and functional QuakeC for testing [via Half-Life .bsp at first], then there would be working model to use a test basis to ensure the map compiler changes were done properly.

/Is dodging the .bsp version # topic.
MeTcHsteekle
Posts: 399
Joined: Thu May 15, 2008 10:46 pm
Location: its a secret

Post by MeTcHsteekle »

eally if someone made a workable sample map and functional QuakeC for testing [via Half-Life .bsp at first], then there would be working model to use a test basis to ensure the map compiler changes were done properly.
what about the half-life to quake map experiments? perhaps the code from that could help as a starting point?

/shooting in the dark with this
bah
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

RMQ has the func_door_rotating from Quakelife, and for the rotation testing I used FTE (there is a testmap, of course not compiled with a proper map compiler yet). It does rotate, just not around the right origin. Don't know if FTE supports origin brushes though, might have to use that (cool) tutorial.

Unless someone is faster, I'll start the map compiler hackery in the new year. I'll use bjptools, because I hope it compiles on both Linux and Windows.

The collision is hopefully someone else's problem (looks at engine coders) ... You can get squished all right in FTE, though, thus I thought this wouldn't be a big issue. I'm often wrong though.

:-P
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

Every engine is compatible with origin brushes. There isn't any special code there regarding them.
The only thing engines lack is physics. (some software renderers can be a bit picky about some rotated bsps too however).

Baker, you missed the bit where it offsets all other brushes by the origin.
Or maybe that's already part of the code?
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Spike wrote:Baker, you missed the bit where it offsets all other brushes by the origin.
Or maybe that's already part of the code?
Wouldn't surprise me, at this point I know very little about what exactly a bsp compiler is doing and was trying a pilot effort to unwrap how the origin brushes work into it.

Goldenboy, if you have a sample origin brush map (+source) and progs (+source), can you zip them and upload them somewhere public like Quaketastic.

It would make it easier for someone wanting to see if they could successfully add it to the map compiler to test their work and lower the effort required.

For example, I might take a shot at it [I may or may not be .bsp knowledgeable enough to be successful] but without a sample map and progs to test it, it makes it hard.
Spike
Posts: 2914
Joined: Fri Nov 05, 2004 3:12 am
Location: UK
Contact:

Post by Spike »

well, basically all an origin brush does is set the origin field of an entity without causing the entity to otherwise move.
It really only matters if the entity(bsp model) rotates.

the ent's new origin should be (max+min)/2 of the origin brush, so that the pivot of the ent is at the center of the origin brush.
you can't just set the origin to the middle of the object, as that would make awkward doors.
The origin brushes are thus invisible, and make no sense if they're the only thing that makes up the model
But because the client will translate the model based on the entitiy's origin, you need to update the other brushes in the model the other way, thus subtracting the origin from each vertex to reverse what the client will do.

The end result is a model that can freely pivot around its own origin.

In Quake, the moving trains move to an entity placed where their min position would be. But then, in quake, all bsp model origins are at '0 0 0' initially, and the mins is usually positive, thus it moves to (target.origin-self.mins). Obviously, this maths still works if you used an origin brush, as the mins is then closer to '0 0 0' presumably. So much for examples.
goldenboy
Posts: 924
Joined: Fri Sep 05, 2008 11:04 pm
Location: Kiel
Contact:

Post by goldenboy »

http://www.quaketastic.com/upload/files ... otation.7z

There is a bsp, map, wad and progs.dat with source. Good luck hacking the map compiler. I'll gladly join the fray, but not now.

May I suggest Treeqbsp as a basis for the hackery.

May I further pimp bjptools:

http://qexpo.tastyspleen.net/uploaded/1 ... 090908.zip

Um, and to explain a bit better how to make a func_door_rotating without a .def or .fgd file, simply create a func_illusionary instead, change the classname to func_door_rotating, and create a "distance" key and set it to like 100.

There is a QUAKED definition in the code though, which can be copy/pasted and saved as a .def.

gb
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

goldenboy wrote:http://www.quaketastic.com/upload/files ... otation.7z

There is a bsp, map, wad and progs.dat with source. Good luck hacking the map compiler. I'll gladly join the fray, but not now.

May I suggest Treeqbsp as a basis for the hackery.

May I further pimp bjptools:

http://qexpo.tastyspleen.net/uploaded/1 ... 090908.zip

Um, and to explain a bit better how to make a func_door_rotating without a .def or .fgd file, simply create a func_illusionary instead, change the classname to func_door_rotating, and create a "distance" key and set it to like 100.

There is a QUAKED definition in the code though, which can be copy/pasted and saved as a .def.

gb
Thanks. Weekends are bad for me, but as soon as the weekend is over I'll get on it.

If I get stuck, I'll post here. I'll likely be using txqbsp.
Baker
Posts: 3666
Joined: Tue Mar 14, 2006 5:15 am

Post by Baker »

Post Reply