8
\$\begingroup\$

Here's some code I wrote about 5 years ago and was looking over recently. The goal was to generate walls with textures on them. If there are intersections in the walls, the textures on the inside are slightly compressed and textures on the outside will be slightly expanded so that patterns tile correctly and there can be special patterns for corners that line up. Here's an example of it in use:

enter image description here

Here's the monstrosity that implements the core algorithm of generating it. It creates quads of 4 points and sends them to the QuadGeometry class, which builds the actual vertex/index buffers. It also generates the tops and edges of walls (as you can see from the TODOs I never finished this part).

using System.Collections.Generic;
using masques.map;
using masques.util;
using SharpDX;

namespace masques.graphics
{
    public static class WallGeometry
    {
        private static Vector3 vec3(float x, float y, float z) { return new Vector3(x, y, z); }

        private static int lookLeftAndRight(MapWalls walls, int x, int y, int z)
        {
            int c = 0;
            if(x > 0 && walls.X.exists(x - 1, y, z)) c += 1;
            if(x < walls.width && walls.X.exists(x, y, z)) c += 2;
            return c;
        }

        private static int lookUpAndDown(MapWalls walls, int x, int y, int z)
        {
            int c = 0;
            if(y > 0 && walls.Y.exists(x, y - 1, z)) c += 1;
            if(y < walls.height && walls.Y.exists(x, y, z)) c += 2;
            return c;
        }

        private static void adjustCorners(ref float a, ref float b, int corners, float adjustment, bool pushOut)
        {
            if(corners == 1)      { a -= adjustment; if(pushOut) b += adjustment; }
            else if(corners == 2) { b -= adjustment; if(pushOut) a += adjustment; }
            else if(corners == 3) { a -= adjustment; b -= adjustment; }
        }

        // TODO edge and top materials not always the defaults
        // TODO don't put top on wall if wall/tile is above
        public static void build(MapWalls walls, QuadGeometry geo) { build(walls, geo, 0, 0, 0, walls.width, walls.height, walls.layers, MapWalls.EDGE_SIZE, 1, true); }
        public static void build(MapWalls walls, QuadGeometry geo, int x0, int y0, int z0, int x1, int y1, int z1, float edgeSize, float wallHeight, bool includeBorders)
        {
            if(x1 <= x0 || y1 <= y0) return;
            int w = walls.width, h = walls.height;
            const ushort edgeMat = 1;
            const ushort topMat = 1;

            /***********************************************************************************************
             *                                           X WALLS                                           *
             ***********************************************************************************************/

            int y0x = includeBorders ? y0 : y0 + 1;
            int y1x = includeBorders ? y1 + 1 : y1;
            foreach(KeyValuePair<IntPoint, WallSegment> kvp in walls.X)
            {
                int x = kvp.Key.x;
                int y = kvp.Key.y;
                int z = kvp.Key.z;
                if(x < x0 || x >= x1 || y < y0x || y >= y1x || z < z0 || z >= z1)
                    continue;


                WallSegment segment = kvp.Value;
                float px1 = x, px1a = px1, px1b = px1;
                float px2 = x + 1, px2a = px2, px2b = px2;
                float py = y;
                float py1 = py - edgeSize;
                float py2 = py + edgeSize;
                float pz = z;

                int corners1 = lookUpAndDown(walls, x, y, z);
                int corners2 = lookUpAndDown(walls, x + 1, y, z);
                bool isWallStart = x == 0 || !walls.X.exists(x - 1, y, z);
                bool isWallEnd = x == w - 1 || !walls.X.exists(x + 1, y, z);
                adjustCorners(ref px1a, ref px1b, corners1, -edgeSize, isWallStart);
                adjustCorners(ref px2a, ref px2b, corners2, +edgeSize, isWallEnd);
                float tx1a = px1a, tx1b = px1b, tx2a = px2a, tx2b = px2b;

                // if we're the beginning of a wall...
                if(isWallStart)
                {
                    // If there are no Y intersections here, add an edge
                    if(corners1 == 0) geo.add(vec3(px1a, py2, pz), vec3(px1a, py1, pz), vec3(px1a, py2, pz + wallHeight), vec3(px1a, py1, pz + wallHeight), 0, 1, 0, 1, edgeMat);
                }
                else if(corners1 != 3)
                {
                    tx1a = px1;
                    tx1b = px1;
                }

                // if we're at the end of a wall...
                if(isWallEnd)
                {
                    // If there are no Y intersections here, add an edge
                    if(corners2 == 0) geo.add(vec3(px2a, py1, pz), vec3(px2a, py2, pz), vec3(px2a, py1, pz + wallHeight), vec3(px2a, py2, pz + wallHeight), 0, 1, 0, 1, edgeMat);
                }
                else if(corners2 != 3)
                {
                    tx2a = px2;
                    tx2b = px2;
                }

                // top
                if(!walls.X.exists(x, y, z + 1))
                {
                    geo.add(vec3(tx2b, py2, pz + wallHeight), vec3(tx1b, py2, pz + wallHeight), vec3(tx2a, py1, pz + wallHeight), vec3(tx1a, py1, pz + wallHeight), 0, 1, 0, 1, topMat);
                }

                // faces
                geo.add(vec3(px1a, py1, pz),  vec3(px2a, py1, pz),  vec3(px1a, py1, pz + wallHeight), vec3(px2a, py1, pz + wallHeight), x + 1, x, z, z + 1, segment.back);
                geo.add(vec3(px2b, py2, pz),  vec3(px1b, py2, pz),  vec3(px2b, py2, pz + wallHeight), vec3(px1b, py2, pz + wallHeight), w - x, w - x - 1, z, z + 1, segment.front);
            }

            /***********************************************************************************************
             *                                           Y WALLS                                           *
             ***********************************************************************************************/

            int x0y = includeBorders ? x0 : x0 + 1;
            int x1y = includeBorders ? x1 + 1 : x1;
            foreach(KeyValuePair<IntPoint, WallSegment> kvp in walls.Y)
            {
                int x = kvp.Key.x;
                int y = kvp.Key.y;
                int z = kvp.Key.z;
                if(x < x0y || x >= x1y || y < y0 || y >= y1 || z < z0 || z >= z1)
                    continue;

                WallSegment segment = kvp.Value;
                float px = x;
                float px1 = px - edgeSize;
                float px2 = px + edgeSize;
                float py1 = y, py1a = py1, py1b = py1;
                float py2 = py1 + 1, py2a = py2, py2b = py2;
                float pz = z;

                int corners1 = lookLeftAndRight(walls, x, y, z);
                int corners2 = lookLeftAndRight(walls, x, y + 1, z);
                bool isWallStart = y == 0 || !walls.Y.exists(x, y - 1, z);
                bool isWallEnd = y == h - 1 || !walls.Y.exists(x, y + 1, z);
                adjustCorners(ref py1a, ref py1b, corners1, -edgeSize, isWallStart);
                adjustCorners(ref py2a, ref py2b, corners2, +edgeSize, isWallEnd);
                float ty1a = py1a, ty1b = py1b, ty2a = py2a, ty2b = py2b;

                // if we're the beginning of a wall...
                if(isWallStart)
                {
                    // If there are no X intersections here, add an edge
                    if(corners1 == 0) geo.add(vec3(px1, py1a, pz), vec3(px2, py1a, pz), vec3(px1, py1a, pz + wallHeight), vec3(px2, py1a, pz + wallHeight), 0, 1, 0, 1, edgeMat);
                }
                else
                {
                    ty1a = py1;
                    ty1b = py1;
                }

                // if we're at the end of a wall...
                if(isWallEnd)
                {
                    // If there are no X intersections here, add an edge
                    if(corners2 == 0) geo.add(vec3(px2, py2a, pz), vec3(px1, py2a, pz), vec3(px2, py2a, pz + wallHeight), vec3(px1, py2a, pz + wallHeight), 0, 1, 0, 1, edgeMat);
                }
                else
                {
                    ty2a = py2;
                    ty2b = py2;
                }

                // top
                if(!walls.Y.exists(x, y, z + 1))
                {
                    geo.add(vec3(px2, ty2b, pz + wallHeight), vec3(px1, ty2a, pz + wallHeight), vec3(px2, ty1b, pz + wallHeight), vec3(px1, ty1a, pz + wallHeight), 0, 1, 0, 1, topMat);
                }

                // wall faces
                geo.add(vec3(px1, py2a, pz),  vec3(px1, py1a, pz),  vec3(px1, py2a, pz + wallHeight), vec3(px1, py1a, pz + wallHeight), h - y, h - y - 1, z, z + 1, segment.back);
                geo.add(vec3(px2, py1b, pz),  vec3(px2, py2b, pz),  vec3(px2, py1b, pz + wallHeight), vec3(px2, py2b, pz + wallHeight), y + 1, y, z, z + 1, segment.front);
            }
        }
    }
}

I was thinking about converting it to Unity, which uses Y-up instead of Z-up since this is a pretty efficient/easy way to throw together simple buildings, but before I do that it might make sense to clean it up a bit. In particular, I'm looking if there's a good way to combine the totally duplicated X walls and Y walls parts. It might be possible to tackle those a different way (such as generating always in X then swapping the coordinates for Y walls), though it's not straightforward how that would be done.

Any suggestions, or is this unsalvageable?

\$\endgroup\$
8
\$\begingroup\$

Regarding CleanCode, it is a good practice to:

  1. provide more clear names: in the lookxxxx methods, I guess that c is corner?
  2. use brackets with if-statements (even if there is a single statement).
  3. no magic numbers; the 1 and 2 mean? (lookup methods)
  4. adjustCorners: use a switch-case instead of the if's
  5. the geo.add lines are really hard to read. Why not divide them over multiple lines?
  6. To prevent a lot of parameters, try to pass objects; like wall, instead of wall.width, wall,height, wall.layers (if you want to keep the current build method, add an overload that takes the wall object).
  7. Extract more methods to make easier to read and understand. Your comments are the first indicators and suggestions for the method names ;-) like: // If there are no Y intersections here, add an edge
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.