bool IntersectBlock(vec3 origin, ivec3 index, vec3 direction, out vec3 hitPos, inout vec3 hitNormal) {
	vec3 minBoundsDist = (      index - origin) / direction;
	vec3 maxBoundsDist = (1.0 + index - origin) / direction;

	vec3 positiveDir = step(0.0, direction);
	vec3 dists       = mix(maxBoundsDist, minBoundsDist, positiveDir);

	if (dists.x > dists.y) {
		if (dists.x > dists.z) {
			hitPos = dists.x * direction + origin;
			hitNormal = vec3(-positiveDir.x * 2.0 + 1.0, 0.0, 0.0);
		} else {
			hitPos = dists.z * direction + origin;
			hitNormal = vec3(0.0, 0.0, -positiveDir.z * 2.0 + 1.0);
		}
	} else if (dists.y > dists.z) {
		hitPos = dists.y * direction + origin;
		hitNormal = vec3(0.0, -positiveDir.y * 2.0 + 1.0, 0.0);
	} else {
		hitPos = dists.z * direction + origin;
		hitNormal = vec3(0.0, 0.0, -positiveDir.z * 2.0 + 1.0);
	}

	return true;
}

bool IntersectAABB(vec3 pos, vec3 dir, vec3 minBounds, vec3 maxBounds, out float dist, inout vec3 hitNormal) {
    if(any(lessThan(minBounds, vec3(0.0)))) return false; 
	vec3 minBoundsDist = (minBounds - pos) / dir;
	vec3 maxBoundsDist = (maxBounds - pos) / dir;

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

	//* Check if no intersection
	if(minDists.x > maxDists.y
	|| minDists.x > maxDists.z
	|| minDists.y > maxDists.x
	|| minDists.y > maxDists.z
	|| minDists.z > maxDists.x
	|| minDists.z > maxDists.y) {
		return false;
	}
    // if(any(greaterThan(minDists,maxDists.yxx))||any(greaterThan(minDists,maxDists.zzx)))

	// Intersect
	vec3 positiveDir = step(0.0, dir);
	vec3 dists = mix(maxBoundsDist, minBoundsDist, positiveDir);
	     dists = max(dists, 0.0);

	if (dists.x > dists.y) {
		if (dists.x > dists.z) {
			dist = dists.x;
			hitNormal = vec3(-positiveDir.x * 2.0 + 1.0, 0.0, 0.0);
		} else {
			dist = dists.z;
			hitNormal = vec3(0.0, 0.0, -positiveDir.z * 2.0 + 1.0);
		}
	} else if (dists.y > dists.z) {
		dist = dists.y;
		hitNormal = vec3(0.0, -positiveDir.y * 2.0 + 1.0, 0.0);
	} else {
		dist = dists.z;
		hitNormal = vec3(0.0, 0.0, -positiveDir.z * 2.0 + 1.0);
	}

	/*
    dist = max(max(dists.x,dists.y),dists.z);
    hitNormal = (-positiveDir * 2.0 + 1.0) * float(equal(dists,vec3(dist)));
	*/

	// Check to make sure we're not intersecting something in the wrong direction
	return dist > 0.0;
}

bool IntersectFence(vec3 origin, ivec3 index, vec3 direction, int id, out vec3 hitPos, inout vec3 hitNormal) {
	bool intersected;
	float intersectDist;
	vec3 intersectNormal;

	// Post
	intersected = IntersectAABB(origin - index, direction, vec3(0.375,0,0.375), vec3(0.625,1,0.625), intersectDist, intersectNormal);

	// Check connected directions
	int idEast  = ExtractVoxelId(ReadVoxel(index + ivec3(1,0,0)));
	int idWest  = ExtractVoxelId(ReadVoxel(index - ivec3(1,0,0)));
	int idSouth = ExtractVoxelId(ReadVoxel(index + ivec3(0,0,1)));
	int idNorth = ExtractVoxelId(ReadVoxel(index - ivec3(0,0,1)));

	bool east  = idEast  == 1 || idEast  == 2 || idEast == 9 || idEast == 10 || idEast == 12 || idEast == 22 || idEast == 23 || idEast == 25 || idEast == 50 || idEast  == (id == 64 ? 64 : 65) || idEast == 78 || (idEast >= 110 && idEast <= 129) || idEast == 200 || idEast == 201;
	bool west  = idWest  == 1 || idWest  == 2 || idWest == 8 || idWest == 11 || idWest == 13 || idWest == 21 || idWest == 24 || idWest == 26 || idWest == 51 || idWest  == (id == 64 ? 64 : 65) || idWest == 78 || (idWest >= 110 && idWest <= 129) || idWest == 200 || idWest == 201;
	bool south = idSouth  == 1 || idSouth  == 2 || idSouth == 6 || idSouth == 12 || idSouth == 13 || idSouth == 25 || idSouth == 26 || idSouth == 26 || idSouth == 52 || idSouth  == (id == 64 ? 64 : 65) || idSouth == 78 || (idSouth >= 110 && idSouth <= 129) || idSouth == 200 || idSouth == 201;
	bool north = idNorth  == 1 || idNorth  == 2 || idNorth == 7 || idNorth == 10 || idNorth == 11 || idNorth == 19 || idNorth == 23 || idNorth == 24 || idNorth == 53 || idNorth  == (id == 64 ? 64 : 65) || idNorth == 78 || (idNorth >= 110 && idNorth <= 129) || idNorth == 200 || idNorth == 201;

	// 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 (IntersectAABB(origin - index, direction, xUpperMin, xUpperMax, tDist, tNormal)) {
			if (!intersected || tDist < intersectDist) {
				intersectDist = tDist;
				intersectNormal = 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 (IntersectAABB(origin - index, direction, xLowerMin, xLowerMax, tDist, tNormal)) {
			if (!intersected || tDist < intersectDist) {
				intersectDist = tDist;
				intersectNormal = 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 (IntersectAABB(origin - index, direction, zUpperMin, zUpperMax, tDist, tNormal)) {
			if (!intersected || tDist < intersectDist) {
				intersectDist = tDist;
				intersectNormal = 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 (IntersectAABB(origin - index, direction, zLowerMin, zLowerMax, tDist, tNormal)) {
			if (!intersected || tDist < intersectDist) {
				intersectDist = tDist;
				intersectNormal = tNormal;
			}
			intersected = true;
		}
	}

	if (intersected) {
		hitPos = intersectDist * direction + origin;
		hitNormal = intersectNormal;
	}

	return intersected;
}


bool IntersectWall(vec3 origin, ivec3 index, vec3 direction, out vec3 hitPos, inout vec3 hitNormal) {
	bool intersected = false;
	float intersectDist;
	vec3 intersectNormal;

	// Check connected directions
	int idEast  = ExtractVoxelId(ReadVoxel(index + ivec3(1,0,0)));
	int idWest  = ExtractVoxelId(ReadVoxel(index - ivec3(1,0,0)));
	int idSouth = ExtractVoxelId(ReadVoxel(index + ivec3(0,0,1)));
	int idNorth = ExtractVoxelId(ReadVoxel(index - ivec3(0,0,1)));
	int idUp    = ExtractVoxelId(ReadVoxel(index + ivec3(0,1,0)));

	bool east  = idEast  == 1 || idEast  == 2 || idEast == 9 || idEast == 10 || idEast == 12 || idEast == 22 || idEast == 23 || idEast == 25 || idEast == 50 || idEast == 66 || idEast == 78 || (idEast >= 110 && idEast <= 129) || idEast == 200 || idEast == 201;
	bool west  = idWest  == 1 || idWest  == 2 || idWest == 8 || idWest == 11 || idWest == 13 || idWest == 21 || idWest == 24 || idWest == 26 || idWest == 51 || idWest == 66 || idWest == 78 || (idWest >= 110 && idWest <= 129) || idWest == 200 || idWest == 201;
	bool south = idSouth  == 1 || idSouth  == 2 || idSouth == 6 || idSouth == 12 || idSouth == 13 || idSouth == 25 || idSouth == 26 || idSouth == 26 || idSouth == 52 || idSouth == 66 || idSouth == 78 || (idSouth >= 110 && idSouth <= 129) || idSouth == 200 || idSouth == 201;
	bool north = idNorth  == 1 || idNorth  == 2 || idNorth == 7 || idNorth == 10 || idNorth == 11 || idNorth == 19 || idNorth == 23 || idNorth == 24 || idNorth == 53 || idNorth == 66 || idNorth == 78 || (idNorth >= 110 && idNorth <= 129) || idNorth == 200 || idNorth == 201;
	bool up    = idUp  == 1 || idUp  == 2 || (idUp >= 5 && idUp <= 17) || idUp  == 64 || idUp == 65 || idUp == 66 || idUp == 78 || (idUp >= 110 && idUp <= 129) || idUp == 200 || idUp == 201;

	// Post
	if (!((east && west && !south && !north) || (!east && !west && south && north)) || up) {
		intersected = IntersectAABB(origin - index, direction, vec3(0.25,0,0.25), vec3(0.75,1,0.75), intersectDist, intersectNormal);
	}

	// 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 (IntersectAABB(origin - index, direction, minBounds, maxBounds, tDist, tNormal)) {
			if (!intersected || tDist < intersectDist) {
				intersectDist = tDist;
				intersectNormal = 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 (IntersectAABB(origin - index, direction, minBounds, maxBounds, tDist, tNormal)) {
			if (!intersected || tDist < intersectDist) {
				intersectDist = tDist;
				intersectNormal = tNormal;
				intersected = true;
			}
		}
	}

	if (intersected) {
		hitPos = intersectDist * direction + origin;
		hitNormal = intersectNormal;
	}

	return intersected;
}

bool IntersectAnvil(vec3 origin, ivec3 index, vec3 direction, int dataValue, out vec3 hitPos, inout vec3 hitNormal) {
	bool intersected = false;
	float intersectDist;
	vec3 intersectNormal;

	int facing = dataValue % 2;

	// Base
	intersected = IntersectAABB(origin - index, direction, vec3(0.125,0,0.125), vec3(0.875,0.25,0.875), intersectDist, intersectNormal);

	// temp holders
	float tDist;
	vec3 tNormal;

	vec3 minBounds = facing == 0 ? vec3(0.25, 0.25,   0.1875) : vec3(0.1875, 0.25,   0.25);
	vec3 maxBounds = facing == 0 ? vec3(0.75, 0.3125, 0.8125) : vec3(0.8125, 0.3125, 0.75);
	if (IntersectAABB(origin - index, direction, minBounds, maxBounds, tDist, tNormal)) {
		if (!intersected || tDist < intersectDist) {
			intersectDist = tDist;
			intersectNormal = tNormal;
			intersected = true;
		}
	}

	minBounds = facing == 0 ? vec3(0.375, 0.3125, 0.25) : vec3(0.25, 0.3125, 0.375);
	maxBounds = facing == 0 ? vec3(0.625, 0.625,  0.75) : vec3(0.75, 0.625,  0.625);
	if (IntersectAABB(origin - index, direction, minBounds, maxBounds, tDist, tNormal)) {
		if (!intersected || tDist < intersectDist) {
			intersectDist = tDist;
			intersectNormal = tNormal;
			intersected = true;
		}
	}

	minBounds = facing == 0 ? vec3(0.1875, 0.625, 0.0) : vec3(0.0, 0.625, 0.1875);
	maxBounds = facing == 0 ? vec3(0.8125, 1.0,   1.0) : vec3(1.0, 1.0,   0.8125);
	if (IntersectAABB(origin - index, direction, minBounds, maxBounds, tDist, tNormal)) {
		if (!intersected || tDist < intersectDist) {
			intersectDist = tDist;
			intersectNormal = tNormal;
			intersected = true;
		}
	}

	if (intersected) {
		hitPos = intersectDist * direction + origin;
		hitNormal = intersectNormal;
	}

	return intersected;
}

bool IntersectGenericEmitter(vec3 origin, ivec3 index, vec3 direction, out vec3 hitPos, inout vec3 hitNormal, in float radius) {
	vec3 r0 = origin - index; // Origin relative to voxel
	vec3 rd = direction; // Ray direction
	vec3 s0 = vec3(0.5, 0.6, 0.5); // Sphere center relative to voxel
	float sr = radius; // Sphere radius

    float a = dot(rd, rd);
    vec3 s0_r0 = r0 - s0;
    float b = 2.0 * dot(rd, s0_r0);
    float c = dot(s0_r0, s0_r0) - (sr * sr);
    if (b*b - 4.0*a*c < 0.0) {
        return false;
    }
    float dist = (-b - sqrt((b*b) - 4.0*a*c))/(2.0*a);
	hitPos = dist * direction + origin;
	return true;
}

bool IsStair(int id) { return (id != 18 && id >= 6 && id <= 30); }

int GetStairState(int id) { return id - 6 - (13 * int(id > 18)); }

// bool IntersectVoxel(vec3 origin, ivec3 index, vec3 direction, int id, out vec3 hitPos, inout vec3 hitNormal) {
// 	// default value
// 	hitPos = origin;

// 	if(id == 21 || id == 22) {
// 		return IntersectSlab(origin, index, direction, id == 21, hitPos, hitNormal);
// 	} else if(id >= 23 && id <= 47) {
// 		int stairState = GetStairState(id);
//             if(stairState != clamp(stairState, 0, 11)) {
//                 hitPos = origin + direction * dist;
//                 return hit;
//             }

//             const vec3[12][4] stairBounds = vec3[12][4](
//                 vec3[4](vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 0.5), vec3(-1.0), vec3(-1.0)),
//                 vec3[4](vec3(0.0, 0.0, 0.5), vec3(1.0, 1.0, 1.0), vec3(-1.0), vec3(-1.0)),
//                 vec3[4](vec3(0.5, 0.0, 0.0), vec3(1.0, 1.0, 1.0), vec3(-1.0), vec3(-1.0)),
//                 vec3[4](vec3(0.0, 0.0, 0.0), vec3(0.5, 1.0, 1.0), vec3(-1.0), vec3(-1.0)),
//                 vec3[4](vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 0.5), vec3(0.0, 0.0, 0.0), vec3(0.5, 1.0, 1.0)),
//                 vec3[4](vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 0.5), vec3(0.5, 0.0, 0.0), vec3(1.0, 1.0, 1.0)),
//                 vec3[4](vec3(0.0, 0.0, 0.5), vec3(1.0, 1.0, 1.0), vec3(0.0, 0.0, 0.0), vec3(0.5, 1.0, 1.0)),
//                 vec3[4](vec3(0.0, 0.0, 0.5), vec3(1.0, 1.0, 1.0), vec3(0.5, 0.0, 0.0), vec3(1.0, 1.0, 1.0)),
//                 vec3[4](vec3(0.0, 0.0, 0.0), vec3(0.5, 1.0, 0.5), vec3(-1.0), vec3(-1.0)),
//                 vec3[4](vec3(0.5, 0.0, 0.0), vec3(1.0, 1.0, 0.5), vec3(-1.0), vec3(-1.0)),
//                 vec3[4](vec3(0.0, 0.0, 0.5), vec3(0.5, 1.0, 1.0), vec3(-1.0), vec3(-1.0)),
//                 vec3[4](vec3(0.5, 0.0, 0.5), vec3(1.0, 1.0, 1.0), vec3(-1.0), vec3(-1.0))
//             );

//             float dist2;
//             vec3 hitNormal2;
//             bool hit2 = IntersectAABB(origin - index, direction, stairBounds[stairState][0], stairBounds[stairState][1], dist2, hitNormal2);

//             float dist3;
//             vec3 hitNormal3;
//             bool hit3 = IntersectAABB(origin - index, direction, stairBounds[stairState][2], stairBounds[stairState][3], dist3, hitNormal3);

//             bool  tHit       = bool(mix(float(hit), 1.0       , float(hit2 && (!hit || dist2 < dist))));
//             vec3  tHitNormal =      mix(hitNormal , hitNormal2, float(hit2 && (!hit || dist2 < dist)));
//             float tDist      =      mix(dist      , dist2     , float(hit2 && (!hit || dist2 < dist)));

//             hit       = bool(mix(float(tHit), 1.0       , float(hit3 && (!tHit || dist3 < tDist))));
//             hitNormal =      mix(tHitNormal , hitNormal3, float(hit3 && (!tHit || dist3 < tDist)));
//             dist      =      mix(tDist      , dist3     , float(hit3 && (!tHit || dist3 < tDist)));
// 	}

// 	// Full blocks are always intersected
// 	return IntersectBlock(origin, index, direction, hitPos, hitNormal);
// }

const vec3[75][2] bounds = vec3[75][2](

			// Redstone torch off
			vec3[2](vec3(0.4375, 0.0000, 0.4375), vec3(0.5625, 0.6250, 0.5625)),

			// Slabs, stairs bottom
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
            vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
            vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
            vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
            vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
            vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
            vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
            vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),

            // Slabs, stairs top
			vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
			vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
            vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
			vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
            vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
			vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
            vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
			vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
            vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
			vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
            vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
			vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
            vec3[2](vec3(0.0000, 0.5000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),

			// Buttons
			vec3[2](vec3(0.0000, 0.3750, 0.3125), vec3(0.1250, 0.6250, 0.6875)),
			vec3[2](vec3(0.8750, 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.1250)),
			vec3[2](vec3(0.3250, 0.3750, 0.8750), vec3(0.6875, 0.6250, 1.0000)),
			vec3[2](vec3(0.3750, 0.0000, 0.3125), vec3(0.6250, 0.1250, 0.6875)),
			vec3[2](vec3(0.3125, 0.0000, 0.3750), vec3(0.6875, 0.1250, 0.6250)),
			vec3[2](vec3(0.3750, 0.8750, 0.3125), vec3(0.6250, 1.0000, 0.6875)),
			vec3[2](vec3(0.3125, 0.8750, 0.3750), vec3(0.6875, 1.0000, 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)),

			// Pressure plates
			vec3[2](vec3(0.0625, 0.0000, 0.0625), vec3(0.9375, 0.06250, 0.9375)),
			vec3[2](vec3(0.0625, 0.0000, 0.0625), vec3(0.9375, 0.03125, 0.9375)),

			// Doors and trapdoors
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.1875, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(0.1875, 1.0000, 1.0000)),
			vec3[2](vec3(0.8125, 0.0000, 0.0000), vec3(1.0000, 1.0000, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 1.0000, 0.1875)),
			vec3[2](vec3(0.0000, 0.0000, 0.8125), vec3(1.0000, 1.0000, 1.0000)),
			vec3[2](vec3(0.0000, 0.8125, 0.0000), vec3(1.0000, 1.0000, 1.0000)),

			// Carpets, snow and misc blocks
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.0625, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.1250, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.2500, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.3750, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.5000, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.6250, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.7500, 1.0000)),
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.8750, 1.0000)),

			// Grass path and farmland
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 0.9375, 1.0000)),

			// Fences and walls (post only)
			vec3[2](vec3(0.3750, 0.0000, 0.3750), vec3(0.6250, 1.0000, 0.6250)),
			vec3[2](vec3(0.3750, 0.0000, 0.3750), vec3(0.6250, 1.0000, 0.6250)),
			vec3[2](vec3(0.2500, 0.0000, 0.2500), vec3(0.7500, 1.0000, 0.7500)),

			// Lava
			vec3[2](vec3(0.0000, 0.0000, 0.0000), vec3(1.0000, 8.0/9.0, 1.0000)),

			// Torch
			vec3[2](vec3(0.4375, 0.0000, 0.4375), vec3(0.5625, 0.6250, 0.5625)),
			vec3[2](vec3(0.4375, 0.0000, 0.4375), vec3(0.5625, 0.6250, 0.5625)),
			vec3[2](vec3(0.4375, 0.0000, 0.4375), vec3(0.5625, 0.6250, 0.5625)),

			// Lanterns
			vec3[2](vec3(0.3125, 0.0000, 0.3125), vec3(0.6875, 0.4375, 0.6875)),
			vec3[2](vec3(0.3125, 0.0625, 0.3125), vec3(0.6875, 0.5000, 0.6875)),
			vec3[2](vec3(0.3125, 0.0000, 0.3125), vec3(0.6875, 0.4375, 0.6875)),
			vec3[2](vec3(0.3125, 0.0625, 0.3125), vec3(0.6875, 0.5000, 0.6875)),

			// End rods
			vec3[2](vec3(0.4375, 0.0000, 0.4375), vec3(0.5625, 1.0000, 0.5625)),
			vec3[2](vec3(0.0000, 0.4375, 0.4375), vec3(1.0000, 0.5625, 0.5625)),
			vec3[2](vec3(0.4375, 0.4375, 0.0000), vec3(0.5625, 0.5625, 1.0000)),

			// Beacon
			vec3[2](vec3(0.1875, 0.0000, 0.1875), vec3(0.8125, 0.8750, 0.8125))
		);

const vec3[12][4] stairBounds = vec3[12][4](
                vec3[4](vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 0.5), vec3(-1.0), vec3(-1.0)),
                vec3[4](vec3(0.0, 0.0, 0.5), vec3(1.0, 1.0, 1.0), vec3(-1.0), vec3(-1.0)),
                vec3[4](vec3(0.5, 0.0, 0.0), vec3(1.0, 1.0, 1.0), vec3(-1.0), vec3(-1.0)),
                vec3[4](vec3(0.0, 0.0, 0.0), vec3(0.5, 1.0, 1.0), vec3(-1.0), vec3(-1.0)),
                vec3[4](vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 0.5), vec3(0.0, 0.0, 0.0), vec3(0.5, 1.0, 1.0)),
                vec3[4](vec3(0.0, 0.0, 0.0), vec3(1.0, 1.0, 0.5), vec3(0.5, 0.0, 0.0), vec3(1.0, 1.0, 1.0)),
                vec3[4](vec3(0.0, 0.0, 0.5), vec3(1.0, 1.0, 1.0), vec3(0.0, 0.0, 0.0), vec3(0.5, 1.0, 1.0)),
                vec3[4](vec3(0.0, 0.0, 0.5), vec3(1.0, 1.0, 1.0), vec3(0.5, 0.0, 0.0), vec3(1.0, 1.0, 1.0)),
                vec3[4](vec3(0.0, 0.0, 0.0), vec3(0.5, 1.0, 0.5), vec3(-1.0), vec3(-1.0)),
                vec3[4](vec3(0.5, 0.0, 0.0), vec3(1.0, 1.0, 0.5), vec3(-1.0), vec3(-1.0)),
                vec3[4](vec3(0.0, 0.0, 0.5), vec3(0.5, 1.0, 1.0), vec3(-1.0), vec3(-1.0)),
                vec3[4](vec3(0.5, 0.0, 0.5), vec3(1.0, 1.0, 1.0), vec3(-1.0), vec3(-1.0))
            );

bool IntersectVoxel(vec3 origin, ivec3 index, vec3 direction, int id, out vec3 hitPos, inout vec3 hitNormal, in const float sphere_radius) {
	// default value
	hitPos = origin;

	#ifdef IntersectFences
	if (id == 64 || id == 65) {
		return IntersectFence(origin, index, direction, id, hitPos, hitNormal);
	}
    #endif
    
    #ifdef IntersectWalls
	if (id == 66) {
		return IntersectWall(origin, index, direction, hitPos, hitNormal);
    }
    #endif

    #ifdef IntersectAnvils
	if (id == 130 || id == 131) {
		return IntersectAnvil(origin, index, direction, id-129, hitPos, hitNormal);
	}
	#endif

	if(id >= 4 && id <= 77) {

		float dist;
		bool hit = IntersectAABB(origin - index, direction, bounds[id-4][0], bounds[id-4][1], dist, hitNormal);

        #ifdef IntersectStairs

            int stairState = GetStairState(id);
            if(stairState != clamp(stairState, 0, 11)) {
                hitPos = origin + direction * dist;
                return hit;
            }


            float dist2;
            vec3 hitNormal2;
            bool hit2 = IntersectAABB(origin - index, direction, stairBounds[stairState][0], stairBounds[stairState][1], dist2, hitNormal2);

            float dist3;
            vec3 hitNormal3;
            bool hit3 = IntersectAABB(origin - index, direction, stairBounds[stairState][2], stairBounds[stairState][3], dist3, hitNormal3);

            bool  tHit       = bool(mix(float(hit), 1.0       , float(hit2 && (!hit || dist2 < dist))));
            vec3  tHitNormal =      mix(hitNormal , hitNormal2, float(hit2 && (!hit || dist2 < dist)));
            float tDist      =      mix(dist      , dist2     , float(hit2 && (!hit || dist2 < dist)));

            hit       = bool(mix(float(tHit), 1.0       , float(hit3 && (!tHit || dist3 < tDist))));
            hitNormal =      mix(tHitNormal , hitNormal3, float(hit3 && (!tHit || dist3 < tDist)));
            dist      =      mix(tDist      , dist3     , float(hit3 && (!tHit || dist3 < tDist)));
        #endif

		hitPos = origin + direction * dist;
		return hit;
	} else if(isSpherical(id)) {
		return IntersectGenericEmitter(origin, index, direction, hitPos, hitNormal, sphere_radius);
	}

	return IntersectBlock(origin, index, direction, hitPos, hitNormal);
}

bool IntersectVoxelAlphatest(vec3 origin, ivec3 index, vec3 direction, vec4[2] voxel, int id, out vec3 hitPos, inout vec3 hitNormal, inout vec3 rayTint, in const float sphere_radius, in bool depthHit) {
	bool hit = IntersectVoxel(origin, index, direction, id, hitPos, hitNormal, sphere_radius);
	if((id == LEAVES || id == 200) && depthHit) return false;
	if (hit) {
		
		// Perform alpha test
		#ifdef ALPHA_TEST
		if(id == LEAVES || id == 200 || id == STAINED_GLASS) {
			vec4 albedo_opacity = getAlbedoAt(hitPos, hitNormal, voxel);
			rayTint *= id == STAINED_GLASS ? albedo_opacity.rgb : vec3(1.0);
			return albedo_opacity.a > 0.9;
		} else {
			return true;
		}
		#else
		return true;
		#endif
	}

	return false;
}

// bool IntersectVoxelAlphatest(vec3 origin, ivec3 index, vec3 direction, vec4[2] voxel, int id, out vec3 hitPos, inout vec3 hitNormal, in const float sphere_radius, in bool depthHit) {
// 	bool hit = IntersectVoxel(origin, index, direction, id, hitPos, hitNormal, sphere_radius);
// 	if((id == LEAVES || id == 200) && depthHit) return false;
// 	if(hit) {
// 		// Figure out texture coordinates
// 		int   tileSize = ExtractVoxelTileSize(voxel);
// 		ivec2 tileOffs = ExtractVoxelTileIndex(voxel) * tileSize;

// 		ivec2 tileTexel;
// 		if (abs(hitNormal.y) > abs(hitNormal.x) && abs(hitNormal.y) > abs(hitNormal.z)) {
// 			tileTexel = ivec2(fract(hitPos.x) * tileSize, fract(hitPos.z * sign(hitNormal.y)) * tileSize);
// 		} else {
// 			tileTexel = ivec2(fract(hitPos.x * sign(hitNormal.z) - hitPos.z * sign(hitNormal.x)) * tileSize, fract(-hitPos.y) * tileSize);
// 		}

// 		ivec2 texelIndex = tileOffs + tileTexel;

// 		// Read textures
// 		float alpha = texelFetch(depthtex2, texelIndex, 0).a;
// 		// vec4 albedo = texelFetch(depthtex2, texelIndex, 0); // Albedo

// 		// accColor *= mix(vec3(1.0), toLinear(albedo.rgb) * _sRGB_AP1_ALBEDO, float(id == 201));
		
// 		#ifdef ALPHA_TEST
// 			return (mix(alpha, 0.0, float(id == 201))) > 0.102;
// 		#else
// 			return bool(mix(1.0, 0.0, float(id == 201)));
// 		#endif
// 	}
// 	return hit;
// }

float sum(vec3 v) { return dot(v, vec3(1.0)); }

float toConservativeEuclidean(float Manhattan) {
	return Manhattan == 1 ? 1 : int(floor(Manhattan * 0.57735026918f));
}

#define DIFFUSE_SPHERE_RADIUS 0.42
#define SPECULAR_SPHERE_RADIUS 0.2

bool RaytraceVoxel(vec3 origin, ivec3 originIndex, vec3 direction, out vec4[2] voxel, out vec3 hitPos, out ivec3 hitIndex, out vec3 hitNormal, out vec3 rayTint, const int dist, in const float sphere_radius) {
	ivec3 mapPos = originIndex;

	vec3 deltaDist = abs(vec3(length(direction)) / direction);
	
	ivec3 rayStep = ivec3(sign(direction));

	vec3 sideDist = (sign(direction) * (vec3(mapPos) - origin) + (sign(direction) * 0.5) + 0.5) * deltaDist; 
	
	bvec3 mask = bvec3(false);

	bool hit = false;

	voxel = ReadVoxel(originIndex);

	rayTint = vec3(1.0);
	int id = ExtractVoxelId(voxel);
	if ((id > 0 && id < 250)) {
		if (IntersectVoxelAlphatest(origin, originIndex, direction, voxel, id, hitPos, hitNormal, rayTint, sphere_radius, true)) {
			return true;
		}
	}

	
	for (int i = 0; i < dist; i++) {
		mask = lessThanEqual(sideDist.xyz, min(sideDist.yzx, sideDist.zxy));
		sideDist += vec3(mask) * deltaDist;
		mapPos += ivec3(vec3(mask)) * rayStep;

		if (!IsInVoxelizationVolume(mapPos) || hit) { continue; }

		voxel = ReadVoxel(mapPos);
		hitIndex = mapPos;
		id = ExtractVoxelId(voxel);
		if (id > 0 && id < 250) {
			// hitPos = direction / sum(vec3(mask) * direction) * sum(vec3(mask) * (mapPos + vec3(lessThan(direction, vec3(0))) - origin)) + origin;
			hit = IntersectVoxelAlphatest(origin, mapPos, direction, voxel, id, hitPos, hitNormal, rayTint, sphere_radius, false);
		}
		
	}
	
	// hitIndex = mapPos;
	if(!hit) hitPos = direction / sum(vec3(mask) * direction) * sum(vec3(mask) * (mapPos + vec3(lessThan(direction, vec3(0))) - origin)) + origin;
	return hit;
}

bool RaytraceVoxelCache(vec3 origin, ivec3 originIndex, vec3 direction, out vec4[2] voxel, out vec3 hitPos, out ivec3 hitIndex, out vec3 hitNormal, out vec3 rayTint, const int dist, in const float sphere_radius) {
	ivec3 mapPos = originIndex;

	vec3 deltaDist = abs(vec3(length(direction)) / direction);
	
	ivec3 rayStep = ivec3(sign(direction));

	vec3 sideDist = (sign(direction) * (vec3(mapPos) - origin) + (sign(direction) * 0.5) + 0.5) * deltaDist; 
	
	bvec3 mask = bvec3(false);

	bool hit = false;

	voxel = ReadVoxel(originIndex);

	rayTint = vec3(1.0);
	int id = ExtractVoxelId(voxel);
	if ((id > 0 && id < 250)) {
		IntersectVoxelAlphatest(origin, originIndex, direction, voxel, id, hitPos, hitNormal, rayTint, sphere_radius, true);
	}

	
	for (int i = 0; i < dist; i++) {
		mask = lessThanEqual(sideDist.xyz, min(sideDist.yzx, sideDist.zxy));
		sideDist += vec3(mask) * deltaDist;
		mapPos += ivec3(vec3(mask)) * rayStep;

		if (!IsInVoxelizationVolume(mapPos) || hit) { continue; }

		voxel = ReadVoxel(mapPos);
		hitIndex = mapPos;
		id = ExtractVoxelId(voxel);
		if (id > 0 && id < 250) {
			// hitPos = direction / sum(vec3(mask) * direction) * sum(vec3(mask) * (mapPos + vec3(lessThan(direction, vec3(0))) - origin)) + origin;
			hit = IntersectVoxelAlphatest(origin, mapPos, direction, voxel, id, hitPos, hitNormal, rayTint, sphere_radius, false);
		}
		
	}
	
	// hitIndex = mapPos;
	if(!hit) hitPos = direction / sum(vec3(mask) * direction) * sum(vec3(mask) * (mapPos + vec3(lessThan(direction, vec3(0))) - origin)) + origin;
	return hit;
}



// bool RaytraceVoxel2(vec3 origin, ivec3 originIndex, vec3 direction, bool transmit, out vec4[2] voxel, out vec3 hitPos, out ivec3 voxelIndex, out vec3 hitNormal, const int maxSteps, in const float sphere_radius) {


// 	voxel = ReadVoxel(originIndex);

// 	int pid = ExtractVoxelId(voxel);
// 	if ((pid > 0  && pid < 250) && !transmit) {
// 		if (IntersectVoxelAlphatest(origin, originIndex, direction, voxel, pid, hitPos, hitNormal, sphere_radius, true)) {
// 			return true;
// 		}
// 	}
    
// 	voxelIndex = originIndex;
// 	vec3 deltaDist;
// 	vec3 next;
// 	ivec3 deltaSign;
// 	for (int axis = 0; axis < 3; ++axis) {
// 		deltaDist[axis] = length(direction / direction[axis]);
// 		if (direction[axis] < 0.0) {
// 			deltaSign[axis] = -1;
// 			next[axis] = (origin[axis] - voxelIndex[axis]) * deltaDist[axis];
// 		} else {
// 			deltaSign[axis] = 1;
// 			next[axis] = (voxelIndex[axis] + 1.0 - origin[axis]) * deltaDist[axis];
// 		}
// 	}

// 	bool hit = false;

// 	for (int i = 0; i < maxSteps && !hit; ++i) {
// 		if (next.x > next.y) {
// 			if (next.y > next.z) {
// 				next.z       += deltaDist.z;
// 				voxelIndex.z += deltaSign.z;
// 				hitNormal     = vec3(0, 0, -deltaSign.z);
// 			} else {
// 				next.y       += deltaDist.y;
// 				voxelIndex.y += deltaSign.y;
// 				hitNormal     = vec3(0, -deltaSign.y, 0);
// 			}
// 		} else if (next.x > next.z) {
// 			next.z       += deltaDist.z;
// 			voxelIndex.z += deltaSign.z;
// 			hitNormal     = vec3(0, 0, -deltaSign.z);
// 		} else {
// 			next.x       += deltaDist.x;
// 			voxelIndex.x += deltaSign.x;
// 			hitNormal     = vec3(-deltaSign.x, 0, 0);
// 		}

// 		if (!IsInVoxelizationVolume(voxelIndex)) { break; }

// 		voxel  = ReadVoxel(voxelIndex);
// 		int id = ExtractVoxelId(voxel);
	
// 		if(id > 0 && id < 250) {
// 			hit = IntersectVoxelAlphatest(origin, voxelIndex, direction, voxel, id, hitPos, hitNormal, sphere_radius, false);
// 		}
// 	}

// 	return hit;
// }