Source: render/RenderList.js

import { Vector3 } from '../math/Vector3.js';
import { Sphere } from '../math/Sphere.js';

var helpVector3 = new Vector3();
var helpSphere = new Sphere();

var sortFrontToBack = function(a, b) {
	if (a.renderOrder !== b.renderOrder) {
		return a.renderOrder - b.renderOrder;
	} else if (a.material.id !== b.material.id) {
		// batch
		return a.material.id - b.material.id;
	} else if (a.z !== b.z) {
		return a.z - b.z;
	} else {
		return a.id - b.id;
	}
}

var sortBackToFront = function(a, b) {
	if (a.renderOrder !== b.renderOrder) {
		return a.renderOrder - b.renderOrder;
	} else if (a.z !== b.z) {
		return b.z - a.z;
	} else if (a.material.id !== b.material.id) {
		// fix Unstable sort below chrome version 7.0
		// if render same object with different materials
		return a.material.id - b.material.id;
	} else {
		return a.id - b.id;
	}
}

/**
 * Render list is used to collect renderable objects from the scene.
 * Render list has an opaque list and a transparent list.
 * @constructor
 * @hideconstructor
 * @memberof zen3d
 */
function RenderList() {
	var renderItems = [];
	var renderItemsIndex = 0;

	/**
     * Opaque list.
     * @memberof zen3d.RenderList#
     */
	var opaque = [];
	var opaqueCount = 0;

	/**
     * Transparent list.
     * @memberof zen3d.RenderList#
     */
	var transparent = [];
	var transparentCount = 0;

	/**
     * Mark count start.
     * @memberof zen3d.RenderList#
     */
	function startCount() {
		renderItemsIndex = 0;

		opaqueCount = 0;
		transparentCount = 0;
	}

	/**
     * Collect render list.
     * @memberof zen3d.RenderList#
     * @param {zen3d.Object3D} object - The object to be collected.
     * @param {zen3d.Camera} camera - The main camera.
     */
	function add(object, camera) {
		// frustum test
		if (object.frustumCulled && camera.frustumCulled) {
			helpSphere.copy(object.geometry.boundingSphere).applyMatrix4(object.worldMatrix);
			var frustumTest = camera.frustum.intersectsSphere(helpSphere);
			if (!frustumTest) { // only test bounding sphere
				return;
			}
		}

		// calculate z
		helpVector3.setFromMatrixPosition(object.worldMatrix);
		helpVector3.project(camera);

		if (Array.isArray(object.material)) {
			var groups = object.geometry.groups;

			for (var i = 0; i < groups.length; i++) {
				var group = groups[i];
				var groupMaterial = object.material[group.materialIndex];
				if (groupMaterial) {
					_doAdd(object, object.geometry, groupMaterial, helpVector3.z, group);
				}
			}
		} else {
			_doAdd(object, object.geometry, object.material, helpVector3.z);
		}
	}

	function _doAdd(object, geometry, material, z, group) {
		var renderable = renderItems[renderItemsIndex];

		if (renderable === undefined) {
			renderable = {
				object: object,
				geometry: geometry,
				material: material,
				z: z,
				renderOrder: object.renderOrder,
				group: group
			};
			renderItems[renderItemsIndex] = renderable;
		} else {
			renderable.object = object;
			renderable.geometry = geometry;
			renderable.material = material;
			renderable.z = z;
			renderable.renderOrder = object.renderOrder;
			renderable.group = group;
		}

		if (material.transparent) {
			transparent[transparentCount] = renderable;
			transparentCount++;
		} else {
			opaque[opaqueCount] = renderable;
			opaqueCount++;
		}

		renderItemsIndex++;
	}

	/**
     * Mark count finished.
     * @memberof zen3d.RenderList#
     */
	function endCount() {
		opaque.length = opaqueCount;
		transparent.length = transparentCount;
	}

	/**
     * Sort render list.
     * @memberof zen3d.RenderList#
     */
	function sort() {
		opaque.sort(sortFrontToBack);
		transparent.sort(sortBackToFront);
	}

	return {
		opaque: opaque,
		transparent: transparent,
		startCount: startCount,
		add: add,
		endCount: endCount,
		sort: sort
	};
}

export { RenderList };