I’m having trouble coming up with a logical way to represent the edges (the rainbow blotches) as integers. I don’t want to just do some clockwise/counter clockwise representation because I think that’s not very thoughtful and I’m also willing to skip numbers. They can be negative (although I think thats nasty too), should have some relation with the cartesian coordinates of the edge (however that will be represented), and be memorable. I think it would be cleanest if they were represented with the combination of only 1s and 0s too

I guess they aren’t really enums

I’d like to avoid counting the non external edges of the 1x1 squares that make up the big square too

It doesn’t really make sense to use base 3 with the dyadic structure of that square, the way I see it. (Obviously you’d end up with an extra code, for instance.) You could just replace the binary representation with its ternary equivalent, but it wouldn’t be as natural, I suppose.

I don’t see an easy way, but you could have first digit (being a 0, 1, or 2) represent if the edge was on the top, one of the sides, or the bottom. The problem we run into is that there are two possible edge locations on top, two and bottom, and four in the middle when we can only represent 3 possible locations with another digit. I’d have the top left be 00, top right be 01, and then let the right side upper edge be 02. On the bottom, 20 would be the bottom right, 21 the bottom left, and 22 actually be the left side lower edge. In the middle 10 is the left upper edge, 11 isn’t assigned (it’s the middle) and 12 is the right side lower edge.

Now all of that being said, if I was actually using this in a game, I would have all the edges in a circular linked list with the unary operator metamethod returning the edge on the opposite side. If it would be helpful in your particular use case, you could also just have 4 edges with clockwise and counterclockwise sub-edges.

What I was suggesting in the second half of my response is not having to restrict ourselves to defining an enumeration as integer values, but rather as distinct tables, each with varying properties describing the edge in relation to the others. Like so:

Now I’m not sure what your use case is (something like this could easily be overkill) but I’ve found that breaking things down like this helps me. For example in one game I’m working on right now, I have to determine what direction (up, down, left, right, forward, backward) a thruster block is placed on a ship so that I know which thrusters to use when the player flies it and presses w (forward). I also need to be able to easily convert this into local x-y-z vectors, and need to frequently find the inverse vector/direction when applying inertia dampening using the opposite thrusters. Sometimes when a cockpit block is taken off and put on in a different direction, I needed to apply clockwise rotations around the different axis’s to match the new orientation. What I ended up with is this code (feel free to use in your personal projects):

local abs = math.abs
local Iterable = shared.Iterable
local negatable = {
__unm = function(self)
return self.neg
end
}
local FORWARD = setmetatable({
dir = 'Z';
sign = 1;
vector = Vector3.new(0, 0, 1);
}, negatable)
local BACKWARD = setmetatable({
dir = FORWARD.dir;
sign = -FORWARD.sign;
vector = -FORWARD.vector;
}, negatable)
local UP = setmetatable({
dir = 'Y';
sign = 1;
vector = Vector3.new(0, 1, 0);
}, negatable)
local DOWN = setmetatable({
dir = UP.dir;
sign = -UP.sign;
vector = -UP.vector;
}, negatable)
local LEFT = setmetatable({
dir = 'X';
sign = 1;
vector = Vector3.new(1, 0, 0);
}, negatable)
local RIGHT = setmetatable({
dir = LEFT.dir;
sign = -LEFT.sign;
vector = -LEFT.vector;
}, negatable)
FORWARD[FORWARD] = FORWARD;
FORWARD[BACKWARD] = FORWARD;
FORWARD[UP] = RIGHT;
FORWARD[DOWN] = -FORWARD[UP];
FORWARD[LEFT] = UP;
FORWARD[RIGHT] = -FORWARD[LEFT];
BACKWARD[FORWARD] = BACKWARD;
BACKWARD[BACKWARD] = BACKWARD;
BACKWARD[UP] = FORWARD[DOWN];
BACKWARD[DOWN] = FORWARD[UP];
BACKWARD[LEFT] = FORWARD[RIGHT];
BACKWARD[RIGHT] = FORWARD[LEFT];
UP[FORWARD] = LEFT;
UP[BACKWARD] = -UP[FORWARD];
UP[UP] = UP;
UP[DOWN] = UP;
UP[LEFT] = BACKWARD;
UP[RIGHT] = -UP[LEFT];
DOWN[FORWARD] = UP[BACKWARD];
DOWN[BACKWARD] = UP[FORWARD];
DOWN[UP] = DOWN;
DOWN[DOWN] = DOWN;
DOWN[LEFT] = UP[RIGHT];
DOWN[RIGHT] = UP[LEFT];
LEFT[FORWARD] = DOWN;
LEFT[BACKWARD] = -LEFT[FORWARD];
LEFT[UP] = FORWARD;
LEFT[DOWN] = -LEFT[UP];
LEFT[LEFT] = LEFT;
LEFT[RIGHT] = LEFT;
RIGHT[FORWARD] = LEFT[BACKWARD];
RIGHT[BACKWARD] = LEFT[FORWARD];
RIGHT[UP] = LEFT[DOWN];
RIGHT[DOWN] = LEFT[UP];
RIGHT[LEFT] = RIGHT;
RIGHT[RIGHT] = RIGHT;
FORWARD.neg = BACKWARD
BACKWARD.neg = FORWARD
UP.neg = DOWN
DOWN.neg = UP
LEFT.neg = RIGHT
RIGHT.neg = LEFT
local Operations = {}
Operations.__index = Operations
Operations.__call = next
function Operations:__unm()
local ops = setmetatable({}, Operations)
local len = #self
for i, op in self do
ops[len - i + 1] = -op
end
return ops
end
local Direction = {
VALUES = Iterable {
FORWARD = FORWARD;
BACKWARD = BACKWARD;
UP = UP;
DOWN = DOWN;
LEFT = LEFT;
RIGHT = RIGHT;
};
}
Direction.__index = Direction
function Direction.new()
return setmetatable({
primaryAxis = FORWARD;
secondaryAxis = UP;
tertiaryAXis = LEFT;
},Direction)
end
function Direction.getDir(vector)
local v1 = FORWARD:Dot(vector)
local v2 = UP:Dot(vector)
local v3 = LEFT:Dot(vector)
if abs(v1) > abs(v2) then
if abs(v1) > abs(v3) then
if v1 > 0 then
return FORWARD
else
return BACKWARD
end
elseif v3 > 0 then
return LEFT
else
return RIGHT
end
elseif abs(v2) > abs(v3) then
if v2 > 0 then
return UP
else
return DOWN
end
elseif v3 > 0 then
return LEFT
else
return RIGHT
end
end
function Direction.newFromCFrame(cframe)
return setmetatable({
primaryAxis = Direction.getDir(cframe:vectorToWorldSpace(FORWARD.vector));
secondaryAxis = Direction.getDir(cframe:vectorToWorldSpace(UP.vector));
tertiaryAXis = Direction.getDir(cframe:vectorToWorldSpace(LEFT.vector));
},Direction)
end
function Direction:getOperations(fromDirection)
local operations = {}
local from1 = fromDirection.primaryAxis
local from2 = fromDirection.secondaryAxis
local from3 = fromDirection.tertiaryAxis
local to1 = self.primaryAxis
if from1 == to1 then
elseif from1[from2] == to1 then
operations[1] = from2
elseif from1[-from2] == to1 then
operations[1] = -from2
elseif from1[from3] == to1 then
operations[1] = from3
elseif from1[-from3] == to1 then
operations[1] = from3
else
operations[1] = from2
operations[2] = from2
end
local to2 = self.secondaryAxis
if from2 == to2 then
elseif from2[from1] == to2 then
operations[#operations + 1] = from1
elseif from2[-from1] == to2 then
operations[#operations + 1] = -from1
elseif from2[from3] == to2 then
operations[#operations + 1] = from3
elseif from2[-from3] == to2 then
operations[#operations + 1] = -from3
else
operations[#operations + 1] = from1
operations[#operations + 1] = from1
end
return operations
end
function Direction:applyOperations(operations)
for i, operation in ipairs(operations) do
self.primaryAxis = self.primaryAxis[operation]
self.secondaryAxis = self.secondaryAxis[operation]
self.tertiaryAxis = self.tertiaryAxis[operation]
end
end

But if I was going to restrict the enum to 2 digits of any base, I’d say either base 4. It must be a higher base than 2 to represent all those edges, and 8 or higher can always represent all the edges in a single digit. Bases 3, 5, 6, and 7 don’t divide easily. That leaves 4 for two digits or base 8 or higher otherwise with a single digit.

I’d create loops that took advantage of geometric patterns to initialize the enumerations. Anything that could be computed at runtime in this case can be precomputed and stored as helpful information in the enumeration.

The initialization doesn’t have to take advantage of patterns or generalizations, although it is nice if it is possible to. Anything that can be computed given the structure and no additional input can be precomputed (even if that means the hard way without helpful patterns) if the structure is known during initialization. I can’t say much more without knowing specific details. “active edges” sounds like the maze generation algorithms and pathfinding algorithms I’ve worked on. You can send me a PM if the details are secret and would like some of my input.

Details aren’t really secret but as of right now I’m not really worrying about what I can optimize in run time, but how to minimize the amount of time I will spend actually inputting the bare minimum of what is necessary (I realize these two are interconnected but emphasis on “bare minimum” because I will be making as many optimizations as possible)

So that’s why I’m looking for a practical method for representing the edges that does not take much effort to type nor think about its meaning