#define INTERSECT_PARTIAL_BLOCKS
#define INTERSECT_SLOW_PARTIAL_BLOCKS

#ifdef INTERSECT_PARTIAL_BLOCKS
bool IntersectAABox(vec3 rayPos, vec3 invRayDir, vec3 boxRadii, out float dist, inout vec3 hitNormal) {
	// This one will not intersect backfaces, or in other words this will not intersect the box from inside it.
	vec3 scaledRayPos = rayPos * invRayDir;
	vec3 minDists = -boxRadii * abs(invRayDir) - scaledRayPos; // distance to each frontfacing plane
	vec3 maxDists =  boxRadii * abs(invRayDir) - scaledRayPos; // distance to each backfacing plane

	float maxOfMin = MaxOf(minDists); // distance to the frontfaces
	float minOfMax = MinOf(maxDists); // distance to the backfaces

	dist = maxOfMin; // Distance to intersection (if the box was intersected)

	// Deterimine normal
	if (dist == minDists.x) {
		hitNormal = vec3(invRayDir.x < 0.0 ? 1.0 : -1.0, 0.0, 0.0);
	} else if (dist == minDists.y) {
		hitNormal = vec3(0.0, invRayDir.y < 0.0 ? 1.0 : -1.0, 0.0);
	} else {
		hitNormal = vec3(0.0, 0.0, invRayDir.z < 0.0 ? 1.0 : -1.0);
	}

	// Check for intersection & return whether or not we intersected
	return dist > 0.0 && maxOfMin <= minOfMax;
}
bool IntersectAABox(vec3 rayPos, vec3 invRayDir, vec3 minBounds, vec3 maxBounds, out float dist, inout vec3 hitNormal) {
	// This will not intersect backfaces, or in other words this will not intersect the box from inside it.
	// You should use the version that rakes in radii instead of min & max bounds if you can, as it will be faster.
	vec3 minBoundsDist = (minBounds - rayPos) * invRayDir;
	vec3 maxBoundsDist = (maxBounds - rayPos) * invRayDir;

	vec3 minDists = min(minBoundsDist, maxBoundsDist);
	vec3 maxDists = max(minBoundsDist, maxBoundsDist);

	float maxOfMin = MaxOf(minDists); // distance to frontfaces
	float minOfMax = MinOf(maxDists); // distance to backfaces

	dist = maxOfMin; // Distance to intersection (if the box was intersected)

	// Deterimine normal
	if (dist == minDists.x) {
		hitNormal = vec3(invRayDir.x < 0.0 ? 1.0 : -1.0, 0.0, 0.0);
	} else if (dist == minDists.y) {
		hitNormal = vec3(0.0, invRayDir.y < 0.0 ? 1.0 : -1.0, 0.0);
	} else {
		hitNormal = vec3(0.0, 0.0, invRayDir.z < 0.0 ? 1.0 : -1.0);
	}

	// Check for intersection & return whether or not we intersected
	return dist > 0.0 && maxOfMin <= minOfMax;
}

#ifdef INTERSECT_SLOW_PARTIAL_BLOCKS
bool IntersectAnvil(vec3 relativeOrigin, vec3 direction, vec3 inverseDirection, bool northSouth, out float hitDist, inout vec3 hitNormal) {
	// Base
	bool intersected = IntersectAABox(relativeOrigin, inverseDirection, vec3(0.125,0,0.125), vec3(0.875,0.25,0.875), hitDist, hitNormal);

	// temp holders
	float tDist;
	vec3 tNormal;

	vec3 minBounds = northSouth ? vec3(0.25, 0.25,   0.1875) : vec3(0.1875, 0.25,   0.25);
	vec3 maxBounds = northSouth ? vec3(0.75, 0.3125, 0.8125) : vec3(0.8125, 0.3125, 0.75);
	if (IntersectAABox(relativeOrigin, inverseDirection, minBounds, maxBounds, tDist, tNormal)) {
		if (!intersected || tDist < hitDist) {
			hitDist = tDist;
			hitNormal = tNormal;
			intersected = true;
		}
	}

	minBounds = northSouth ? vec3(0.375, 0.3125, 0.25) : vec3(0.25, 0.3125, 0.375);
	maxBounds = northSouth ? vec3(0.625, 0.625,  0.75) : vec3(0.75, 0.625,  0.625);
	if (IntersectAABox(relativeOrigin, inverseDirection, minBounds, maxBounds, tDist, tNormal)) {
		if (!intersected || tDist < hitDist) {
			hitDist = tDist;
			hitNormal = tNormal;
			intersected = true;
		}
	}

	minBounds = northSouth ? vec3(0.1875, 0.625, 0.0) : vec3(0.0, 0.625, 0.1875);
	maxBounds = northSouth ? vec3(0.8125, 1.0,   1.0) : vec3(1.0, 1.0,   0.8125);
	if (IntersectAABox(relativeOrigin, inverseDirection, minBounds, maxBounds, tDist, tNormal)) {
		if (!intersected || tDist < hitDist) {
			hitDist = tDist;
			hitNormal = tNormal;
			intersected = true;
		}
	}

	return intersected;
}
bool IntersectFence(vec3 relativeOrigin, vec3 direction, vec3 inverseDirection, int shapeMasks, out float hitDist, inout vec3 hitNormal) {
	// Post
	bool intersected = IntersectAABox(relativeOrigin, inverseDirection, vec3(0.375,0,0.375), vec3(0.625,1,0.625), hitDist, hitNormal);

	// Check connected directions
	bool east  = (shapeMasks & 0x8) == 0x8; // 1000
	bool west  = (shapeMasks & 0x4) == 0x4; // 0100
	bool south = (shapeMasks & 0x2) == 0x2; // 0010
	bool north = (shapeMasks & 0x1) == 0x1; // 0001

	// temp holders
	float tDist;
	vec3 tNormal;

	// East-west connection
	if (east || west) {
		// Upper bar
		vec3 xUpperMin = vec3(west ? 0.0 : 0.5, 0.75,   0.4375);
		vec3 xUpperMax = vec3(east ? 1.0 : 0.5, 0.9375, 0.5625);
		if (IntersectAABox(relativeOrigin, inverseDirection, xUpperMin, xUpperMax, tDist, tNormal)) {
			if (!intersected || tDist < hitDist) {
				hitDist = tDist;
				hitNormal = tNormal;
			}
			intersected = true;
		}

		// Lower bar
		vec3 xLowerMin = vec3(west ? 0.0 : 0.5, 0.375,  0.4375);
		vec3 xLowerMax = vec3(east ? 1.0 : 0.5, 0.5625, 0.5625);
		if (IntersectAABox(relativeOrigin, inverseDirection, xLowerMin, xLowerMax, tDist, tNormal)) {
			if (!intersected || tDist < hitDist) {
				hitDist = tDist;
				hitNormal = tNormal;
			}
			intersected = true;
		}
	}

	// North-south connection
	if (south || north) {
		// Upper bar
		vec3 zUpperMin = vec3(0.4375, 0.75,   north ? 0.0 : 0.5);
		vec3 zUpperMax = vec3(0.5625, 0.9375, south ? 1.0 : 0.5);
		if (IntersectAABox(relativeOrigin, inverseDirection, zUpperMin, zUpperMax, tDist, tNormal)) {
			if (!intersected || tDist < hitDist) {
				hitDist = tDist;
				hitNormal = tNormal;
				intersected = true;
			}
		}

		// Lower bar
		vec3 zLowerMin = vec3(0.4375, 0.375,  north ? 0.0 : 0.5);
		vec3 zLowerMax = vec3(0.5625, 0.5625, south ? 1.0 : 0.5);
		if (IntersectAABox(relativeOrigin, inverseDirection, zLowerMin, zLowerMax, tDist, tNormal)) {
			if (!intersected || tDist < hitDist) {
				hitDist = tDist;
				hitNormal = tNormal;
				intersected = true;
			}
		}
	}

	return intersected;
}
bool IntersectWall(vec3 relativeOrigin, vec3 direction, vec3 inverseDirection, int shapeMasks, out float hitDist, inout vec3 hitNormal) {
	bool intersected = false;

	// Check connected directions
	bool up    = (shapeMasks & 0x10) == 0x10; // 10000
	bool east  = (shapeMasks & 0x08) == 0x08; // 01000
	bool west  = (shapeMasks & 0x04) == 0x04; // 00100
	bool south = (shapeMasks & 0x02) == 0x02; // 00010
	bool north = (shapeMasks & 0x01) == 0x01; // 00001

	// Post
	if (!((east && west && !south && !north) || (!east && !west && south && north)) || up) {
		intersected = IntersectAABox(relativeOrigin, inverseDirection, vec3(0.25, 0, 0.25), vec3(0.75, 1, 0.75), hitDist, hitNormal);
	}

	// temp holders
	float tDist;
	vec3 tNormal;

	// East-west connection
	if (east || west) {
		vec3 minBounds = vec3(west ? 0.0 : 0.5, 0.0,   0.3125);
		vec3 maxBounds = vec3(east ? 1.0 : 0.5, 0.875, 0.6875);
		if (IntersectAABox(relativeOrigin, inverseDirection, minBounds, maxBounds, tDist, tNormal)) {
			if (!intersected || tDist < hitDist) {
				hitDist = tDist;
				hitNormal = tNormal;
				intersected = true;
			}
		}
	}

	// North-south connection
	if (south || north) {
		vec3 minBounds = vec3(0.3125, 0.0,   north ? 0.0 : 0.5);
		vec3 maxBounds = vec3(0.6875, 0.875, south ? 1.0 : 0.5);
		if (IntersectAABox(relativeOrigin, inverseDirection, minBounds, maxBounds, tDist, tNormal)) {
			if (!intersected || tDist < hitDist) {
				hitDist = tDist;
				hitNormal = tNormal;
				intersected = true;
			}
		}
	}

	return intersected;
}
bool IntersectCrop(vec3 relativeOrigin, vec3 direction, vec3 inverseDirection, out float hitDist, inout vec3 hitNormal) {
	float intersectDist;
	bool intersected = IntersectAABox(relativeOrigin, inverseDirection, vec3(0,-0.0625,0.25), vec3(1, 0.9375, 0.251), hitDist, hitNormal);

	float dist; vec3 normal;
	if (IntersectAABox(relativeOrigin, inverseDirection, vec3(0,-0.0625,0.75), vec3(1, 0.9375, 0.751), dist, normal)) {
		if (!intersected || dist < hitDist) {
			hitDist = dist;
			hitNormal = normal;
			intersected = true;
		}
	}

	if (IntersectAABox(relativeOrigin, inverseDirection, vec3(0.75,-0.0625,0), vec3(0.751, 0.9375, 1), dist, normal)) {
		if (!intersected || dist < hitDist) {
			hitDist = dist;
			hitNormal = normal;
			intersected = true;
		}
	}

	if (IntersectAABox(relativeOrigin, inverseDirection, vec3(0.75,-0.0625,0), vec3(0.751, 0.9375, 1), dist, normal)) {
		if (!intersected || dist < hitDist) {
			hitDist = dist;
			hitNormal = normal;
			intersected = true;
		}
	}

	return intersected;
}

bool IntersectWallTorch(vec3 relativeOrigin, vec3 direction, int face, out float hitDist, inout vec3 hitNormal) {
	const float angle = radians(22.5);
	vec3 rotationOrigin = vec3(face == 0 ?  0.0 : face == 1 ? 1.0 : 0.5, 3.5/16.0, face == 2 ? 0.0 : face == 3 ?  1.0 : 0.0);
	vec3 rotationVector = vec3(face == 2 ? -1.0 : face == 3 ? 1.0 : 0.0, 0.0,      face == 0 ? 1.0 : face == 1 ? -1.0 : 0.0);

	vec3 rotatedOrigin    = Rotate(relativeOrigin - rotationOrigin, rotationVector, angle) + rotationOrigin;
	vec3 rotatedDirection = Rotate(direction, rotationVector, angle);

	vec3 minBounds = vec3(face == 0 ? -0.0625 : face == 1 ? 0.9375 : 0.4375,         3.5/16.0, face == 2 ? -0.0625 : face == 3 ? 0.9375 : 0.4375);
	vec3 maxBounds = vec3(face == 0 ?  0.0625 : face == 1 ? 1.0625 : 0.5625, 0.625 + 3.5/16.0, face == 2 ?  0.0625 : face == 3 ? 1.0625 : 0.5625);

	if (IntersectAABox(rotatedOrigin, 1.0 / rotatedDirection, minBounds, maxBounds, hitDist, hitNormal)) {
		hitNormal = Rotate(hitNormal, rotationVector, -angle);

		return true;
	}

	return false;
}
#endif

bool IntersectVoxel(vec3 relativeOrigin, vec3 direction, int id, inout float hitDist, inout vec3 hitNormal) {
	if (id > 1) {
		vec3 inverseDirection = 1.0 / direction;

		if (id <= 16) {
			vec3 minBounds, maxBounds;
			minBounds = vec3(0);
			maxBounds = vec3(1, id * 0.0625 - 0.0625, 1);

			return IntersectAABox(relativeOrigin, inverseDirection, minBounds, maxBounds, hitDist, hitNormal);
		} else if (id <= 96) {
			vec3[97 - 17][2] bounds1 = vec3[97 - 17][2](
				// Fluids
				// This is currently just so they don't look completely awful
				vec3[2](vec3(0), vec3(1, 8.0/9.0, 1)),
				vec3[2](vec3(0), vec3(1, 8.0/9.0, 1)),

				// Slabs (Top)
				vec3[2](vec3(0, 0.5, 0), vec3(1)),
				// Torches (not on walls)
				vec3[2](vec3(0.4375, 0.0, 0.4375), vec3(0.5625, 0.625, 0.5625)),

				// Pressure Plates (not pressed)
				vec3[2](vec3(0.0625, 0, 0.0625), vec3(0.9375, 0.0625, 0.9375)),
				// Pressure Plates (pressed)
				vec3[2](vec3(0.0625, 0, 0.0625), vec3(0.9375, 0.03125, 0.9375)),

				// Doors & Trapdoors
				vec3[2](vec3(0), vec3(0.1875, 1, 1)),
				vec3[2](vec3(0.8125, 0, 0), vec3(1)),
				vec3[2](vec3(0), vec3(1, 1, 0.1875)),
				vec3[2](vec3(0, 0, 0.8125), vec3(1)),
				vec3[2](vec3(0, 0.8125, 0), vec3(1)),

				// Buttons
				vec3[2](vec3(0.0000, 0.375, 0.3125), vec3(0.1250, 0.625, 0.6875)),
				vec3[2](vec3(0.8750, 0.375, 0.3125), vec3(1.0000, 0.625, 0.6875)),
				vec3[2](vec3(0.3125, 0.375, 0.0000), vec3(0.6875, 0.625, 0.1250)),
				vec3[2](vec3(0.3250, 0.375, 0.8750), vec3(0.6875, 0.625, 1.0000)),
				vec3[2](vec3(0.3750, 0.000, 0.3125), vec3(0.6250, 0.125, 0.6875)),
				vec3[2](vec3(0.3125, 0.000, 0.3750), vec3(0.6875, 0.125, 0.6250)),
				vec3[2](vec3(0.3750, 0.875, 0.3125), vec3(0.6250, 1.000, 0.6875)),
				vec3[2](vec3(0.3125, 0.875, 0.3750), vec3(0.6875, 1.000, 0.6250)),

				vec3[2](vec3(0.0000, 0.3750, 0.3125), vec3(0.0625, 0.6250, 0.6875)),
				vec3[2](vec3(0.9375, 0.3750, 0.3125), vec3(1.0000, 0.6250, 0.6875)),
				vec3[2](vec3(0.3125, 0.3750, 0.0000), vec3(0.6875, 0.6250, 0.0625)),
				vec3[2](vec3(0.3125, 0.3750, 0.9375), vec3(0.6875, 0.6250, 1.0000)),
				vec3[2](vec3(0.3750, 0.0000, 0.3125), vec3(0.6250, 0.0625, 0.6875)),
				vec3[2](vec3(0.3125, 0.0000, 0.3750), vec3(0.6875, 0.0625, 0.6250)),
				vec3[2](vec3(0.3750, 0.9375, 0.3125), vec3(0.6250, 1.0000, 0.6875)),
				vec3[2](vec3(0.3125, 0.9375, 0.3750), vec3(0.6875, 1.0000, 0.6250)),

				// Cactus
				vec3[2](vec3(0.0625, 0, 0.0625), vec3(0.9375, 1, 0.9375)),

				// Nether portal
				vec3[2](vec3(0, 0, 0.375), vec3(1, 1, 0.625)),
				vec3[2](vec3(0.375, 0, 0), vec3(0.625, 1, 1)),

				// Ladders, some states of vines
				vec3[2](vec3(0.05, 0, 0), vec3(0.05, 1, 1)), // east
				vec3[2](vec3(0.95, 0, 0), vec3(0.95, 1, 1)), // west
				vec3[2](vec3(0, 0, 0.05), vec3(1, 1, 0.05)), // south
				vec3[2](vec3(0, 0, 0.95), vec3(1, 1, 0.95)), // north

				// Glass panes, first box (also iron bars until i get around to giving them their own model)
				vec3[2](vec3(0.4375, 0, 0.4375), vec3(0.5625, 1, 0.5625)), // none
				vec3[2](vec3(0.4375, 0, 0     ), vec3(0.5625, 1, 0.5   )), // north
				vec3[2](vec3(0.4375, 0, 0.5   ), vec3(0.5625, 1, 1     )), // south
				vec3[2](vec3(0,      0, 0.4375), vec3(0.5,    1, 0.5625)), // west
				vec3[2](vec3(0.5,    0, 0.4375), vec3(1,      1, 0.5625)), // east
				vec3[2](vec3(0.4375, 0, 0     ), vec3(0.5625, 1, 1     )), // north-south
				vec3[2](vec3(0,      0, 0.4375), vec3(1,      1, 0.5625)), // east-west

				// End rods
				vec3[2](vec3(0.0625, 0.4375, 0.4375), vec3(1,      0.5625, 0.5625)),
				vec3[2](vec3(0,      0.4375, 0.4375), vec3(0.9375, 0.5625, 0.5625)),
				vec3[2](vec3(0.4375, 0.0625, 0.4375), vec3(0.5625, 1,      0.5625)),
				vec3[2](vec3(0.4375, 0,      0.4375), vec3(0.5625, 0.9375, 0.5625)),
				vec3[2](vec3(0.4375, 0.4375, 0.0625), vec3(0.5625, 0.5625, 1     )),
				vec3[2](vec3(0.4375, 0.4375, 0     ), vec3(0.5625, 0.5625, 0.9375)),

				// Stairs (straight)
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),

				// Stairs (outer corner)
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),

				// Glass panes with second box (also iron bars until i get around to giving them their own model)
				vec3[2](vec3(0,      0, 0.4375), vec3(0.5625, 1, 0.5625)), // north-west
				vec3[2](vec3(0,      0, 0.4375), vec3(0.5625, 1, 0.5625)), // south-west
				vec3[2](vec3(0.4375, 0, 0.4375), vec3(1,      1, 0.5625)), // north-east
				vec3[2](vec3(0.4375, 0, 0.4375), vec3(1,      1, 0.5625)), // south-east

				vec3[2](vec3(0,      0, 0.4375), vec3(0.5625, 1, 0.5625)), // north, south, west
				vec3[2](vec3(0.4375, 0, 0.4375), vec3(1,      1, 0.5625)), // north, south, east
				vec3[2](vec3(0,      0, 0.4375), vec3(1,      1, 0.5625)), // north, east, west
				vec3[2](vec3(0,      0, 0.4375), vec3(1,      1, 0.5625)), // south, east, west

				vec3[2](vec3(0,      0, 0.4375), vec3(1,      1, 0.5625)), // cross

				// Stairs (inner corner)
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     )),
				vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      1     ))
			);

			bool intersected = IntersectAABox(relativeOrigin, inverseDirection, bounds1[id - 17][0], bounds1[id - 17][1], hitDist, hitNormal);

			if (58 <= id) {
				vec3[97 - 58][2] bounds2 = vec3[97 - 58][2](
					// End rods
					vec3[2](vec3(0,      0.375,  0.375 ), vec3(0.0625, 0.625,  0.625 )),
					vec3[2](vec3(0.9375, 0.375,  0.375 ), vec3(1,      0.625,  0.625 )),
					vec3[2](vec3(0.375,  0,      0.375 ), vec3(0.625,  0.0625, 0.625 )),
					vec3[2](vec3(0.375,  0.9375, 0.375 ), vec3(0.625,  1,      0.625 )),
					vec3[2](vec3(0.375,  0.375,  0     ), vec3(0.625,  0.625,  0.0625)),
					vec3[2](vec3(0.375,  0.375,  0.9375), vec3(0.625,  0.625,  1     )),

					// Stairs (straight)
					vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      0.5   )),
					vec3[2](vec3(0,      0.5,    0.5   ), vec3(1,      1,      1     )),
					vec3[2](vec3(0.5,    0.5,    0     ), vec3(1,      1,      1     )),
					vec3[2](vec3(0,      0.5,    0     ), vec3(0.5,    1,      1     )),
					vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    0.5   )),
					vec3[2](vec3(0,      0,      0.5   ), vec3(1,      0.5,    1     )),
					vec3[2](vec3(0.5,    0,      0     ), vec3(1,      0.5,    1     )),
					vec3[2](vec3(0,      0,      0     ), vec3(0.5,    0.5,    1     )),

					// Stairs (outer corner)
					vec3[2](vec3(0.5,    0.5,    0     ), vec3(1,      1,      0.5   )),
					vec3[2](vec3(0.5,    0.5,    0.5   ), vec3(1,      1,      1     )),
					vec3[2](vec3(0,      0.5,    0     ), vec3(0.5,    1,      0.5   )),
					vec3[2](vec3(0,      0.5,    0.5   ), vec3(0.5,    1,      1     )),
					vec3[2](vec3(0.5,    0,      0     ), vec3(1,      0.5,    0.5   )),
					vec3[2](vec3(0.5,    0,      0.5   ), vec3(1,      0.5,    1     )),
					vec3[2](vec3(0,      0,      0     ), vec3(0.5,    0.5,    0.5   )),
					vec3[2](vec3(0,      0,      0.5   ), vec3(0.5,    0.5,    1     )),

					// Glass panes with second box (also iron bars until i get around to giving them their own model)
					vec3[2](vec3(0.4375, 0, 0     ), vec3(0.5625, 1, 0.5625)), // north-west
					vec3[2](vec3(0.4375, 0, 0.4375), vec3(0.5625, 1, 1     )), // south-west
					vec3[2](vec3(0.4375, 0, 0     ), vec3(0.5625, 1, 0.5625)), // north-east
					vec3[2](vec3(0.4375, 0, 0.4375), vec3(0.5625, 1, 1     )), // south-east

					vec3[2](vec3(0.4375, 0, 0     ), vec3(0.5625, 1, 1     )), // north, south, west
					vec3[2](vec3(0.4375, 0, 0     ), vec3(0.5625, 1, 1     )), // north, south, east
					vec3[2](vec3(0.4375, 0, 0     ), vec3(0.5625, 1, 0.5625)), // north, east, west
					vec3[2](vec3(0.4375, 0, 0.4375), vec3(0.5625, 1, 1     )), // south, east, west

					vec3[2](vec3(0.4375, 0, 0     ), vec3(0.5625, 1, 1     )), // cross

					// Stairs (inner corner)
					vec3[2](vec3(0,      0.5,    0     ), vec3(1,      1,      0.5   )),
					vec3[2](vec3(0,      0.5,    0.5   ), vec3(1,      1,      1     )),
					vec3[2](vec3(0,      0.5,    0     ), vec3(0.5,    1,      1     )),
					vec3[2](vec3(0,      0.5,    0     ), vec3(0.5,    1,      1     )),
					vec3[2](vec3(0,      0,      0     ), vec3(1,      0.5,    0.5   )),
					vec3[2](vec3(0,      0,      0.5   ), vec3(1,      0.5,    1     )),
					vec3[2](vec3(0,      0,      0     ), vec3(0.5,    0.5,    1     )),
					vec3[2](vec3(0,      0,      0     ), vec3(0.5,    0.5,    1     ))
				);

				float tDist;
				vec3 tNormal;
				if (IntersectAABox(relativeOrigin, inverseDirection, bounds2[id - 58][0], bounds2[id - 58][1], tDist, tNormal)) {
					if (!intersected || tDist < hitDist) {
						hitDist = tDist;
						hitNormal = tNormal;
						intersected = true;
					}
				}
			}

			if (89 <= id) {
				vec3[97 - 89][2] bounds3 = vec3[97 - 89][2](
					// Stairs (inner corner)
					vec3[2](vec3(0.5,    0.5,    0.5   ), vec3(1,      1,      1     )),
					vec3[2](vec3(0.5,    0.5,    0     ), vec3(1,      1,      0.5   )),
					vec3[2](vec3(0.5,    0.5,    0     ), vec3(1,      1,      0.5   )),
					vec3[2](vec3(0.5,    0.5,    0.5   ), vec3(1,      1,      1     )),
					vec3[2](vec3(0.5,    0,      0.5   ), vec3(1,      0.5,    1     )),
					vec3[2](vec3(0.5,    0,      0     ), vec3(1,      0.5,    0.5   )),
					vec3[2](vec3(0.5,    0,      0     ), vec3(1,      0.5,    0.5   )),
					vec3[2](vec3(0.5,    0,      0.5   ), vec3(1,      0.5,    1     ))
				);

				float tDist;
				vec3 tNormal;
				if (IntersectAABox(relativeOrigin, inverseDirection, bounds3[id - 89][0], bounds3[id - 89][1], tDist, tNormal)) {
					if (!intersected || tDist < hitDist) {
						hitDist = tDist;
						hitNormal = tNormal;
						intersected = true;
					}
				}
			}

			return intersected;
		}

		#ifdef INTERSECT_SLOW_PARTIAL_BLOCKS
		else if (id <= 98) { // Anvils
			return IntersectAnvil(relativeOrigin, direction, inverseDirection, id == 98, hitDist, hitNormal);
		} else if (id <= 114) { // Fences
			return IntersectFence(relativeOrigin, direction, inverseDirection, id - 99, hitDist, hitNormal);
		} else if (id <= 146) { // Walls
			return IntersectWall(relativeOrigin, direction, inverseDirection, id - 115, hitDist, hitNormal);
		} else if (id <= 150) { // Wall torches
			return IntersectWallTorch(relativeOrigin, direction, id - 147, hitDist, hitNormal);
		}
		#endif
	}

	// IDs that aren't yet considered are always intersected
	return true;
}
#endif

bool FindPotentialHit(
	vec3 pos, vec3 dir,
	vec3 deltaT,
	ivec3 deltaSign,
	ivec3 positiveDir,

	inout ivec3 idx, inout int level,
	inout vec3 steps, inout int iteration,

	out float minT, out bvec3 isMinComp
) {
	const int maxLevel = OCTREE_LEVELS - 1;

	bool potentialHit = false;
	do {
		// Find distances to next potential voxels
		ivec3 plane = ((idx >> level) + positiveDir) << level;
		vec3 planeT = abs(plane - pos) * deltaT;

		minT = MinOf(planeT);
		#if RAYTRACE_MAX_T > 0
		// Check that we're still within the raytrace range
		if (minT > RAYTRACE_MAX_T) { break; }
		#endif
		isMinComp = bvec3(planeT.x == minT, planeT.y == minT, planeT.z == minT);

		ivec3 prevIdx = idx;
		// Adding 0.5*isMinComp here is needed to avoid incorrect rounding
		// Mathemathically anything >= 0 and < 1 would be valid so 0.5 is probably safest
		ivec3 stepsToNext = ivec3(minT * abs(dir) - steps + 0.5 * vec3(isMinComp));
		steps += stepsToNext;
		idx += stepsToNext * deltaSign;

		// Check that we haven't stepped beyond the bounds
		if (OutsideVoxelmap(idx)) { break; }

		// Step up a level if we've moved to another "node" in the higher level
		int levelAbove = level + 1;
		bool shouldStepUp = (prevIdx >> levelAbove) != (idx >> levelAbove);
		level = min(shouldStepUp ? levelAbove : level, maxLevel);

		// Step down until either at lowest level or an empty node is found
		bool occupied = VoxelIsOccupied(idx, level);
		while (occupied && level > 0) {
			occupied = VoxelIsOccupied(idx, --level);
		}

		potentialHit = occupied && level == 0;
	} while (++iteration < 4096 && !potentialHit);

	return potentialHit;
}

bool RaytraceVoxels(vec3 pos, ivec3 idx, vec3 dir) {
	// In theory, using ints for idx should be faster on Volta & Turing, as well as AMD.

	// Early-exit when outside voxelized volume
	if (OutsideVoxelmap(idx)) { return false; }

	#ifdef INTERSECT_PARTIAL_BLOCKS
		// Check for intersection with starting voxel
		// TODO: It might be possible to optimize this
		float voxel0Alpha = texture(shadowcolor0, CalculateVoxelmapCoord(idx)).a;
		if (voxel0Alpha < (254.5 - OCTREE_LEVELS) / 255.0) {
			float hitDist; vec3 hitNormal;

			if (IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - voxel0Alpha * 255.0), hitDist, hitNormal)) {
				return true;
			}
		}
	#endif

	#if OCTREE_LEVELS > 1
	ivec3 deltaSign = ivec3(sign(dir));
	if (deltaSign.x == 0 && deltaSign.y == 0 && deltaSign.z == 0) { return false; }
	ivec3 positiveDir = Max0(deltaSign);
	ivec3 negativeDir = min(deltaSign, 0);

	vec3 deltaT = 1.0 / abs(dir);

	const int maxLevel = OCTREE_LEVELS - 1;
	int level = 0;
	while (level++ < maxLevel && !VoxelIsOccupied(idx, level)); --level;

	// there's probably a better name for this
	vec3 steps = (idx - pos) * deltaSign + positiveDir - 1;

	bool hit = false;
	int i = 0;
	do {
		float minT; bvec3 isMinComp;
		bool potentialHit = FindPotentialHit(
			pos, dir,
			deltaT, deltaSign, positiveDir,
			idx, level,
			steps, i,
			minT, isMinComp
		);

		// If a potential hit was found, try to intersect
		if (potentialHit) {
			#ifdef INTERSECT_PARTIAL_BLOCKS
			float voxel0Alpha = texture(shadowcolor0, CalculateVoxelmapCoord(idx)).a;
			float hitDist; vec3 hitNormal;
			#ifdef ALT_STEPPING_ALGO
			hit = IntersectVoxel(opos - idx, dir, int((255.5 - OCTREE_LEVELS) - voxel0Alpha * 255.0), hitDist, hitNormal);
			#else
			hit = IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - voxel0Alpha * 255.0), hitDist, hitNormal);
			#endif
			#else
			hit = true;
			#endif
		} else { // If no potential hit was found, break as we didn't find anything.
			break;
		}
	} while (++i < 4096 && !hit);
	#else
	vec3 nextT;
	vec3 deltaDist = 1.0 / abs(dir);
	ivec3 deltaSign;
	for (int axis = 0; axis < 3; ++axis) {
		if (dir[axis] < 0.0) {
			deltaSign[axis] = -1;
			nextT[axis] = (pos[axis] - idx[axis]) * deltaDist[axis];
		} else if (dir[axis] > 0.0) {
			deltaSign[axis] = 1;
			nextT[axis] = (idx[axis] - pos[axis]) * deltaDist[axis] + deltaDist[axis];
		} else {
			deltaSign[axis] = 0;
			nextT[axis] = uintBitsToFloat(0x7f800000u);// = infinity
		}
	}

	if (deltaSign.x == 0 && deltaSign.y == 0 && deltaSign.z == 0) { return false; }

	bool hit = false;
	do {
		float minComp = MinOf(nextT);
		if (nextT.x == minComp) {
			idx.x   += deltaSign.x;
			nextT.x += deltaDist.x;
		}
		if (nextT.y == minComp) {
			idx.y   += deltaSign.y;
			nextT.y += deltaDist.y;
		}
		if (nextT.z == minComp) {
			idx.z   += deltaSign.z;
			nextT.z += deltaDist.z;
		}

		#if RAYTRACE_MAX_T >= 0
		if (MinOf(nextT) > RAYTRACE_MAX_T) { break; }
		#endif
		if (OutsideVoxelmap(idx)) { break; }

		float voxel0Alpha = texture(shadowcolor0, CalculateVoxelmapCoord(idx)).a;
		#ifdef DEV_VIS
		++reads[0];
		#endif
		#ifdef INTERSECT_PARTIAL_BLOCKS
		if (voxel0Alpha < (255.5 - OCTREE_LEVELS) / 255.0) {
			float hitDist; vec3 hitNormal;
			hit = IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - voxel0Alpha * 255.0), hitDist, hitNormal);
		}
		#else
		hit = voxel0Alpha < (255.5 - OCTREE_LEVELS) / 255.0;
		#endif
	} while (!hit);
	#endif

	return hit;
}

bool RaytraceVoxels(
	vec3 pos, ivec3 idx, vec3 dir,
	out vec3 hitPos, out ivec3 hitIdx,
	out vec2 voxelmapCoord, out vec3 hitNormal
) {
	// In theory, using ints for idx should be faster on Volta & Turing, as well as AMD.

	// Early-exit when outside voxelized volume
	if (OutsideVoxelmap(idx)) { return false; }

	#ifdef INTERSECT_PARTIAL_BLOCKS
		// Check for intersection with starting voxel
		// TODO: It might be possible to optimize this
		voxelmapCoord = CalculateVoxelmapCoord(idx);
		float voxel0Alpha = texture(shadowcolor0, voxelmapCoord).a;
		if (voxel0Alpha < (254.5 - OCTREE_LEVELS) / 255.0) {
			float hitDist;

			if (IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - voxel0Alpha * 255.0), hitDist, hitNormal)) {
				hitPos = pos + dir * hitDist;
				hitIdx = idx;
				return true;
			}
		}
	#endif

	#if OCTREE_LEVELS > 1
	ivec3 deltaSign = ivec3(sign(dir));
	if (deltaSign.x == 0 && deltaSign.y == 0 && deltaSign.z == 0) { return false; }
	ivec3 positiveDir = Max0(deltaSign);
	ivec3 negativeDir = min(deltaSign, 0);

	vec3 deltaT = 1.0 / abs(dir);

	const int maxLevel = OCTREE_LEVELS - 1;
	int level = 0;
	while (level++ < maxLevel && !VoxelIsOccupied(idx, level)); --level;

	// there's probably a better name for this
	vec3 steps = (idx - pos) * deltaSign + positiveDir - 1;

	bool hit = false;
	int i = 0;
	do {
		float minT; bvec3 isMinComp;
		bool potentialHit = FindPotentialHit(
			pos, dir,
			deltaT, deltaSign, positiveDir,
			idx, level,
			steps, i,
			minT, isMinComp
		);

		// If a potential hit was found, try to intersect
		if (potentialHit) {
			hitIdx = idx;

			hitNormal = vec3(isMinComp) * -deltaSign;

			float hitDist = minT;
			#ifdef INTERSECT_PARTIAL_BLOCKS
			voxelmapCoord = CalculateVoxelmapCoord(idx);
			float voxel0Alpha = texture(shadowcolor0, voxelmapCoord).a;
			hit = IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - voxel0Alpha * 255.0), hitDist, hitNormal);
			#else
			hit = true;
			#endif

			hitPos = pos + hitDist * dir;
		} else {
			break;
		}
	} while (++i < 4096 && !hit);
	#else
	vec3 nextT;
	vec3 deltaDist = 1.0 / abs(dir);
	ivec3 deltaSign;
	for (int axis = 0; axis < 3; ++axis) {
		if (dir[axis] < 0.0) {
			deltaSign[axis] = -1;
			nextT[axis] = (pos[axis] - idx[axis]) * deltaDist[axis];
		} else if (dir[axis] > 0.0) {
			deltaSign[axis] = 1;
			nextT[axis] = (idx[axis] - pos[axis]) * deltaDist[axis] + deltaDist[axis];
		} else {
			deltaSign[axis] = 0;
			nextT[axis] = uintBitsToFloat(0x7f800000u);// = infinity
		}
	}

	if (deltaSign.x == 0 && deltaSign.y == 0 && deltaSign.z == 0) { return false; }

	bool hit = false;
	while (!hit) {
		float hitDist;

		float minComp = MinOf(nextT);
		if (nextT.x == minComp) {
			hitDist   = nextT.x;
			hitNormal = vec3(-deltaSign.x, 0.0, 0.0);

			idx.x   += deltaSign.x;
			nextT.x += deltaDist.x;
		}
		if (nextT.y == minComp) {
			hitDist   = nextT.y;
			hitNormal = vec3(0.0, -deltaSign.y, 0.0);

			idx.y   += deltaSign.y;
			nextT.y += deltaDist.y;
		}
		if (nextT.z == minComp) {
			hitDist   = nextT.z;
			hitNormal = vec3(0.0, 0.0, -deltaSign.z);

			idx.z   += deltaSign.z;
			nextT.z += deltaDist.z;
		}

		#if RAYTRACE_MAX_T >= 0
		if (MinOf(nextT) > RAYTRACE_MAX_T) { break; }
		#endif
		if (OutsideVoxelmap(idx)) { break; }

		voxelmapCoord = CalculateVoxelmapCoord(idx);
		float voxel0Alpha = texture(shadowcolor0, voxelmapCoord).a;
		if (voxel0Alpha < (255.5 - OCTREE_LEVELS) / 255.0) {
			#ifdef INTERSECT_PARTIAL_BLOCKS
				hit = IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - voxel0Alpha * 255.0), hitDist, hitNormal);
			#else
				hit = true;
			#endif
			hitPos = pos + hitDist * dir; hitIdx = idx;
		}
	}
	#endif

	return hit;
}

vec2 GetUv(vec3 hitPos, vec3 hitNormal, vec4[2] voxel, out mat3 tbnMatrix) {
	vec2 uv;
	if (abs(hitNormal.y) > abs(hitNormal.x) && abs(hitNormal.y) > abs(hitNormal.z)) {
		uv = vec2(fract(hitPos.x), fract(hitPos.z * sign(hitNormal.y)));
		tbnMatrix = mat3(
			vec3(1.0, 0.0, 0.0),
			vec3(0.0, 0.0, sign(hitNormal.y)),
			hitNormal
		);
	} else {
		uv = vec2(fract(hitPos.x * sign(hitNormal.z) - hitPos.z * sign(hitNormal.x)), fract(-hitPos.y));
		tbnMatrix = mat3(
			vec3(sign(hitNormal.z), 0.0, -sign(hitNormal.x)),
			vec3(0.0, -1.0, 0.0),
			hitNormal
		);
	}

	uv += 256.0 * 15.0 * UnpackUnorm2x4(voxel[1].y) + 255.0 * voxel[1].zw;
	uv *= exp2(voxel[1].x * 15.0) / textureSize(atlas, 0);

	return uv;
}
vec2 GetUv(vec3 hitPos, vec3 hitNormal, vec4[2] voxel) {
	vec2 uv;
	if (abs(hitNormal.y) > abs(hitNormal.x) && abs(hitNormal.y) > abs(hitNormal.z)) {
		uv = vec2(fract(hitPos.x), fract(hitPos.z * sign(hitNormal.y)));
	} else {
		uv = vec2(fract(hitPos.x * sign(hitNormal.z) - hitPos.z * sign(hitNormal.x)), fract(-hitPos.y));
	}

	uv += 256.0 * 15.0 * UnpackUnorm2x4(voxel[1].y) + 255.0 * voxel[1].zw;
	uv *= exp2(voxel[1].x * 15.0) / textureSize(atlas, 0);

	return uv;
}
bool AlphaTest(vec3 hitPos, vec4[2] voxel, vec3 hitNormal, out vec2 uv, out mat3 tbnMatrix) {
	uv = GetUv(hitPos, hitNormal, voxel, tbnMatrix);

	// TODO: Ray differentials to be able to safely use lod > 0?
	return textureLod(atlas, uv, 0).a > max(RandNextF(), 26.5 / 255.0);
	//return textureLod(atlas, uv, 0).a > RandNextF();
}
bool AlphaTest(vec3 hitPos, vec4[2] voxel, vec3 hitNormal, out vec2 uv) {
	uv = GetUv(hitPos, hitNormal, voxel);

	// TODO: Ray differentials to be able to safely use lod > 0?
	return textureLod(atlas, uv, 0).a > max(RandNextF(), 26.5 / 255.0);
	//return textureLod(atlas, uv, 0).a > RandNextF();
}

bool RaytraceVoxelsAlphatest(
	vec3 pos, ivec3 idx, vec3 dir,
	out vec3 hitPos, out ivec3 hitIdx, out vec3 hitNormal,
	out vec4[2] hitVoxel, out vec2 hitUv
) {
	// In theory, using ints for idx should be faster on Volta & Turing, as well as AMD.

	// Early-exit when outside voxelized volume
	if (OutsideVoxelmap(idx)) { return false; }

	#ifdef INTERSECT_PARTIAL_BLOCKS
		// Check for intersection with starting voxel
		// TODO: It might be possible to optimize this
		vec2 voxelmapCoord = CalculateVoxelmapCoord(idx);
		hitVoxel[0] = texture(shadowcolor0, voxelmapCoord);
		if (hitVoxel[0].a > 0.5 / 255.0 && hitVoxel[0].a < (254.5 - OCTREE_LEVELS) / 255.0) {
			float hitDist;

			if (IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - hitVoxel[0].a * 255.0), hitDist, hitNormal)) {
				hitPos = pos + dir * hitDist;
				hitVoxel[1] = texture(shadowcolor1, voxelmapCoord);

				if (AlphaTest(hitPos, hitVoxel, hitNormal, hitUv)) {
					hitIdx = idx;
					return true;
				}
			}
		}
	#endif

	#if OCTREE_LEVELS > 1
	ivec3 deltaSign = ivec3(sign(dir));
	if (deltaSign.x == 0 && deltaSign.y == 0 && deltaSign.z == 0) { return false; }
	ivec3 positiveDir = Max0(deltaSign);
	ivec3 negativeDir = min(deltaSign, 0);

	vec3 deltaT = 1.0 / abs(dir);

	const int maxLevel = OCTREE_LEVELS - 1;
	int level = 0;
	while (level++ < maxLevel && !VoxelIsOccupied(idx, level)); --level;

	// there's probably a better name for this
	vec3 steps = (idx - pos) * deltaSign + positiveDir - 1;

	bool hit = false;
	int i = 0;
	do {
		float minT; bvec3 isMinComp;
		bool potentialHit = FindPotentialHit(
			pos, dir,
			deltaT, deltaSign, positiveDir,
			idx, level,
			steps, i,
			minT, isMinComp
		);

		// If a potential hit was found, try to intersect
		if (potentialHit) {
			vec2 voxelmapCoord = CalculateVoxelmapCoord(idx);
			hitVoxel[0] = texture(shadowcolor0, voxelmapCoord);
			hitVoxel[1] = texture(shadowcolor1, voxelmapCoord);

			hitIdx = idx;

			float hitDist = minT;
			hitNormal = vec3(isMinComp) * -deltaSign;

			#ifdef INTERSECT_PARTIAL_BLOCKS
			hit = IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - hitVoxel[0].a * 255.0), hitDist, hitNormal);
			#else
			hit = true;
			#endif

			hitPos = pos + hitDist * dir;

			hit = hit && AlphaTest(hitPos, hitVoxel, hitNormal, hitUv);
		} else {
			break;
		}
	} while (++i < 4096 && !hit);
	#else
	vec3 nextT;
	vec3 deltaDist = 1.0 / abs(dir);
	ivec3 deltaSign;
	for (int axis = 0; axis < 3; ++axis) {
		if (dir[axis] < 0.0) {
			deltaSign[axis] = -1;
			nextT[axis] = (pos[axis] - idx[axis]) * deltaDist[axis];
		} else if (dir[axis] > 0.0) {
			deltaSign[axis] = 1;
			nextT[axis] = (idx[axis] - pos[axis]) * deltaDist[axis] + deltaDist[axis];
		} else {
			deltaSign[axis] = 0;
			nextT[axis] = uintBitsToFloat(0x7f800000u);// = infinity
		}
	}

	if (deltaSign.x == 0 && deltaSign.y == 0 && deltaSign.z == 0) { return false; }

	bool hit = false;
	do {
		float hitDist;

		float minComp = MinOf(nextT);
		if (nextT.x == minComp) {
			hitDist   = nextT.x;
			hitNormal = vec3(-deltaSign.x, 0.0, 0.0);

			idx.x   += deltaSign.x;
			nextT.x += deltaDist.x;
		}
		if (nextT.y == minComp) {
			hitDist   = nextT.y;
			hitNormal = vec3(0.0, -deltaSign.y, 0.0);

			idx.y   += deltaSign.y;
			nextT.y += deltaDist.y;
		}
		if (nextT.z == minComp) {
			hitDist   = nextT.z;
			hitNormal = vec3(0.0, 0.0, -deltaSign.z);

			idx.z   += deltaSign.z;
			nextT.z += deltaDist.z;
		}

		#if RAYTRACE_MAX_T >= 0
		if (MinOf(nextT) > RAYTRACE_MAX_T) { break; }
		#endif
		if (OutsideVoxelmap(idx)) { break; }

		vec2 voxelmapCoord = CalculateVoxelmapCoord(idx);
		hitVoxel[0] = texture(shadowcolor0, voxelmapCoord);
		if (hitVoxel[0].a < (255.5 - OCTREE_LEVELS) / 255.0) {
			hitVoxel[1] = texture(shadowcolor1, voxelmapCoord);

			#ifdef INTERSECT_PARTIAL_BLOCKS
			hit = IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - hitVoxel[0].a * 255.0), hitDist, hitNormal);
			#else
			hit = true;
			#endif

			hitPos = pos + hitDist * dir; hitIdx = idx;
			hit = hit && AlphaTest(hitPos, hitVoxel, hitNormal, hitUv);
		}
	} while (!hit);
	#endif

	return hit;
}
bool RaytraceVoxelsAlphatest(
	vec3 pos, ivec3 idx, vec3 dir,
	out vec3 hitPos, out ivec3 hitIdx, out vec3 hitNormal,
	out vec4[2] hitVoxel, out vec2 hitUv, out mat3 tbnMatrix
) {
	// In theory, using ints for idx should be faster on Volta & Turing, as well as AMD.

	// Early-exit when outside voxelized volume
	if (OutsideVoxelmap(idx)) { return false; }

	#ifdef INTERSECT_PARTIAL_BLOCKS
		// Check for intersection with starting voxel
		// TODO: It might be possible to optimize this
		vec2 voxelmapCoord = CalculateVoxelmapCoord(idx);
		hitVoxel[0] = texture(shadowcolor0, voxelmapCoord);
		if (hitVoxel[0].a > 0.5 / 255.0 && hitVoxel[0].a < (254.5 - OCTREE_LEVELS) / 255.0) {
			float hitDist;

			if (IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - hitVoxel[0].a * 255.0), hitDist, hitNormal)) {
				hitPos = pos + dir * hitDist;
				hitVoxel[1] = texture(shadowcolor1, voxelmapCoord);

				if (AlphaTest(hitPos, hitVoxel, hitNormal, hitUv, tbnMatrix)) {
					hitIdx = idx;
					return true;
				}
			}
		}
	#endif

	#if OCTREE_LEVELS > 1
	ivec3 deltaSign = ivec3(sign(dir));
	if (deltaSign.x == 0 && deltaSign.y == 0 && deltaSign.z == 0) { return false; }
	ivec3 positiveDir = Max0(deltaSign);
	ivec3 negativeDir = min(deltaSign, 0);

	vec3 deltaT = 1.0 / abs(dir);

	const int maxLevel = OCTREE_LEVELS - 1;
	int level = 0;
	while (level++ < maxLevel && !VoxelIsOccupied(idx, level)); --level;

	// there's probably a better name for this
	vec3 steps = (idx - pos) * deltaSign + positiveDir - 1;

	bool hit = false;
	int i = 0;
	do {
		float minT; bvec3 isMinComp;
		bool potentialHit = FindPotentialHit(
			pos, dir,
			deltaT, deltaSign, positiveDir,
			idx, level,
			steps, i,
			minT, isMinComp
		);

		// If a potential hit was found, try to intersect
		if (potentialHit) {
			vec2 voxelmapCoord = CalculateVoxelmapCoord(idx);
			hitVoxel[0] = texture(shadowcolor0, voxelmapCoord);
			hitVoxel[1] = texture(shadowcolor1, voxelmapCoord);

			hitIdx = idx;

			float hitDist = minT;
			hitNormal = vec3(isMinComp) * -deltaSign;

			#ifdef INTERSECT_PARTIAL_BLOCKS
			hit = IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - hitVoxel[0].a * 255.0), hitDist, hitNormal);
			#else
			hit = true;
			#endif

			hitPos = pos + hitDist * dir;

			hit = hit && AlphaTest(hitPos, hitVoxel, hitNormal, hitUv, tbnMatrix);
		} else {
			break;
		}
	} while (++i < 4096 && !hit);
	#else
	vec3 nextT;
	vec3 deltaDist = 1.0 / abs(dir);
	ivec3 deltaSign;
	for (int axis = 0; axis < 3; ++axis) {
		if (dir[axis] < 0.0) {
			deltaSign[axis] = -1;
			nextT[axis] = (pos[axis] - idx[axis]) * deltaDist[axis];
		} else if (dir[axis] > 0.0) {
			deltaSign[axis] = 1;
			nextT[axis] = (idx[axis] - pos[axis]) * deltaDist[axis] + deltaDist[axis];
		} else {
			deltaSign[axis] = 0;
			nextT[axis] = uintBitsToFloat(0x7f800000u);// = infinity
		}
	}

	if (deltaSign.x == 0 && deltaSign.y == 0 && deltaSign.z == 0) { return false; }

	bool hit = false;
	do {
		float hitDist;

		float minComp = MinOf(nextT);
		if (nextT.x == minComp) {
			hitDist   = nextT.x;
			hitNormal = vec3(-deltaSign.x, 0.0, 0.0);

			idx.x   += deltaSign.x;
			nextT.x += deltaDist.x;
		}
		if (nextT.y == minComp) {
			hitDist   = nextT.y;
			hitNormal = vec3(0.0, -deltaSign.y, 0.0);

			idx.y   += deltaSign.y;
			nextT.y += deltaDist.y;
		}
		if (nextT.z == minComp) {
			hitDist   = nextT.z;
			hitNormal = vec3(0.0, 0.0, -deltaSign.z);

			idx.z   += deltaSign.z;
			nextT.z += deltaDist.z;
		}

		#if RAYTRACE_MAX_T >= 0
		if (MinOf(nextT) > RAYTRACE_MAX_T) { break; }
		#endif
		if (OutsideVoxelmap(idx)) { break; }

		vec2 voxelmapCoord = CalculateVoxelmapCoord(idx);
		hitVoxel[0] = texture(shadowcolor0, voxelmapCoord);
		if (hitVoxel[0].a < (255.5 - OCTREE_LEVELS) / 255.0) {
			hitVoxel[1] = texture(shadowcolor1, voxelmapCoord);

			#ifdef INTERSECT_PARTIAL_BLOCKS
			hit = IntersectVoxel(pos - idx, dir, int((255.5 - OCTREE_LEVELS) - hitVoxel[0].a * 255.0), hitDist, hitNormal);
			#else
			hit = true;
			#endif

			hitPos = pos + hitDist * dir; hitIdx = idx;
			hit = hit && AlphaTest(hitPos, hitVoxel, hitNormal, hitUv, tbnMatrix);
		}
	} while (!hit);
	#endif

	return hit;
}

bool RaytraceVoxelsAlphatest(vec3 pos, ivec3 idx, vec3 dir) {
	vec3 hitPos, hitNormal; ivec3 hitIdx; vec4[2] hitVoxel; vec2 hitUv;
	return RaytraceVoxelsAlphatest(pos, idx, dir, hitPos, hitIdx, hitNormal, hitVoxel, hitUv);
}
