Origin Brushes
Moderator: InsideQC Admins
14 posts
• Page 1 of 1
Origin Brushes
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.
It appears the texture name must be "origin". Like how "clip" is a specially designated texture name.
A function that returns a string version of the name of the brush type
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.
And here, for completeness is the part where the brush gets an all clear if it is an entity texture.
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:
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.
Treat origin brushes like clip brushes, it seems:
Apparently, an origin brush is actually part of the rotating object entity:
And maybe something again about identifying the center of the origin brush:
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:
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",)
return CONTENTS_CURRENT_270;
if (!strncasecmp(name, "!cur_180",)
return CONTENTS_CURRENT_180;
if (!strncasecmp(name, "!cur_up", 7))
return CONTENTS_CURRENT_UP;
if (!strncasecmp(name, "!cur_dwn",)
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.
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
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.
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.
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.
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.
-

goldenboy - Posts: 924
- Joined: Fri Sep 05, 2008 11:04 pm
- Location: Kiel
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.
However, even with the collision hackery, origin brushes would still be a quality of life improvement for mappers.
- metlslime
- Posts: 316
- Joined: Tue Feb 05, 2008 11:03 pm
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.
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
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
- MeTcHsteekle
- Posts: 399
- Joined: Thu May 15, 2008 10:46 pm
- Location: its a secret
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.

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.
-

goldenboy - Posts: 924
- Joined: Fri Sep 05, 2008 11:04 pm
- Location: Kiel
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?
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?
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
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.
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
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.
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.
- Spike
- Posts: 2892
- Joined: Fri Nov 05, 2004 3:12 am
- Location: UK
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
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
-

goldenboy - Posts: 924
- Joined: Fri Sep 05, 2008 11:04 pm
- Location: Kiel
goldenboy wrote:http://www.quaketastic.com/upload/files/misc/rotation.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
Relevant link, storing here so I can find it without forum digging:
http://www.superjer.com/learn/func_door_rotating.php
viewtopic.php?p=13056&highlight=another#13056
http://www.superjer.com/learn/func_door_rotating.php
viewtopic.php?p=13056&highlight=another#13056
-

Baker - Posts: 3666
- Joined: Tue Mar 14, 2006 5:15 am
14 posts
• Page 1 of 1
Who is online
Users browsing this forum: No registered users and 1 guest