Crowbar - an open-source editor

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335
*Farnsworth voice* Good news, everyone!

Fzqzzai.png


Doesn't do much yet, but that grid was a pain in the arse to build.
 

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335
More good news: got selection by clicking to work.

49Gh5rB.gif


For those curious, I followed a surprisingly elegant method for obtaining the on-screen object which I read about somewhere on the internet (I think it was StackExchange, but I can't remember). Basically, you look at the Z-buffer (where the 3D "depth" of each pixel is stored) as you render each object; if the depth value for the pixel you're checking changes between before and after the object is rendered, you know that the object at that point was nearer (the point of the Z-buffer is not to render a pixel if the thing you're rendering is further away than the closest object at that pixel so far). It requires redrawing the view once, so if you've got a good frame rate (which you should have in a 3D application) it can potentially be used in real-time to select different object as the user drags their mouse over the screen, eg. "painting" textures onto faces as opposed to using multiple clicks (something Microbrush 2 touts).

This is just a POC test right now and the code behind it is kind of horrible, so I'll need to refactor it in a way that doesn't leave my OCD screaming before I'm able to utilise it fully.
 

henke37

aa
Sep 23, 2011
2,075
515
I hope that you have a plan for dealing with the situation of multiple brushes that have overlapping faces.
 

henke37

aa
Sep 23, 2011
2,075
515
I am not sure that you understood. I mean literally overlapping faces, they are in the same place, z-fighting. And it should be possible to select each face correctly without any crazy jiggling of the camera.
 

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335
That's exactly what I mean. There's no more "correct" way to handle that situation than above, because the Z-fighting is caused by floating point rounding errors in the Z-buffer when two faces are on the same plane and the camera is slightly oblique. If a pixel from one face is drawn "over" the other face, that means the value in the Z-buffer was slightly nearer to the camera after floating point rounding, hence the Z-buffer will have been updated to the new value. If you click on that pixel, the view will detect the Z update and will know that that object was rendered at that pixel.
 

henke37

aa
Sep 23, 2011
2,075
515
That's not how hammer does it, hammer allows you to hold the mouse button to cycle between the overlapping faces.
 

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335
I didn't know it did that - however, I've just tried it and I think it's "drill picking" - you run the algorithm again but don't render the object you've already selected, so you'll get a Z-buffer change for the object behind instead.
 

wareya

L420: High Member
Jun 17, 2012
493
191
I think hammer's doing a raycast to figure out the distances of everything under the cursor because on sufficiently complex maps selecting anything takes a significant split second for me, much longer than a single frame.
 

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335
So, something more exciting this time: I’ve got a working download for you guys to muck about with. It’s more of a proof-of-concept tech demo at the moment, but it does show how the views should work and the basis for how the objects within the editor will be structured.

EfP9Tzc.png


At the moment, the build is for Windows 32-bit (although that screenshot is from my MacBook) and is available here: https://www.dropbox.com/s/s2hk1sqpybyruaf/crowbar_alpha_0.01.zip?dl=0 It should work straight out of the box, but if not then try installing the Microsoft Visual C++ Runtime. (I’d expect most of you have it anyway, since AFAIK it’s a prerequisite for Source games.)

For controls: the mouse wheel will zoom the 2D view in and out, and will move the camera in the 3D view, in a similar way to Hammer. (Right now I think the zooming is inverted because I’ve been doing a lot of the development on a MacBook.) Space-drag will scroll the 2D view; pressing P will activate mouse look mode in the 3D view, where W/A/S/D/Q/Z will move the camera until P is pressed again. The views require focus to receive the key events, so click on them if they don’t seem to work. All these keys will be customisable in the end product, but they’re hard-coded for now.

To explain what’s going on in the demo, I’ll start with how things are usually managed in Hammer. As you may know, brushes in Hammer are always defined in world space: the co-ordinates of the vertices are relative to the big red, green and blue sticks that sit in the centre of the map (the world origin). In 2D, it would look something like this:

puL5Rg3.png


This setup is simple and intuitive, but it means that co-ordinates that are “convenient” with respect to the world (like brushes that are on-grid) are handled better than other, more arbitrary co-ordinates. If you’ve worked on small fiddly bits of brushwork, or rotated complicated pieces of geometry at arbitrary angles, you’ll probably have run into what’s known as “floating point precision” errors: because of the odd angle or the very small co-ordinate values, the computer doesn’t have enough mathematical precision to store the exact values (you can’t represent 12.123454321, for example, if you can only store 5 digits), so it approximates. Because these approximations aren’t exactly the same as the ideal values, you end up with minute gaps between brushes or, at worst, invalid solids. If you do more odd rotations then these errors will be carried through the subsequent rotations, potentially growing larger and larger.

What we can do is be a bit clever and allow brushes to be defined not just relative to the world (like normal), but also relative to other brushes. This works out like so:

JPi4hfr.png


Each object - be it a brush, entity, or anything else - is given its own origin, and the origin is positioned relative to the object’s parent. This is exactly the same as how parenting works in Source: a child will move wherever its parent moves, and will maintain the same relative orientation when its parent rotates. It will also maintain the same relative scale: if you make the parent twice as big, the child will also become twice as big as it was before.

The co-ordinates of a brush, instead of being defined with respect to the world, are now defined with respect to its own origin. As you can see in the image above, the blue child square specifies its top right X value as 0.25 units away from its local origin, as opposed to 4.5 units away from the global map origin. This means that we can keep the co-ordinates of brush vertices convenient (eg. as integers), specify the translation/rotation/scale of the brush relative to its parent, and then calculate the world co-ordinates when we need them (when compiling). This won’t totally eliminate floating-point precision errors, but it will mean that they’ll only be calculated once when you compile the map, and the compiler may well do other clever things like merging groups vertices that are all minutely close together.

In terms of general level construction, this hierarchical method lets us do two very useful things:
  • It lets us position a brush relative to another reference brush, in situations where this might be awkward to do otherwise, and
  • It allows us to do whatever we want with the reference brush and have the child stay in the same relative position.

Since everything, including the world itself, is a hierarchical object, you can even apply transformations to the world and have them affect all the objects in the map. Got an over-scaled map? Scale the world down and all the brushes will follow suit.

In this demo, the orange blocks are the parent brushes and the small red blocks are the children. If you select the orange brushes (through the selection dialogue at the moment, unfortunately) and transform them with the transformation dialogue, the red brushes will change too.

Basically, try your best to break the application and let me know what you think of it. :p

EDIT: Only just realised weird things were happening with that link, it's fixed now.
 
Last edited:

Passerby

L2: Junior Member
Mar 27, 2010
99
15
Are you still Devloping this on git, I took a look at the repo but last commit was 8 months ago.
 

worMatty

Repacking Evangelist
aa
Jul 22, 2014
1,258
999
Nice work, x6herbius. Would you mind providing me with an example of when the following benefit would be useful? I think I don't have enough experience to be able to imagine a situation. Plus I'm a bit thick when it comes to maths:

It lets us position a brush relative to another reference brush, in situations where this might be awkward to do otherwise, and
 

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335
I am still working on this, but because it's now my university final year project it's in a private repository until I finish with it this term, then I'll update the public one.

For the relative positioning, the classic example is a robotic arm - perhaps something more Half-Life than TF2, but it demonstrates it well. Say you have a base that sits on the floor, a first arm portion that's hinged on the base, a second arm portion that's hinged on the end of the first and then a pincer, made of two parts, that are both hinged on the end of the second arm. Using hierarchical positioning, you can move the first arm part to whatever wacky rotation you want on the base and the lower parts of the arm will maintain their relative positions. Then you can do the same for the second part of the arm, and still be able to open and close the pincer pieces by rotating them on the correct axis. If you were in Hammer, it'd be nigh impossible to rotate the hinged pincers in any useful way once they were at some skew angle, or to shift them relative to the endpoint of the second arm, because you can only perform operations along the world X/Y/Z axes.
 
Last edited:

YM

LVL100 YM
aa
Dec 5, 2007
7,135
6,056
Can you make it so that things -only- have an origin if they use something other than the default grid's origin please? It'll be unbelievably frustrating to have to worry about an origin for every single brush.
 

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335
I was thinking of having the origin be fairly transparent to most operations - ie. it exists, but you don't have to care about it in general. There's essentially no difference between a brush whose origin is at (0,0,0) and whose vertices range from (10,10,10) to (20,20,20), and a brush whose origin is at (15,15,15) and whose vertices range from (-5,-5,-5) to (5,5,5), if you apply your operations appropriately.

With Hammer's "flat" hierarchy where everything is just a child of the world, you perform translations/rotations/scaling on the physical vertex values with respect to a reference point - rotations are done around the mid-point, scaling is done against the minimum/maximum point of the brush's bounding box (ie. the opposite corner to the one you're dragging), etc, and this changes the co-ordinates of these vertices. If you've got a non-flat hierarchy you can still perform these same operations on the physical vertices (changing their co-ordinates with respect to the origin), but on top of that you've also got the option of transforming the origin itself (which doesn't change any vertex co-ordinates at all - they're still in the same relative positions to the brush origin, but the brush origin has been oriented differently). The position of vertices relative to the origin doesn't really matter at all in a flat hierarchy, which is perfectly mostly sufficient for Hammer, but in a non-flat one the origin becomes the pivot point of the child on the parent and so you'll want to be able to specify exactly where it is on your brush.

In general, which type of operation you choose to perform depends entirely on which would be easier - I'd imagine translating a brush would just translate the origin in the world, rotating would rotate the origin in the world, and scaling would scale the vertices themselves with respect to the brush origin, before hierarchies come into play.
 
Last edited:

NoodleCollie

Stoat fiend
aa
Jul 30, 2009
383
335
Ladies and gentlemen, we have our first brush:

07SVCqG.jpg


There's still a long way to go, but I can draw a brushes, snapped to the grid, export them as VMFs and have them load correctly in Hammer. This is an achievement for me personally.
 

Kraken

Few more zeros and ones for the site to proccess
Dec 21, 2014
430
121
Just found about this.
It's amazing and the maps work in hammer.
Incredible.
 

worMatty

Repacking Evangelist
aa
Jul 22, 2014
1,258
999
Very well done.