vec3 ObjectToView(vec3 objectPos) {
	return mat3(gl_ModelViewMatrix) * objectPos + gl_ModelViewMatrix[3].xyz;
}

vec3 ViewToWorld(vec3 viewPos) {
	return mat3(shadowModelViewInverse) * viewPos + shadowModelViewInverse[3].xyz;
}

// renderStage — только Iris. В OptiFine shadow pass и так рендерит только terrain,
// поэтому проверка нужна только для Iris.
#ifdef IS_IRIS
uniform int renderStage;
#endif

// Записывает compact ID блока в voxelimg.
// voxelData: 0=воздух, 99=блокер, 1-31=индекс цвета флудфила
void UpdateVoxelMap(int vertexID, int voxelData) {
	if (vertexID % 4 != 0) {
		return;
	}

	vec3 objectPos = gl_Vertex.xyz + at_midBlock / 64.0;
	vec3 viewPos   = ObjectToView(objectPos);
	vec3 worldPos  = ViewToWorld(viewPos);
	vec3 voxelPos  = WorldToVoxel(worldPos);

	// В Iris проверяем renderStage — shadow pass рендерит несколько стадий.
	// В OptiFine shadow pass — всегда только terrain, проверка не нужна.
	#ifdef IS_IRIS
	bool isEligible = renderStage == MC_RENDER_STAGE_TERRAIN_SOLID      ||
	                  renderStage == MC_RENDER_STAGE_TERRAIN_TRANSLUCENT ||
	                  renderStage == MC_RENDER_STAGE_TERRAIN_CUTOUT      ||
	                  renderStage == MC_RENDER_STAGE_TERRAIN_CUTOUT_MIPPED;
	#else
	bool isEligible = true;
	#endif

	if (isEligible && IsInVoxelMapVolume(voxelPos) && voxelData > 0) {
		imageStore(voxelimg, ivec3(voxelPos), uvec4(uint(voxelData), 0u, 0u, 0u));
	}
}
