thothbot.parallax.core.client.renderers.WebGLRenderer.java Source code

Java tutorial

Introduction

Here is the source code for thothbot.parallax.core.client.renderers.WebGLRenderer.java

Source

/*
 * Copyright 2012 Alex Usachev, thothbot@gmail.com
 * 
 * This file is part of Parallax project.
 * 
 * Parallax is free software: you can redistribute it and/or modify it 
 * under the terms of the Creative Commons Attribution 3.0 Unported License.
 * 
 * Parallax is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE. See the Creative Commons Attribution 
 * 3.0 Unported License. for more details.
 * 
 * You should have received a copy of the the Creative Commons Attribution 
 * 3.0 Unported License along with Parallax. 
 * If not, see http://creativecommons.org/licenses/by/3.0/.
 */

package thothbot.parallax.core.client.renderers;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import thothbot.parallax.core.client.context.Canvas3d;
import thothbot.parallax.core.client.events.HasEventBus;
import thothbot.parallax.core.client.events.ViewportResizeEvent;
import thothbot.parallax.core.client.gl2.WebGLConstants;
import thothbot.parallax.core.client.gl2.WebGLExtension;
import thothbot.parallax.core.client.gl2.WebGLFramebuffer;
import thothbot.parallax.core.client.gl2.WebGLProgram;
import thothbot.parallax.core.client.gl2.WebGLRenderingContext;
import thothbot.parallax.core.client.gl2.WebGLShaderPrecisionFormat;
import thothbot.parallax.core.client.gl2.WebGLUniformLocation;
import thothbot.parallax.core.client.gl2.arrays.Float32Array;
import thothbot.parallax.core.client.gl2.arrays.Uint8Array;
import thothbot.parallax.core.client.gl2.enums.BeginMode;
import thothbot.parallax.core.client.gl2.enums.BlendEquationMode;
import thothbot.parallax.core.client.gl2.enums.BlendingFactorDest;
import thothbot.parallax.core.client.gl2.enums.BlendingFactorSrc;
import thothbot.parallax.core.client.gl2.enums.BufferTarget;
import thothbot.parallax.core.client.gl2.enums.BufferUsage;
import thothbot.parallax.core.client.gl2.enums.ClearBufferMask;
import thothbot.parallax.core.client.gl2.enums.CullFaceMode;
import thothbot.parallax.core.client.gl2.enums.DataType;
import thothbot.parallax.core.client.gl2.enums.DepthFunction;
import thothbot.parallax.core.client.gl2.enums.DrawElementsType;
import thothbot.parallax.core.client.gl2.enums.EnableCap;
import thothbot.parallax.core.client.gl2.enums.FrontFaceDirection;
import thothbot.parallax.core.client.gl2.enums.PixelStoreParameter;
import thothbot.parallax.core.client.gl2.enums.ShaderPrecisionSpecifiedTypes;
import thothbot.parallax.core.client.gl2.enums.Shaders;
import thothbot.parallax.core.client.gl2.enums.TextureMinFilter;
import thothbot.parallax.core.client.gl2.enums.TextureTarget;
import thothbot.parallax.core.client.gl2.enums.TextureUnit;
import thothbot.parallax.core.client.gl2.extension.ExtTextureFilterAnisotropic;
import thothbot.parallax.core.client.gl2.extension.WebGLCompressedTextureS3tc;
import thothbot.parallax.core.client.renderers.WebGLExtensions.Id;
import thothbot.parallax.core.client.shaders.Attribute;
import thothbot.parallax.core.client.shaders.ProgramParameters;
import thothbot.parallax.core.client.shaders.Shader;
import thothbot.parallax.core.client.shaders.Uniform;
import thothbot.parallax.core.client.shaders.Uniform.TYPE;
import thothbot.parallax.core.client.textures.CompressedTexture;
import thothbot.parallax.core.client.textures.CubeTexture;
import thothbot.parallax.core.client.textures.DataTexture;
import thothbot.parallax.core.client.textures.RenderTargetCubeTexture;
import thothbot.parallax.core.client.textures.RenderTargetTexture;
import thothbot.parallax.core.client.textures.Texture;
import thothbot.parallax.core.shared.Log;
import thothbot.parallax.core.shared.cameras.Camera;
import thothbot.parallax.core.shared.cameras.HasNearFar;
import thothbot.parallax.core.shared.core.AbstractGeometry;
import thothbot.parallax.core.shared.core.BufferAttribute;
import thothbot.parallax.core.shared.core.BufferGeometry;
import thothbot.parallax.core.shared.core.BufferGeometry.DrawCall;
import thothbot.parallax.core.shared.core.Face3;
import thothbot.parallax.core.shared.core.FastMap;
import thothbot.parallax.core.shared.core.Geometry;
import thothbot.parallax.core.shared.core.GeometryGroup;
import thothbot.parallax.core.shared.core.GeometryObject;
import thothbot.parallax.core.shared.core.Object3D;
import thothbot.parallax.core.shared.lights.DirectionalLight;
import thothbot.parallax.core.shared.lights.HemisphereLight;
import thothbot.parallax.core.shared.lights.Light;
import thothbot.parallax.core.shared.lights.PointLight;
import thothbot.parallax.core.shared.lights.ShadowLight;
import thothbot.parallax.core.shared.lights.SpotLight;
import thothbot.parallax.core.shared.materials.HasEnvMap;
import thothbot.parallax.core.shared.materials.HasFog;
import thothbot.parallax.core.shared.materials.HasSkinning;
import thothbot.parallax.core.shared.materials.HasWireframe;
import thothbot.parallax.core.shared.materials.LineBasicMaterial;
import thothbot.parallax.core.shared.materials.Material;
import thothbot.parallax.core.shared.materials.MeshBasicMaterial;
import thothbot.parallax.core.shared.materials.MeshFaceMaterial;
import thothbot.parallax.core.shared.materials.MeshLambertMaterial;
import thothbot.parallax.core.shared.materials.MeshPhongMaterial;
import thothbot.parallax.core.shared.materials.ShaderMaterial;
import thothbot.parallax.core.shared.math.Color;
import thothbot.parallax.core.shared.math.Frustum;
import thothbot.parallax.core.shared.math.Mathematics;
import thothbot.parallax.core.shared.math.Matrix3;
import thothbot.parallax.core.shared.math.Matrix4;
import thothbot.parallax.core.shared.math.Vector2;
import thothbot.parallax.core.shared.math.Vector3;
import thothbot.parallax.core.shared.math.Vector4;
import thothbot.parallax.core.shared.objects.Line;
import thothbot.parallax.core.shared.objects.Mesh;
import thothbot.parallax.core.shared.objects.PointCloud;
import thothbot.parallax.core.shared.objects.SkinnedMesh;
import thothbot.parallax.core.shared.scenes.AbstractFog;
import thothbot.parallax.core.shared.scenes.FogExp2;
import thothbot.parallax.core.shared.scenes.Scene;

import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.CanvasElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.ImageElement;

/**
 * The WebGL renderer displays your beautifully crafted {@link Scene}s using WebGL, if your device supports it.
 */
public class WebGLRenderer extends AbstractRenderer implements HasEventBus {
    // The HTML5 Canvas's 'webgl' context obtained from the canvas where the renderer will draw.
    private WebGLRenderingContext gl;

    private WebGlRendererInfo info;

    private List<Light> lights = new ArrayList<Light>();

    public Map<String, List<WebGLObject>> _webglObjects = GWT.isScript() ? new FastMap<List<WebGLObject>>()
            : new HashMap<String, List<WebGLObject>>();

    public List<WebGLObject> _webglObjectsImmediate = new ArrayList<WebGLObject>();

    public List<WebGLObject> opaqueObjects = new ArrayList<WebGLObject>();
    public List<WebGLObject> transparentObjects = new ArrayList<WebGLObject>();

    public double devicePixelRatio = _devicePixelRatio();

    public final native double _devicePixelRatio() /*-{
                                                   return self.devicePixelRatio !==null ? self.devicePixelRatio : 1.0;
                                                   }-*/;

    public boolean _logarithmicDepthBuffer = false;

    // ---- Properties ------------------------------------

    public Shader.PRECISION _precision = Shader.PRECISION.HIGHP;

    // clearing
    private boolean autoClearColor = true;
    private boolean autoClearDepth = true;
    private boolean autoClearStencil = true;

    // scene graph
    private boolean sortObjects = true;

    // physically based shading
    private boolean gammaInput = false;
    private boolean gammaOutput = false;

    // shadow map

    //   private boolean shadowMapEnabled = false;
    //   shadowMapType = PCFShadowMap;
    private CullFaceMode shadowMapCullFace = CullFaceMode.FRONT;
    private boolean shadowMapDebug = false;
    private boolean shadowMapCascade = false;

    // morphs
    private int maxMorphTargets = 8;
    private int maxMorphNormals = 4;

    // flags
    private boolean autoScaleCubemaps = true;

    // ---- Internal properties ----------------------------

    public Map<String, Shader> _programs;

    private WebGLProgram _currentProgram = null;
    private WebGLFramebuffer _currentFramebuffer = null;
    private int _currentMaterialId = -1;
    private int _currentGeometryGroupHash = -1;
    private Camera _currentCamera = null;

    private int _usedTextureUnits = 0;

    // GL state cache

    private Material.SIDE _oldDoubleSided = null;
    private Material.SIDE _oldFlipSided = null;
    private Material.SIDE cache_oldMaterialSided = null;

    private Material.BLENDING _oldBlending = null;

    private BlendEquationMode _oldBlendEquation = null;
    private BlendingFactorSrc _oldBlendSrc = null;
    private BlendingFactorDest _oldBlendDst = null;

    private Boolean _oldDepthTest = null;
    private Boolean _oldDepthWrite = null;

    private Boolean _oldPolygonOffset = null;
    private Double _oldPolygonOffsetFactor = null;
    private Double _oldPolygonOffsetUnits = null;

    //   _oldLineWidth = null,

    private int _viewportX = 0;
    private int _viewportY = 0;
    private int _viewportWidth = 0;
    private int _viewportHeight = 0;
    private int _currentWidth = 0;
    private int _currentHeight = 0;

    private Uint8Array _newAttributes = Uint8Array.create(16);
    private Uint8Array _enabledAttributes = Uint8Array.create(16);

    // frustum
    public Frustum _frustum = new Frustum();

    // camera matrices cache

    public Matrix4 _projScreenMatrix = new Matrix4();
    public Matrix4 _projScreenMatrixPS = new Matrix4();

    public Vector3 _vector3 = new Vector3();

    // light arrays cache
    private Vector3 _direction = new Vector3();

    private boolean _lightsNeedUpdate = true;

    private RendererLights _lights;

    private List<Plugin> plugins;

    //   var sprites = [];
    //   var lensFlares = [];

    // GPU capabilities
    private int _maxTextures;
    private int _maxVertexTextures;
    private int _maxTextureSize;
    private int _maxCubemapSize;

    private boolean _supportsVertexTextures;
    private boolean _supportsBoneTextures;

    private WebGLShaderPrecisionFormat _vertexShaderPrecisionHighpFloat;
    private WebGLShaderPrecisionFormat _vertexShaderPrecisionMediumpFloat;
    private WebGLShaderPrecisionFormat _vertexShaderPrecisionLowpFloat;

    private WebGLShaderPrecisionFormat _fragmentShaderPrecisionHighpFloat;
    private WebGLShaderPrecisionFormat _fragmentShaderPrecisionMediumpFloat;
    private WebGLShaderPrecisionFormat _fragmentShaderPrecisionLowpFloat;

    //   private OESTextureFloat GLExtensionTextureFloat;
    //   private OESStandardDerivatives GLExtensionStandardDerivatives;
    //   private ExtTextureFilterAnisotropic GLExtensionTextureFilterAnisotropic;
    //   private WebGLCompressedTextureS3tc GLExtensionCompressedTextureS3TC;

    // clamp precision to maximum available
    private boolean highpAvailable;
    private boolean mediumpAvailable;

    private boolean isAutoUpdateObjects = true;
    private boolean isAutoUpdateScene = true;

    /**
     * The constructor will create renderer for the {@link Canvas3d} widget.
     * 
     * @param gl     the {@link WebGLRenderingContext}
     * @param width  the viewport width
     * @param height the viewport height
     */
    public WebGLRenderer(WebGLRenderingContext gl, int width, int height) {
        this.gl = gl;

        this.setInfo(new WebGlRendererInfo());

        this._lights = new RendererLights();
        this._programs = GWT.isScript() ? new FastMap<Shader>() : new HashMap<String, Shader>();

        this._maxTextures = gl.getParameteri(WebGLConstants.MAX_TEXTURE_IMAGE_UNITS);
        this._maxVertexTextures = gl.getParameteri(WebGLConstants.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
        this._maxTextureSize = gl.getParameteri(WebGLConstants.MAX_TEXTURE_SIZE);
        this._maxCubemapSize = gl.getParameteri(WebGLConstants.MAX_CUBE_MAP_TEXTURE_SIZE);

        this._supportsVertexTextures = (this._maxVertexTextures > 0);
        this._supportsBoneTextures = this._supportsVertexTextures
                && WebGLExtensions.get(gl, WebGLExtensions.Id.OES_texture_float) != null;

        this._vertexShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(Shaders.VERTEX_SHADER,
                ShaderPrecisionSpecifiedTypes.HIGH_FLOAT);
        this._vertexShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(Shaders.VERTEX_SHADER,
                ShaderPrecisionSpecifiedTypes.MEDIUM_FLOAT);
        this._vertexShaderPrecisionLowpFloat = gl.getShaderPrecisionFormat(Shaders.VERTEX_SHADER,
                ShaderPrecisionSpecifiedTypes.LOW_FLOAT);

        this._fragmentShaderPrecisionHighpFloat = gl.getShaderPrecisionFormat(Shaders.FRAGMENT_SHADER,
                ShaderPrecisionSpecifiedTypes.HIGH_FLOAT);
        this._fragmentShaderPrecisionMediumpFloat = gl.getShaderPrecisionFormat(Shaders.FRAGMENT_SHADER,
                ShaderPrecisionSpecifiedTypes.MEDIUM_FLOAT);
        this._fragmentShaderPrecisionLowpFloat = gl.getShaderPrecisionFormat(Shaders.FRAGMENT_SHADER,
                ShaderPrecisionSpecifiedTypes.LOW_FLOAT);

        this.highpAvailable = _vertexShaderPrecisionHighpFloat.getPrecision() > 0
                && _fragmentShaderPrecisionHighpFloat.getPrecision() > 0;
        this.mediumpAvailable = _vertexShaderPrecisionMediumpFloat.getPrecision() > 0
                && _fragmentShaderPrecisionMediumpFloat.getPrecision() > 0;

        if (this._precision == Shader.PRECISION.HIGHP && !highpAvailable) {

            if (mediumpAvailable) {

                this._precision = Shader.PRECISION.MEDIUMP;
                Log.warn("WebGLRenderer: highp not supported, using mediump.");

            } else {

                this._precision = Shader.PRECISION.LOWP;
                Log.warn("WebGLRenderer: highp and mediump not supported, using lowp.");

            }

        }

        if (this._precision == Shader.PRECISION.MEDIUMP && !mediumpAvailable) {

            this._precision = Shader.PRECISION.LOWP;
            Log.warn("THREE.WebGLRenderer: mediump not supported, using lowp.");
        }

        WebGLExtensions.get(gl, WebGLExtensions.Id.OES_texture_float);
        WebGLExtensions.get(gl, WebGLExtensions.Id.OES_texture_float_linear);
        WebGLExtensions.get(gl, WebGLExtensions.Id.OES_standard_derivatives);

        if (_logarithmicDepthBuffer) {
            WebGLExtensions.get(gl, WebGLExtensions.Id.EXT_frag_depth);
        }

        WebGLCompressedTextureS3tc GLExtensionCompressedTextureS3TC = (WebGLCompressedTextureS3tc) gl
                .getExtension("WEBGL_compressed_texture_s3tc");
        if (GLExtensionCompressedTextureS3TC == null)
            GLExtensionCompressedTextureS3TC = (WebGLCompressedTextureS3tc) gl
                    .getExtension("MOZ_WEBGL_compressed_texture_s3tc");
        if (GLExtensionCompressedTextureS3TC == null)
            GLExtensionCompressedTextureS3TC = (WebGLCompressedTextureS3tc) gl
                    .getExtension("WEBKIT_WEBGL_compressed_texture_s3tc");
        if (GLExtensionCompressedTextureS3TC == null)
            Log.warn("WebGLRenderer: S3TC compressed textures not supported.");

        setSize(width, height);
        setDefaultGLState();

        // default plugins (order is important)
        this.plugins = new ArrayList<Plugin>();
    }

    public void addPlugin(Plugin plugin) {
        deletePlugin(plugin);
        this.plugins.add(plugin);
    }

    public void deletePlugin(Plugin plugin) {
        if (plugin == null)
            return;

        if (this.plugins.remove(plugin)) {
            plugin.deallocate();
        }
    }

    public boolean supportsVertexTextures() {
        return this._maxVertexTextures > 0;
    }

    public boolean supportsFloatTextures() {
        return WebGLExtensions.get(this.gl, Id.OES_texture_float) != null;
    }

    public boolean supportsStandardDerivatives() {
        return WebGLExtensions.get(this.gl, Id.OES_standard_derivatives) != null;
    }

    public boolean supportsCompressedTextureS3TC() {
        return WebGLExtensions.get(this.gl, Id.WEBGL_compressed_texture_s3tc) != null;
    }

    public boolean supportsCompressedTexturePVRTC() {
        return WebGLExtensions.get(this.gl, Id.WEBGL_compressed_texture_pvrtc) != null;
    }

    public boolean supportsBlendMinMax() {
        return WebGLExtensions.get(this.gl, Id.EXT_blend_minmax) != null;
    }

    public int getMaxAnisotropy() {
        WebGLExtension extension = WebGLExtensions.get(this.gl, Id.EXT_texture_filter_anisotropic);

        return extension != null ? getGL().getParameteri(ExtTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT)
                : 0;
    }

    public Shader.PRECISION getPrecision() {
        return this._precision;
    }

    /**
     * Gets {@link #setAutoClearColor(boolean)} flag.
     */
    public boolean isAutoClearColor() {
        return autoClearColor;
    }

    /**
     * Defines whether the renderer should clear the color buffer. 
     * Default is true.
     * 
     * @param isAutoClearColor false or true
     */
    public void setAutoClearColor(boolean isAutoClearColor) {
        this.autoClearColor = isAutoClearColor;
    }

    /**
     * Gets {@link #setAutoClearDepth(boolean)} flag.
     */
    public boolean isAutoClearDepth() {
        return autoClearDepth;
    }

    /**
     * Defines whether the renderer should clear the depth buffer. 
     * Default is true.
     * 
     * @param isAutoClearDepth false or true
     */
    public void setAutoClearDepth(boolean isAutoClearDepth) {
        this.autoClearDepth = isAutoClearDepth;
    }

    /**
     * Gets {@link #setAutoClearStencil(boolean)} flag.
     */
    public boolean isAutoClearStencil() {
        return autoClearStencil;
    }

    /**
     * Defines whether the renderer should clear the stencil buffer. 
     * Default is true.
     * 
     * @param isAutoClearStencil false or true
     */
    public void setAutoClearStencil(boolean isAutoClearStencil) {
        this.autoClearStencil = isAutoClearStencil;
    }

    /**
     * Gets {@link #setSortObjects(boolean)} flag.
     */
    public boolean isSortObjects() {
        return sortObjects;
    }

    /**
     * Defines whether the renderer should sort objects. 
     * Default is true.
     * 
     * @param isSortObjects false or true
     */
    public void setSortObjects(boolean isSortObjects) {
        this.sortObjects = isSortObjects;
    }

    /**
     * Gets {@link #setAutoUpdateObjects(boolean)} flag.
     */
    public boolean isAutoUpdateObjects() {
        return isAutoUpdateObjects;
    }

    /**
     * Defines whether the renderer should auto update objects. 
     * Default is true.
     * 
     * @param isAutoUpdateObjects false or true
     */
    public void setAutoUpdateObjects(boolean isAutoUpdateObjects) {
        this.isAutoUpdateObjects = isAutoUpdateObjects;
    }

    /**
     * Gets {@link #setAutoUpdateScene(boolean)} flag.
     */
    public boolean isAutoUpdateScene() {
        return isAutoUpdateScene;
    }

    public boolean isGammaInput() {
        return this.gammaInput;
    }

    public void setGammaInput(boolean isGammaInput) {
        this.gammaInput = isGammaInput;
    }

    public boolean isGammaOutput() {
        return this.gammaOutput;
    }

    public void setGammaOutput(boolean isGammaOutput) {
        this.gammaOutput = isGammaOutput;
    }

    /**
     * Defines whether the renderer should auto update the scene.
     * Default is true.
     * 
     * @param isAutoUpdateScene false or true
     */
    public void setAutoUpdateScene(boolean isAutoUpdateScene) {
        this.isAutoUpdateScene = isAutoUpdateScene;
    }

    /**
     * Gets {@link WebGlRendererInfo} instance with debug information.
     * 
     * @return the {@link WebGlRendererInfo} instance
     */
    public WebGlRendererInfo getInfo() {
        return info;
    }

    private void setInfo(WebGlRendererInfo info) {
        this.info = info;
    }

    /**
     * Gets the WebGL context from the {@link Canvas3d} widget.
     * 
     * @return the underlying context implementation for drawing onto the
     *         {@link Canvas3d}.
     */
    public WebGLRenderingContext getGL() {
        return this.gl;
    }

    private void setDefaultGLState() {
        getGL().clearColor(0.0, 0.0, 0.0, 1.0);
        getGL().clearDepth(1);
        getGL().clearStencil(0);

        getGL().enable(EnableCap.DEPTH_TEST);
        getGL().depthFunc(DepthFunction.LEQUAL);

        getGL().frontFace(FrontFaceDirection.CCW);
        getGL().cullFace(CullFaceMode.BACK);
        getGL().enable(EnableCap.CULL_FACE);

        getGL().enable(EnableCap.BLEND);
        getGL().blendEquation(BlendEquationMode.FUNC_ADD);
        getGL().blendFunc(BlendingFactorSrc.SRC_ALPHA, BlendingFactorDest.ONE_MINUS_SRC_ALPHA);

        getGL().viewport(_viewportX, _viewportY, _viewportWidth, _viewportHeight);
        getGL().clearColor(clearColor.getR(), clearColor.getG(), clearColor.getB(), clearAlpha);
    }

    /**
     * Return a Boolean true if the context supports vertex textures.
     */

    /**
     * Sets the sizes and also sets {@link #setViewport(int, int, int, int)} size.
     * 
     * @param width  the {@link Canvas3d} width.
     * @param height the {@link Canvas3d} height.
     */
    @Override
    public void setSize(int width, int height) {
        super.setSize(width, height);

        setViewport(0, 0, width, height);

        EVENT_BUS.fireEvent(new ViewportResizeEvent(this));
    }

    /**
     * Sets the viewport to render from (X, Y) to (X + absoluteWidth, Y + absoluteHeight).
     * By default X and Y = 0.
     */
    public void setViewport(int x, int y, int width, int height) {
        this._viewportX = x;
        this._viewportY = y;

        this._viewportWidth = width;
        this._viewportHeight = height;

        getGL().viewport(this._viewportX, this._viewportY, this._viewportWidth, this._viewportHeight);
    }

    /**
     * Sets the scissor area from (x, y) to (x + absoluteWidth, y + absoluteHeight).
     */
    public void setScissor(int x, int y, int width, int height) {
        getGL().scissor(x, y, width, height);
    }

    /**
     * Enable the scissor test. When this is enabled, only the pixels 
     * within the defined scissor area will be affected by further 
     * renderer actions.
     */
    public void enableScissorTest(boolean enable) {
        if (enable)
            getGL().enable(EnableCap.SCISSOR_TEST);
        else
            getGL().disable(EnableCap.SCISSOR_TEST);
    }

    @Override
    public void setClearColor(Color color, double alpha) {
        this.clearColor.copy(color);
        this.clearAlpha = alpha;

        getGL().clearColor(this.clearColor.getR(), this.clearColor.getG(), this.clearColor.getB(), this.clearAlpha);
    }

    @Override
    public void clear() {
        clear(true, true, true);
    }

    /**
     * Tells the renderer to clear its color, depth or stencil drawing buffer(s).
     * If no parameters are passed, no buffer will be cleared.
     * 
     * @param color   is it should clear color
     * @param depth   is it should clear depth
     * @param stencil is it should clear stencil
     */
    public void clear(boolean color, boolean depth, boolean stencil) {
        int bits = 0;

        if (color)
            bits |= ClearBufferMask.COLOR_BUFFER_BIT.getValue();
        if (depth)
            bits |= ClearBufferMask.DEPTH_BUFFER_BIT.getValue();
        if (stencil)
            bits |= ClearBufferMask.STENCIL_BUFFER_BIT.getValue();

        getGL().clear(bits);
    }

    public void clearColor() {
        getGL().clear(ClearBufferMask.COLOR_BUFFER_BIT.getValue());
    }

    public void clearDepth() {
        getGL().clear(ClearBufferMask.DEPTH_BUFFER_BIT.getValue());
    }

    public void clearStencil() {
        getGL().clear(ClearBufferMask.STENCIL_BUFFER_BIT.getValue());
    }

    /**
     * Clear {@link RenderTargetTexture} and GL buffers.
     */
    public void clearTarget(RenderTargetTexture renderTarget, boolean color, boolean depth, boolean stencil) {
        setRenderTarget(renderTarget);
        clear(color, depth, stencil);
    }

    public void resetGLState() {
        _currentProgram = null;
        _currentCamera = null;

        _oldBlending = null;
        _oldDepthTest = null;
        _oldDepthWrite = null;
        _oldDoubleSided = null;
        _oldFlipSided = null;
        _currentGeometryGroupHash = -1;
        _currentMaterialId = -1;

        _lightsNeedUpdate = true;
    }

    private void initAttributes() {

        for (int i = 0, l = _newAttributes.getLength(); i < l; i++) {

            _newAttributes.set(i, 0);

        }

    }

    private void enableAttribute(Integer attribute) {

        _newAttributes.set(attribute, 1);

        if (_enabledAttributes.get(attribute) == 0) {

            getGL().enableVertexAttribArray(attribute);
            _enabledAttributes.set(attribute, 1);

        }

    }

    private void disableUnusedAttributes() {

        for (int i = 0, l = _enabledAttributes.getLength(); i < l; i++) {

            if (_enabledAttributes.get(i) != _newAttributes.get(i)) {

                getGL().disableVertexAttribArray(i);
                _enabledAttributes.set(i, 0);

            }

        }

    }

    /**
     * Morph Targets Buffer initialization
     */
    private void setupMorphTargets(Material material, WebGLGeometry geometrybuffer, Mesh object) {

        // set base
        Map<String, Integer> attributes = material.getShader().getAttributesLocations();
        Map<String, Uniform> uniforms = material.getShader().getUniforms();

        if (object.morphTargetBase != -1 && attributes.get("position") >= 0) {
            getGL().bindBuffer(BufferTarget.ARRAY_BUFFER,
                    geometrybuffer.__webglMorphTargetsBuffers.get(object.morphTargetBase));
            enableAttribute(attributes.get("position"));
            getGL().vertexAttribPointer(attributes.get("position"), 3, DataType.FLOAT, false, 0, 0);

        } else if (attributes.get("position") >= 0) {
            getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometrybuffer.__webglVertexBuffer);
            enableAttribute(attributes.get("position"));
            getGL().vertexAttribPointer(attributes.get("position"), 3, DataType.FLOAT, false, 0, 0);
        }

        if (object.morphTargetForcedOrder.size() > 0) {
            // set forced order

            int m = 0;
            List<Integer> order = object.morphTargetForcedOrder;
            List<Double> influences = object.morphTargetInfluences;

            while (material instanceof HasSkinning && m < ((HasSkinning) material).getNumSupportedMorphTargets()
                    && m < order.size()) {
                if (attributes.get("morphTarget" + m) >= 0) {
                    gl.bindBuffer(BufferTarget.ARRAY_BUFFER,
                            geometrybuffer.__webglMorphTargetsBuffers.get(order.get(m)));
                    enableAttribute(attributes.get("morphTarget" + m));
                    gl.vertexAttribPointer(attributes.get("morphTarget" + m), 3, DataType.FLOAT, false, 0, 0);

                }

                if (attributes.get("morphNormal" + m) >= 0 && material instanceof HasSkinning
                        && ((HasSkinning) material).isMorphNormals()) {
                    gl.bindBuffer(BufferTarget.ARRAY_BUFFER,
                            geometrybuffer.__webglMorphNormalsBuffers.get(order.get(m)));
                    enableAttribute(attributes.get("morphNormal" + m));
                    gl.vertexAttribPointer(attributes.get("morphNormal" + m), 3, DataType.FLOAT, false, 0, 0);
                }

                object.__webglMorphTargetInfluences.set(m, influences.get(order.get(m)));

                m++;
            }
        } else {
            // find most influencing

            List<Double> influences = object.morphTargetInfluences;
            List<Double[]> activeInfluenceIndices = new ArrayList<Double[]>();

            for (int i = 0; i < influences.size(); i++) {

                double influence = influences.get(i);

                if (influence > 0) {

                    Double[] tmp = new Double[] { influence, (double) i };
                    activeInfluenceIndices.add(tmp);

                }

            }

            if (activeInfluenceIndices.size() > ((HasSkinning) material).getNumSupportedMorphTargets()) {

                Collections.sort(activeInfluenceIndices, new Comparator<Double[]>() {
                    public int compare(Double[] a, Double[] b) {
                        return (int) (b[0] - a[0]);
                    }
                });

            } else if (activeInfluenceIndices.size() > ((HasSkinning) material).getNumSupportedMorphNormals()) {

                Collections.sort(activeInfluenceIndices, new Comparator<Double[]>() {
                    public int compare(Double[] a, Double[] b) {
                        return (int) (b[0] - a[0]);
                    }
                });

            } else if (activeInfluenceIndices.size() == 0) {

                activeInfluenceIndices.add(new Double[] { 0.0, 0.0 });

            }

            int influenceIndex;
            int m = 0;

            while (material instanceof HasSkinning && m < ((HasSkinning) material).getNumSupportedMorphTargets()) {

                if (activeInfluenceIndices.size() > m && activeInfluenceIndices.get(m) != null) {
                    influenceIndex = activeInfluenceIndices.get(m)[1].intValue();

                    if (attributes.get("morphTarget" + m) >= 0) {

                        gl.bindBuffer(BufferTarget.ARRAY_BUFFER,
                                geometrybuffer.__webglMorphTargetsBuffers.get(influenceIndex));
                        enableAttribute(attributes.get("morphTarget" + m));
                        gl.vertexAttribPointer(attributes.get("morphTarget" + m), 3, DataType.FLOAT, false, 0, 0);

                    }

                    if (attributes.get("morphNormal" + m) >= 0 && ((HasSkinning) material).isMorphNormals()) {

                        gl.bindBuffer(BufferTarget.ARRAY_BUFFER,
                                geometrybuffer.__webglMorphNormalsBuffers.get(influenceIndex));
                        enableAttribute(attributes.get("morphNormal" + m));
                        gl.vertexAttribPointer(attributes.get("morphNormal" + m), 3, DataType.FLOAT, false, 0, 0);

                    }

                    object.__webglMorphTargetInfluences.set(m, influences.get(influenceIndex));

                } else {

                    /*
                    _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
                        
                    if ( material.morphNormals ) {
                        
                       _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
                        
                    }
                    */

                    object.__webglMorphTargetInfluences.set(m, 0);

                }

                m++;
            }
        }

        // load updated influences uniform
        if (uniforms.get("morphTargetInfluences").getLocation() != null) {
            Float32Array vals = object.__webglMorphTargetInfluences;
            getGL().uniform1fv(uniforms.get("morphTargetInfluences").getLocation(), vals);
        }
    }

    public void renderBufferImmediate(GeometryObject object, Shader program, Material material) {

        initAttributes();
        //
        //      if ( object.hasPositions && ! object.__webglVertexBuffer ) object.__webglVertexBuffer = getGL().createBuffer();
        //      if ( object.hasNormals && ! object.__webglNormalBuffer ) object.__webglNormalBuffer = getGL().createBuffer();
        //      if ( object.hasUvs && ! object.__webglUvBuffer ) object.__webglUvBuffer = getGL().createBuffer();
        //      if ( object.hasColors && ! object.__webglColorBuffer ) object.__webglColorBuffer = getGL().createBuffer();
        //
        //      if ( object.hasPositions )
        //      {
        //
        //         getGL().bindBuffer( BufferTarget.ARRAY_BUFFER, object.__webglVertexBuffer );
        //         getGL().bufferData( BufferTarget.ARRAY_BUFFER, object.positionArray, BufferUsage.DYNAMIC_DRAW );
        //         enableAttribute( program.attributes.position );
        //         getGL().vertexAttribPointer( program.attributes.position, 3, getGL().FLOAT, false, 0, 0 );
        //
        //      }
        //
        //      if ( object.hasNormals ) {
        //
        //         getGL().bindBuffer( BufferTarget.ARRAY_BUFFER, object.__webglNormalBuffer );
        //
        //         if (((HasShading)material).getShading() == Material.SHADING.FLAT ) {
        //
        //            var nx, ny, nz,
        //               nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz,
        //               normalArray,
        //               i, il = object.count * 3;
        //
        //            for ( int i = 0; i < il; i += 9 ) {
        //
        //               normalArray = object.normalArray;
        //
        //               nax  = normalArray[ i ];
        //               nay  = normalArray[ i + 1 ];
        //               naz  = normalArray[ i + 2 ];
        //
        //               nbx  = normalArray[ i + 3 ];
        //               nby  = normalArray[ i + 4 ];
        //               nbz  = normalArray[ i + 5 ];
        //
        //               ncx  = normalArray[ i + 6 ];
        //               ncy  = normalArray[ i + 7 ];
        //               ncz  = normalArray[ i + 8 ];
        //
        //               nx = ( nax + nbx + ncx ) / 3;
        //               ny = ( nay + nby + ncy ) / 3;
        //               nz = ( naz + nbz + ncz ) / 3;
        //
        //               normalArray[ i ]   = nx;
        //               normalArray[ i + 1 ] = ny;
        //               normalArray[ i + 2 ] = nz;
        //
        //               normalArray[ i + 3 ] = nx;
        //               normalArray[ i + 4 ] = ny;
        //               normalArray[ i + 5 ] = nz;
        //
        //               normalArray[ i + 6 ] = nx;
        //               normalArray[ i + 7 ] = ny;
        //               normalArray[ i + 8 ] = nz;
        //
        //            }
        //
        //         }
        //
        //         getGL().bufferData( BufferTarget.ARRAY_BUFFER, object.normalArray, BufferUsage.DYNAMIC_DRAW );
        //         enableAttribute( program.attributes.normal );
        //         getGL().vertexAttribPointer( program.attributes.normal, 3, getGL().FLOAT, false, 0, 0 );
        //
        //      }
        //
        //      if ( object.hasUvs && material.map ) {
        //
        //         getGL().bindBuffer( BufferTarget.ARRAY_BUFFER, object.__webglUvBuffer );
        //         getGL().bufferData( BufferTarget.ARRAY_BUFFER, object.uvArray, BufferUsage.DYNAMIC_DRAW );
        //         enableAttribute( program.attributes.uv );
        //         getGL().vertexAttribPointer( program.attributes.uv, 2, DataType.FLOAT, false, 0, 0 );
        //
        //      }
        //
        //      if ( object.hasColors && ((HasVertexColors)material).isVertexColors != Material.COLORS.NO ) {
        //
        //         getGL().bindBuffer( BufferTarget.ARRAY_BUFFER, object.__webglColorBuffer );
        //         getGL().bufferData( BufferTarget.ARRAY_BUFFER, object.colorArray, BufferUsage.DYNAMIC_DRAW );
        //         enableAttribute( program.attributes.color );
        //         getGL().vertexAttribPointer( program.attributes.color, 3, DataType.FLOAT, false, 0, 0 );
        //
        //      }
        //
        //      disableUnusedAttributes();
        //
        //      getGL().drawArrays( BeginMode.TRIANGLES, 0, object.count );
        //
        //      object.count = 0;

    }

    private void setupVertexAttributes(Material material, Shader program, BufferGeometry geometry, int startIndex) {

        Map<String, BufferAttribute> geometryAttributes = geometry.getAttributes();

        Map<String, Integer> programAttributes = program.getAttributesLocations();

        for (String key : programAttributes.keySet()) {

            Integer programAttribute = programAttributes.get(key);

            if (programAttribute >= 0) {

                BufferAttribute geometryAttribute = geometryAttributes.get(key);

                if (geometryAttribute != null) {

                    int size = geometryAttribute.getItemSize();

                    gl.bindBuffer(BufferTarget.ARRAY_BUFFER, geometryAttribute.getBuffer());

                    enableAttribute(programAttribute);

                    gl.vertexAttribPointer(programAttribute, size, DataType.FLOAT, false, 0, startIndex * size * 4); // 4 bytes per Float32

                }
                //            else if ( material.defaultAttributeValues != null ) {
                //
                //               if ( material.defaultAttributeValues[ key ].length === 2 ) {
                //
                //                  gl.vertexAttrib2fv( programAttribute, material.defaultAttributeValues[ key ] );
                //
                //               } else if ( material.defaultAttributeValues[ key ].length === 3 ) {
                //
                //                  gl.vertexAttrib3fv( programAttribute, material.defaultAttributeValues[ key ] );
                //
                //               }
                //
                //            }

            }

        }

        disableUnusedAttributes();

    }

    //camera, lights, fog, material, geometry, object
    public void renderBufferDirect(Camera camera, List<Light> lights, AbstractFog fog, Material material,
            BufferGeometry geometry, GeometryObject object) {
        if (!material.isVisible())
            return;

        Shader program = setProgram(camera, lights, fog, material, object);

        Map<String, Integer> attributes = material.getShader().getAttributesLocations();

        boolean updateBuffers = false;
        int wireframeBit = material instanceof HasWireframe && ((HasWireframe) material).isWireframe() ? 1 : 0;

        int geometryGroupHash = (geometry.getId() * 0xffffff) + (material.getShader().getId() * 2) + wireframeBit;

        if (geometryGroupHash != this._currentGeometryGroupHash) {
            this._currentGeometryGroupHash = geometryGroupHash;
            updateBuffers = true;
        }

        if (updateBuffers) {

            initAttributes();

        }

        WebGLRenderingContext gl = getGL();

        // render mesh

        if (object instanceof Mesh) {
            BeginMode mode = material instanceof HasWireframe && ((HasWireframe) material).isWireframe()
                    ? BeginMode.LINES
                    : BeginMode.TRIANGLES;

            BufferAttribute index = geometry.getAttribute("index");

            if (index != null) {
                DrawElementsType type = DrawElementsType.UNSIGNED_SHORT;
                int size = 2;

                List<BufferGeometry.DrawCall> offsets = geometry.getDrawcalls();

                if (offsets.size() == 0) {

                    if (updateBuffers) {

                        setupVertexAttributes(material, program, geometry, 0);

                        getGL().bindBuffer(BufferTarget.ELEMENT_ARRAY_BUFFER, index.getBuffer());

                    }

                    getGL().drawElements(mode, index.getArray().getLength(), type, 0);

                    this.info.getRender().calls++;
                    this.info.getRender().vertices += index.getArray().getLength(); // not really true, here vertices can be shared
                    this.info.getRender().faces += index.getArray().getLength() / 3;

                } else {
                    // if there is more than 1 chunk
                    // must set attribute pointers to use new offsets for each chunk
                    // even if geometry and materials didn't change

                    updateBuffers = true;

                    for (int i = 0, il = offsets.size(); i < il; ++i) {

                        int startIndex = offsets.get(i).index;

                        if (updateBuffers) {

                            setupVertexAttributes(material, program, geometry, startIndex);
                            getGL().bindBuffer(BufferTarget.ELEMENT_ARRAY_BUFFER, index.getBuffer());

                        }

                        gl.drawElements(mode, offsets.get(i).count, type, offsets.get(i).start * size);

                        getInfo().getRender().calls++;
                        getInfo().getRender().vertices += offsets.get(i).count; // not really true, here vertices can be shared
                        getInfo().getRender().faces += offsets.get(i).count / 3;
                    }

                }

            } else {

                // non-indexed triangles

                if (updateBuffers) {

                    setupVertexAttributes(material, program, geometry, 0);

                }

                BufferAttribute position = geometry.getAttribute("position");

                // render non-indexed triangles

                gl.drawArrays(mode, 0, position.getArray().getLength() / 3);

                this.info.getRender().calls++;
                this.info.getRender().vertices += position.getArray().getLength() / 3;
                this.info.getRender().faces += position.getArray().getLength() / 9;

            }

        } else if (object instanceof PointCloud) {
            // render particles

            if (updateBuffers) {

                setupVertexAttributes(material, program, geometry, 0);

            }

            BufferAttribute position = geometry.getAttribute("position");

            // render particles

            gl.drawArrays(BeginMode.POINTS, 0, position.getArray().getLength() / 3);

            this.info.getRender().calls++;
            this.info.getRender().points += position.getArray().getLength() / 3;
        } else if (object instanceof Line) {

            BeginMode mode = (((Line) object).getMode() == Line.MODE.STRIPS) ? BeginMode.LINE_STRIP
                    : BeginMode.LINES;
            object.setLineWidth(gl, ((LineBasicMaterial) material).getLinewidth());

            BufferAttribute index = geometry.getAttribute("index");

            if (index != null) {

                // indexed lines

                //            var type, size;

                //            if ( index.array instanceof Uint32Array ) {
                //
                //               type = _gl.UNSIGNED_INT;
                //               size = 4;
                //
                //            } else {

                DrawElementsType type = DrawElementsType.UNSIGNED_SHORT;
                int size = 2;

                //            }

                List<DrawCall> drawcalls = geometry.getDrawcalls();

                if (drawcalls.size() == 0) {

                    if (updateBuffers) {

                        setupVertexAttributes(material, program, geometry, 0);
                        gl.bindBuffer(BufferTarget.ELEMENT_ARRAY_BUFFER, index.getBuffer());

                    }

                    gl.drawElements(mode, index.getArray().getLength(), type, 0); // 2 bytes per Uint16Array

                    this.info.getRender().calls++;
                    this.info.getRender().vertices += index.getArray().getLength(); // not really true, here vertices can be shared

                } else {

                    // if there is more than 1 chunk
                    // must set attribute pointers to use new offsets for each chunk
                    // even if geometry and materials didn't change

                    if (drawcalls.size() > 1)
                        updateBuffers = true;

                    for (int i = 0, il = drawcalls.size(); i < il; i++) {

                        int startIndex = drawcalls.get(i).index;

                        if (updateBuffers) {

                            setupVertexAttributes(material, program, geometry, startIndex);
                            gl.bindBuffer(BufferTarget.ELEMENT_ARRAY_BUFFER, index.getBuffer());

                        }

                        // render indexed lines

                        gl.drawElements(mode, drawcalls.get(i).count, type, drawcalls.get(i).start * size); // 2 bytes per Uint16Array

                        this.info.getRender().calls++;
                        this.info.getRender().vertices += drawcalls.get(i).count; // not really true, here vertices can be shared

                    }

                }

            } else {

                // non-indexed lines

                if (updateBuffers) {

                    setupVertexAttributes(material, program, geometry, 0);

                }

                BufferAttribute position = geometry.getAttribute("position");

                gl.drawArrays(mode, 0, position.getArray().getLength() / 3);

                this.info.getRender().calls++;
                this.info.getRender().points += position.getArray().getLength() / 3;

            }

        }
    }

    public List<GeometryGroup> makeGroups(Geometry geometry, boolean usesFaceMaterial) {

        long maxVerticesInGroup = WebGLExtensions.get(gl, WebGLExtensions.Id.OES_element_index_uint) != null
                ? 4294967296L
                : 65535L;

        int numMorphTargets = geometry.getMorphTargets().size();
        int numMorphNormals = geometry.getMorphNormals().size();

        String groupHash;

        Map<String, Integer> hash_map = GWT.isScript() ? new FastMap<Integer>() : new HashMap<String, Integer>();

        Map<String, GeometryGroup> groups = GWT.isScript() ? new FastMap<GeometryGroup>()
                : new HashMap<String, GeometryGroup>();

        List<GeometryGroup> groupsList = new ArrayList<GeometryGroup>();

        for (int f = 0, fl = geometry.getFaces().size(); f < fl; f++) {

            Face3 face = geometry.getFaces().get(f);
            Integer materialIndex = usesFaceMaterial ? face.getMaterialIndex() : 0;

            if (!hash_map.containsKey(materialIndex)) {

                hash_map.put(materialIndex.toString(), 0);

            }

            groupHash = materialIndex + "_" + hash_map.get(materialIndex);

            if (!groups.containsKey(groupHash)) {

                GeometryGroup group = new GeometryGroup(materialIndex, numMorphTargets, numMorphNormals);

                groups.put(groupHash, group);
                groupsList.add(group);

            }

            if (groups.get(groupHash).getVertices() + 3 > maxVerticesInGroup) {

                hash_map.put(materialIndex.toString(), hash_map.get(materialIndex) + 1);
                groupHash = materialIndex + "_" + hash_map.get(materialIndex);

                if (!groups.containsKey(groupHash)) {

                    GeometryGroup group = new GeometryGroup(materialIndex, numMorphTargets, numMorphNormals);
                    groups.put(groupHash, group);
                    groupsList.add(group);

                }

            }

            groups.get(groupHash).getFaces3().add(f);
            groups.get(groupHash).setVertices(groups.get(groupHash).getVertices() + 3);

        }

        return groupsList;

    }

    public void initGeometryGroups(Object3D scene, Mesh object, Geometry geometry) {

        Material material = object.getMaterial();
        boolean addBuffers = false;

        if (GeometryGroup.geometryGroups.get(geometry.getId() + "") == null || geometry.isGroupsNeedUpdate()) {

            this._webglObjects.put(object.getId() + "", new ArrayList<WebGLObject>());

            GeometryGroup.geometryGroups.put(geometry.getId() + "",
                    makeGroups(geometry, material instanceof MeshFaceMaterial));

            geometry.setGroupsNeedUpdate(false);

        }

        List<GeometryGroup> geometryGroupsList = GeometryGroup.geometryGroups.get(geometry.getId() + "");

        // create separate VBOs per geometry chunk

        for (int i = 0, il = geometryGroupsList.size(); i < il; i++) {

            GeometryGroup geometryGroup = geometryGroupsList.get(i);

            // initialise VBO on the first access

            if (geometryGroup.__webglVertexBuffer == null) {

                ((Mesh) object).createBuffers(this, geometryGroup);
                ((Mesh) object).initBuffers(gl, geometryGroup);

                geometry.setVerticesNeedUpdate(true);
                geometry.setMorphTargetsNeedUpdate(true);
                geometry.setElementsNeedUpdate(true);
                geometry.setUvsNeedUpdate(true);
                geometry.setNormalsNeedUpdate(true);
                geometry.setTangentsNeedUpdate(true);
                geometry.setColorsNeedUpdate(true);

                addBuffers = true;

            } else {

                addBuffers = false;

            }

            if (addBuffers || !object.__webglActive) {

                addBuffer(geometryGroup, object);

            }

        }

        object.__webglActive = true;

    }

    private void initObject(Object3D object, Object3D scene) {

        if (!object.__webglInit) {

            object.__webglInit = true;
            object._modelViewMatrix = new Matrix4();
            object._normalMatrix = new Matrix3();

        }

        AbstractGeometry geometry = object instanceof GeometryObject ? ((GeometryObject) object).getGeometry()
                : null;

        if (geometry == null) {

            // ImmediateRenderObject

        } else if (!geometry.__webglInit) {

            geometry.__webglInit = true;
            //         geometry.addEventListener( 'dispose', onGeometryDispose );

            if (geometry instanceof BufferGeometry) {

                //

            } else if (object instanceof Mesh) {

                initGeometryGroups(scene, (Mesh) object, (Geometry) geometry);

            } else if (object instanceof Line) {

                if (geometry.__webglVertexBuffer == null) {

                    ((Line) object).createBuffers(this);
                    ((Line) object).initBuffers(gl);

                    geometry.setVerticesNeedUpdate(true);
                    geometry.setColorsNeedUpdate(true);
                    geometry.setLineDistancesNeedUpdate(true);

                }

            } else if (object instanceof PointCloud) {

                if (geometry.__webglVertexBuffer == null) {

                    ((PointCloud) object).createBuffers(this);
                    ((PointCloud) object).initBuffers(gl);

                    geometry.setVerticesNeedUpdate(true);
                    geometry.setColorsNeedUpdate(true);

                }

            }

        }

        if (!object.__webglActive) {

            object.__webglActive = true;

            if (object instanceof Mesh) {

                if (geometry instanceof BufferGeometry) {

                    addBuffer(geometry, (GeometryObject) object);

                } else if (geometry instanceof Geometry) {

                    List<GeometryGroup> geometryGroupsList = GeometryGroup.geometryGroups
                            .get(geometry.getId() + "");

                    for (int i = 0, l = geometryGroupsList.size(); i < l; i++) {

                        addBuffer(geometryGroupsList.get(i), (GeometryObject) object);

                    }

                }

            } else if (object instanceof Line || object instanceof PointCloud) {

                addBuffer(geometry, (GeometryObject) object);

            } /*else if ( object instanceof ImmediateRenderObject || object.immediateRenderCallback ) {
                  
               addBufferImmediate( _webglObjectsImmediate, object );
                  
              }*/

        }

    }

    private void addBuffer(WebGLGeometry buffer, GeometryObject object) {

        int id = object.getId();
        List<WebGLObject> list = _webglObjects.get(id + "");
        if (list == null) {
            list = new ArrayList<WebGLObject>();
            _webglObjects.put(id + "", list);
        }

        WebGLObject webGLObject = new WebGLObject(buffer, object);
        webGLObject.id = id;
        list.add(webGLObject);
    }

    private void projectObject(Object3D scene, Object3D object) {

        if (object.isVisible() == false)
            return;

        if (object instanceof Scene /*|| object instanceof Group */) {

            // skip

        } else {

            if (!(object instanceof Light))
                initObject(object, scene);

            if (object instanceof Light) {

                lights.add((Light) object);

            } /*else if ( object instanceof Sprite ) {
                  
               sprites.push( object );
                  
              } else if ( object instanceof LensFlare ) {
                  
               lensFlares.push( object );
                  
              } */else {

                List<WebGLObject> webglObjects = this._webglObjects.get(object.getId() + "");

                if (webglObjects != null && (object.isFrustumCulled() == false
                        || _frustum.isIntersectsObject((GeometryObject) object) == true)) {

                    updateObject((GeometryObject) object, scene);

                    for (int i = 0, l = webglObjects.size(); i < l; i++) {

                        WebGLObject webglObject = webglObjects.get(i);

                        webglObject.unrollBufferMaterial(this);

                        webglObject.render = true;

                        if (this.sortObjects == true) {

                            if (object.getRenderDepth() > 0) {

                                webglObject.z = object.getRenderDepth();

                            } else {

                                _vector3.setFromMatrixPosition(object.getMatrixWorld());
                                _vector3.applyProjection(_projScreenMatrix);

                                webglObject.z = _vector3.getZ();

                            }

                        }

                    }

                }

            }

        }

        for (int i = 0, l = object.getChildren().size(); i < l; i++) {

            projectObject(scene, object.getChildren().get(i));

        }

    }

    public Material getBufferMaterial(GeometryObject object, GeometryGroup geometryGroup) {

        return object.getMaterial() instanceof MeshFaceMaterial
                ? ((MeshFaceMaterial) object.getMaterial()).getMaterials().get(geometryGroup.getMaterialIndex())
                : object.getMaterial();

    }

    public Material getBufferMaterial(GeometryObject object, Geometry geometry) {

        return object.getMaterial();

    }

    public void updateObject(GeometryObject object, Object3D scene) {
        AbstractGeometry geometry = object.getGeometry();

        Material material = null;

        if (geometry instanceof BufferGeometry) {

            ((BufferGeometry) geometry).setDirectBuffers(gl);

        } else if (object instanceof Mesh) {

            // check all geometry groups

            if (geometry.isGroupsNeedUpdate()) {

                initGeometryGroups(scene, (Mesh) object, (Geometry) geometry);

            }

            List<GeometryGroup> geometryGroupsList = GeometryGroup.geometryGroups.get(geometry.getId() + "");

            for (int i = 0, il = geometryGroupsList.size(); i < il; i++) {

                GeometryGroup geometryGroup = geometryGroupsList.get(i);

                material = getBufferMaterial(object, geometryGroup);

                if (geometry.isGroupsNeedUpdate()) {

                    ((Mesh) object).initBuffers(gl, geometryGroup);

                }

                boolean customAttributesDirty = (material instanceof ShaderMaterial)
                        && ((ShaderMaterial) material).getShader().areCustomAttributesDirty();

                if (geometry.isVerticesNeedUpdate() || geometry.isMorphTargetsNeedUpdate()
                        || geometry.isElementsNeedUpdate() || geometry.isUvsNeedUpdate()
                        || geometry.isNormalsNeedUpdate() || geometry.isColorsNeedUpdate()
                        || geometry.isTangentsNeedUpdate() || customAttributesDirty) {

                    ((Mesh) object).setBuffers(gl, geometryGroup, BufferUsage.DYNAMIC_DRAW,
                            !((Geometry) geometry).isDynamic(), material);

                }

            }

            geometry.setVerticesNeedUpdate(false);
            geometry.setMorphTargetsNeedUpdate(false);
            geometry.setElementsNeedUpdate(false);
            geometry.setUvsNeedUpdate(false);
            geometry.setNormalsNeedUpdate(false);
            geometry.setColorsNeedUpdate(false);
            geometry.setTangentsNeedUpdate(false);

            if (material instanceof ShaderMaterial) {
                ((ShaderMaterial) material).getShader().clearCustomAttributes();
            }

        } else if (object instanceof Line) {

            material = getBufferMaterial(object, (Geometry) geometry);

            boolean customAttributesDirty = (material instanceof ShaderMaterial)
                    && ((ShaderMaterial) material).getShader().areCustomAttributesDirty();

            if (geometry.isVerticesNeedUpdate() || geometry.isColorsNeedUpdate()
                    || geometry.isLineDistancesNeedUpdate() || customAttributesDirty) {

                ((Line) object).setBuffers(gl, BufferUsage.DYNAMIC_DRAW);

            }

            geometry.setVerticesNeedUpdate(false);
            geometry.setColorsNeedUpdate(false);
            geometry.setLineDistancesNeedUpdate(false);

            if (material instanceof ShaderMaterial) {
                ((ShaderMaterial) material).getShader().clearCustomAttributes();
            }

        } else if (object instanceof PointCloud) {

            material = getBufferMaterial(object, (Geometry) geometry);

            boolean customAttributesDirty = (material instanceof ShaderMaterial)
                    && ((ShaderMaterial) material).getShader().areCustomAttributesDirty();

            if (geometry.isVerticesNeedUpdate() || geometry.isColorsNeedUpdate()
                    || ((PointCloud) object).isSortParticles() || customAttributesDirty) {

                ((PointCloud) object).setBuffers(this, BufferUsage.DYNAMIC_DRAW);

            }

            geometry.setVerticesNeedUpdate(false);
            geometry.setColorsNeedUpdate(false);

            if (material instanceof ShaderMaterial) {
                ((ShaderMaterial) material).getShader().clearCustomAttributes();
            }

        }

    }

    @Override
    public void render(Scene scene, Camera camera) {
        render(scene, camera, null);
    }

    public void render(Scene scene, Camera camera, RenderTargetTexture renderTarget) {
        render(scene, camera, renderTarget, false);
    }

    /**
     * Rendering.
     * 
     * @param scene        the {@link Scene} object.
     * @param renderTarget optional
     * @param forceClear   optional
     */
    public void render(Scene scene, Camera camera, RenderTargetTexture renderTarget, boolean forceClear) {
        // Render basic plugins
        if (renderPlugins(this.plugins, scene, camera, Plugin.TYPE.BASIC_RENDER))
            return;

        Log.debug("Called render()");

        AbstractFog fog = scene.getFog();

        // reset caching for this frame
        this._currentGeometryGroupHash = -1;
        this._currentCamera = null;
        this._currentMaterialId = -1;
        this._lightsNeedUpdate = true;

        if (this.isAutoUpdateScene()) {
            scene.updateMatrixWorld(false);
        }

        // update camera matrices and frustum
        if (camera.getParent() == null) {
            camera.updateMatrixWorld(false);
        }

        camera.getMatrixWorldInverse().getInverse(camera.getMatrixWorld());

        _projScreenMatrix.multiply(camera.getProjectionMatrix(), camera.getMatrixWorldInverse());
        _frustum.setFromMatrix(_projScreenMatrix);

        this.lights = new ArrayList<Light>();
        this.opaqueObjects = new ArrayList<WebGLObject>();
        this.transparentObjects = new ArrayList<WebGLObject>();

        projectObject(scene, scene);

        if (this.isSortObjects()) {

            Collections.sort(opaqueObjects, new Comparator<WebGLObject>() {

                @Override
                public int compare(WebGLObject a, WebGLObject b) {
                    if (a.z != b.z) {

                        return (int) (b.z - a.z);

                    } else {

                        return a.id - b.id;

                    }

                }
            });

            Collections.sort(transparentObjects, new Comparator<WebGLObject>() {

                @Override
                public int compare(WebGLObject a, WebGLObject b) {
                    if (a.material.getId() != b.material.getId()) {

                        return a.material.getId() - b.material.getId();

                    } else if (a.z != b.z) {

                        return (int) (a.z - b.z);

                    } else {

                        return a.id - b.id;

                    }

                }
            });

        }

        // custom render plugins (pre pass)
        renderPlugins(this.plugins, scene, camera, Plugin.TYPE.PRE_RENDER);

        this.getInfo().getRender().calls = 0;
        this.getInfo().getRender().vertices = 0;
        this.getInfo().getRender().faces = 0;
        this.getInfo().getRender().points = 0;

        setRenderTarget(renderTarget);

        if (this.isAutoClear() || forceClear) {
            clear(this.isAutoClearColor(), this.isAutoClearDepth(), this.isAutoClearStencil());
        }

        // set matrices for immediate objects

        for (int i = 0, il = this._webglObjectsImmediate.size(); i < il; i++) {
            WebGLObject webglObject = this._webglObjectsImmediate.get(i);
            Object3D object = webglObject.object;

            if (object.isVisible()) {

                setupMatrices(object, camera);

                webglObject.unrollImmediateBufferMaterial();

            }

        }

        Log.debug("  -- render() overrideMaterial : " + (scene.getOverrideMaterial() != null) + ", lights: "
                + lights.size() + ", opaqueObjects: " + opaqueObjects.size() + ", transparentObjects: "
                + transparentObjects.size());

        if (scene.getOverrideMaterial() != null) {
            Material material = scene.getOverrideMaterial();

            setBlending(material.getBlending(), material.getBlendEquation(), material.getBlendSrc(),
                    material.getBlendDst());
            setDepthTest(material.isDepthTest());
            setDepthWrite(material.isDepthWrite());

            setPolygonOffset(material.isPolygonOffset(), material.getPolygonOffsetFactor(),
                    material.getPolygonOffsetUnits());

            renderObjects(opaqueObjects, camera, lights, fog, true, material);
            renderObjects(transparentObjects, camera, lights, fog, true, material);
            renderObjectsImmediate(_webglObjectsImmediate, null, camera, lights, fog, false, material);
        } else {
            Material material = null;

            // opaque pass (front-to-back order)
            setBlending(Material.BLENDING.NO);

            renderObjects(opaqueObjects, camera, lights, fog, false, material);
            renderObjectsImmediate(_webglObjectsImmediate, false, camera, lights, fog, false, material);

            // transparent pass (back-to-front order)
            renderObjects(transparentObjects, camera, lights, fog, true, material);
            renderObjectsImmediate(_webglObjectsImmediate, true, camera, lights, fog, true, material);
        }

        // custom render plugins (post pass)
        renderPlugins(this.plugins, scene, camera, Plugin.TYPE.POST_RENDER);

        // Generate mipmap if we're using any kind of mipmap filtering
        if (renderTarget != null && renderTarget.isGenerateMipmaps()
                && renderTarget.getMinFilter() != TextureMinFilter.NEAREST
                && renderTarget.getMinFilter() != TextureMinFilter.LINEAR) {
            renderTarget.updateRenderTargetMipmap(getGL());
        }

        // Ensure depth buffer writing is enabled so it can be cleared on next render

        this.setDepthTest(true);
        this.setDepthWrite(true);

        //       getGL().finish();
    }

    public void renderObjectsImmediate(List<WebGLObject> renderList, Boolean isTransparentMaterial, Camera camera,
            List<Light> lights, AbstractFog fog, boolean useBlending, Material overrideMaterial) {

        Material material = null;

        for (int i = 0, il = renderList.size(); i < il; i++) {

            WebGLObject webglObject = renderList.get(i);
            GeometryObject object = webglObject.object;

            if (object.isVisible()) {

                if (overrideMaterial != null) {

                    material = overrideMaterial;

                } else {

                    if (isTransparentMaterial != null)
                        material = isTransparentMaterial ? webglObject.transparent : webglObject.opaque;

                    if (material == null)
                        continue;

                    if (useBlending)
                        setBlending(material.getBlending(), material.getBlendEquation(), material.getBlendSrc(),
                                material.getBlendDst());

                    setDepthTest(material.isDepthTest());
                    setDepthWrite(material.isDepthWrite());
                    setPolygonOffset(material.isPolygonOffset(), material.getPolygonOffsetFactor(),
                            material.getPolygonOffsetUnits());

                }

                renderImmediateObject(camera, lights, fog, material, object);

            }

        }

    }

    public void renderImmediateObject(Camera camera, List<Light> lights, AbstractFog fog, Material material,
            GeometryObject object) {

        Shader program = setProgram(camera, lights, fog, material, object);

        this._currentGeometryGroupHash = -1;

        setMaterialFaces(material);

        //      if ( object.immediateRenderCallback ) {
        //
        //         object.immediateRenderCallback( program, _gl, _frustum );
        //
        //      } else {

        //         object.render( function ( object ) { _this.renderBufferImmediate( object, program, material ); } );
        renderBufferImmediate(object, program, material);

        //      }

    }

    private boolean renderPlugins(List<Plugin> plugins, Scene scene, Camera camera, Plugin.TYPE type) {
        if (plugins.size() == 0)
            return false;

        boolean retval = false;
        for (int i = 0, il = plugins.size(); i < il; i++) {
            Plugin plugin = plugins.get(i);

            if (!plugin.isEnabled() || plugin.isRendering() || plugin.getType() != type
                    || (!(plugin.isMulty()) && !plugin.getScene().equals(scene)))
                continue;

            plugin.setRendering(true);
            Log.debug("Called renderPlugins(): " + plugin.getClass().getName());

            // reset state for plugin (to start from clean slate)
            this._currentProgram = null;
            this._currentCamera = null;

            this._oldBlending = null;
            this._oldDepthTest = null;
            this._oldDepthWrite = null;
            this.cache_oldMaterialSided = null;

            this._currentGeometryGroupHash = -1;
            this._currentMaterialId = -1;

            this._lightsNeedUpdate = true;

            plugin.render(camera, lights, _currentWidth, _currentHeight);

            // reset state after plugin (anything could have changed)

            this._currentProgram = null;
            this._currentCamera = null;

            this._oldBlending = null;
            this._oldDepthTest = null;
            this._oldDepthWrite = null;
            this.cache_oldMaterialSided = null;

            this._currentGeometryGroupHash = -1;
            this._currentMaterialId = -1;

            this._lightsNeedUpdate = true;

            plugin.setRendering(false);

            retval = true;
        }

        return retval;
    }

    private void renderObjects(List<WebGLObject> renderList, Camera camera, List<Light> lights, AbstractFog fog,
            boolean useBlending) {
        renderObjects(renderList, camera, lights, fog, useBlending, null);
    }

    //renderList, camera, lights, fog, useBlending, overrideMaterial
    private void renderObjects(List<WebGLObject> renderList, Camera camera, List<Light> lights, AbstractFog fog,
            boolean useBlending, Material overrideMaterial) {
        Material material = null;

        for (int i = renderList.size() - 1; i != -1; i--) {

            WebGLObject webglObject = renderList.get(i);

            GeometryObject object = webglObject.object;
            WebGLGeometry buffer = webglObject.buffer;

            setupMatrices(object, camera);

            if (overrideMaterial != null) {

                material = overrideMaterial;

            } else {

                material = webglObject.material; //TODO: material = (isMaterialTransparent) ? webglObject.transparent : webglObject.opaque;

                if (material == null)
                    continue;

                if (useBlending)
                    setBlending(material.getBlending(), material.getBlendEquation(), material.getBlendSrc(),
                            material.getBlendDst());

                setDepthTest(material.isDepthTest());
                setDepthWrite(material.isDepthWrite());
                setPolygonOffset(material.isPolygonOffset(), material.getPolygonOffsetFactor(),
                        material.getPolygonOffsetUnits());

            }

            setMaterialFaces(material);

            if (buffer instanceof BufferGeometry) {

                renderBufferDirect(camera, lights, fog, material, (BufferGeometry) buffer, object);

            } else {

                renderBuffer(camera, lights, fog, material, buffer, object);

            }

        }

    }

    /**
     * Buffer rendering.
     * Render GeometryObject with material.
     */
    //camera, lights, fog, material, geometryGroup, object
    public void renderBuffer(Camera camera, List<Light> lights, AbstractFog fog, Material material,
            WebGLGeometry geometry, GeometryObject object) {
        if (!material.isVisible())
            return;

        Shader program = setProgram(camera, lights, fog, material, object);

        Map<String, Integer> attributes = material.getShader().getAttributesLocations();

        boolean updateBuffers = false;
        int wireframeBit = material instanceof HasWireframe && ((HasWireframe) material).isWireframe() ? 1 : 0;

        int geometryGroupHash = (geometry.getId() * 0xffffff) + (material.getShader().getId() * 2) + wireframeBit;

        //      Log.error("--- renderBuffer() geometryGroupHash=" + geometryGroupHash 
        //            + ", _currentGeometryGroupHash=" +  this._currentGeometryGroupHash
        //            + ", program.id=" + program.getId()
        ////            + ", geometryGroup.id=" + geometryBuffer.getId()
        ////            + ", __webglLineCount=" + geometryBuffer.__webglLineCount
        //            + ", object.id=" + object.getId()
        //            + ", wireframeBit=" + wireframeBit);

        if (geometryGroupHash != this._currentGeometryGroupHash) {
            this._currentGeometryGroupHash = geometryGroupHash;
            updateBuffers = true;
        }

        if (updateBuffers) {

            initAttributes();

        }

        // vertices
        if (!(material instanceof HasSkinning && ((HasSkinning) material).isMorphTargets())
                && attributes.get("position") >= 0) {
            if (updateBuffers) {
                getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglVertexBuffer);
                enableAttribute(attributes.get("position"));
                getGL().vertexAttribPointer(attributes.get("position"), 3, DataType.FLOAT, false, 0, 0);
            }

        } else if (object instanceof Mesh && ((Mesh) object).morphTargetBase != null) {
            setupMorphTargets(material, geometry, (Mesh) object);
        }

        if (updateBuffers) {
            // custom attributes

            // Use the per-geometryGroup custom attribute arrays which are setup in initMeshBuffers

            if (geometry.__webglCustomAttributesList != null) {
                for (int i = 0; i < geometry.__webglCustomAttributesList.size(); i++) {
                    Attribute attribute = geometry.__webglCustomAttributesList.get(i);

                    if (attributes.get(attribute.belongsToAttribute) >= 0) {
                        getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, attribute.buffer);
                        enableAttribute(attributes.get(attribute.belongsToAttribute));
                        getGL().vertexAttribPointer(attributes.get(attribute.belongsToAttribute), attribute.size,
                                DataType.FLOAT, false, 0, 0);
                    }
                }
            }

            // colors
            if (attributes.get("color") >= 0) {
                if (((Geometry) object.getGeometry()).getColors().size() > 0
                        || ((Geometry) object.getGeometry()).getFaces().size() > 0) {

                    getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglColorBuffer);
                    enableAttribute(attributes.get("color"));
                    getGL().vertexAttribPointer(attributes.get("color"), 3, DataType.FLOAT, false, 0, 0);

                } else {

                    double defaultAttributeValues[] = new double[] { 1.0, 1.0, 1.0 };

                    getGL().vertexAttrib3fv(attributes.get("color"), defaultAttributeValues);

                }
            }

            // normals
            if (attributes.get("normal") >= 0) {
                getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglNormalBuffer);
                enableAttribute(attributes.get("normal"));
                getGL().vertexAttribPointer(attributes.get("normal"), 3, DataType.FLOAT, false, 0, 0);
            }

            // tangents
            if (attributes.get("tangent") >= 0) {
                getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglTangentBuffer);
                enableAttribute(attributes.get("tangent"));
                getGL().vertexAttribPointer(attributes.get("tangent"), 4, DataType.FLOAT, false, 0, 0);
            }

            // uvs
            if (attributes.get("uv") >= 0) {
                if (((Geometry) object.getGeometry()).getFaceVertexUvs().get(0) != null) {
                    getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglUVBuffer);
                    enableAttribute(attributes.get("uv"));
                    getGL().vertexAttribPointer(attributes.get("uv"), 2, DataType.FLOAT, false, 0, 0);

                } else {

                    double defaultAttributeValues[] = new double[] { 0.0, 0.0 };
                    getGL().vertexAttrib2fv(attributes.get("uv"), defaultAttributeValues);
                }
            }

            if (attributes.get("uv2") >= 0) {
                if (((Geometry) object.getGeometry()).getFaceVertexUvs().get(1) != null) {
                    getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglUV2Buffer);
                    enableAttribute(attributes.get("uv2"));
                    getGL().vertexAttribPointer(attributes.get("uv2"), 2, DataType.FLOAT, false, 0, 0);

                } else {

                    double defaultAttributeValues[] = new double[] { 0.0, 0.0 };

                    getGL().vertexAttrib2fv(attributes.get("uv2"), defaultAttributeValues);
                }
            }

            if (material instanceof HasSkinning && ((HasSkinning) material).isSkinning()
                    && attributes.get("skinIndex") >= 0 && attributes.get("skinWeight") >= 0) {
                getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglSkinIndicesBuffer);
                enableAttribute(attributes.get("skinIndex"));
                getGL().vertexAttribPointer(attributes.get("skinIndex"), 4, DataType.FLOAT, false, 0, 0);

                getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglSkinWeightsBuffer);
                enableAttribute(attributes.get("skinWeight"));
                getGL().vertexAttribPointer(attributes.get("skinWeight"), 4, DataType.FLOAT, false, 0, 0);
            }

            // line distances

            if (attributes.get("lineDistance") != null && attributes.get("lineDistance") >= 0) {

                getGL().bindBuffer(BufferTarget.ARRAY_BUFFER, geometry.__webglLineDistanceBuffer);
                enableAttribute(attributes.get("lineDistance"));
                getGL().vertexAttribPointer(attributes.get("lineDistance"), 1, DataType.FLOAT, false, 0, 0);

            }

        }

        disableUnusedAttributes();

        Log.debug("  ----> renderBuffer() ID " + object.getId() + " (" + object.getClass().getSimpleName() + ")");

        // Render object's buffers
        object.renderBuffer(this, geometry, updateBuffers);
    }

    private void initMaterial(Material material, List<Light> lights, AbstractFog fog, GeometryObject object) {
        Log.debug("Called initMaterial for material: " + material.getClass().getName() + " and object "
                + object.getClass().getName());

        // heuristics to create shader parameters according to lights in the scene
        // (not to blow over maxLights budget)
        Map<String, Integer> maxLightCount = allocateLights(lights);
        int maxShadows = allocateShadows(lights);

        ProgramParameters parameters = new ProgramParameters();

        parameters.gammaInput = this.isGammaInput();
        parameters.gammaOutput = this.isGammaOutput();

        parameters.precision = this._precision;

        parameters.supportsVertexTextures = this._supportsVertexTextures;

        if (fog != null) {
            parameters.useFog = true;
            parameters.useFog2 = (fog instanceof FogExp2);
        }

        parameters.logarithmicDepthBuffer = this._logarithmicDepthBuffer;

        parameters.maxBones = allocateBones(object);

        if (object instanceof SkinnedMesh) {
            parameters.useVertexTexture = this._supportsBoneTextures;// && ((SkinnedMesh)object).useVertexTexture;
        }

        parameters.maxMorphTargets = this.maxMorphTargets;
        parameters.maxMorphNormals = this.maxMorphNormals;

        parameters.maxDirLights = maxLightCount.get("directional");
        parameters.maxPointLights = maxLightCount.get("point");
        parameters.maxSpotLights = maxLightCount.get("spot");
        parameters.maxHemiLights = maxLightCount.get("hemi");

        parameters.maxShadows = maxShadows;

        for (Plugin plugin : this.plugins)
            if (plugin instanceof ShadowMap && ((ShadowMap) plugin).isEnabled() && object.isReceiveShadow()) {
                parameters.shadowMapEnabled = object.isReceiveShadow() && maxShadows > 0;
                parameters.shadowMapSoft = ((ShadowMap) plugin).isSoft();
                parameters.shadowMapDebug = ((ShadowMap) plugin).isDebugEnabled();
                parameters.shadowMapCascade = ((ShadowMap) plugin).isCascade();
                break;
            }

        material.updateProgramParameters(parameters);
        Log.debug("initMaterial() called new Program");

        String cashKey = material.getShader().getFragmentSource() + material.getShader().getVertexSource()
                + parameters.toString();

        if (this._programs.containsKey(cashKey)) {
            material.setShader(this._programs.get(cashKey));
        } else {
            Shader shader = material.buildShader(getGL(), parameters);

            this._programs.put(cashKey, shader);

            this.getInfo().getMemory().programs = _programs.size();
        }

        Map<String, Integer> attributes = material.getShader().getAttributesLocations();

        if (material instanceof HasSkinning) {
            if (((HasSkinning) material).isMorphTargets()) {
                int numSupportedMorphTargets = 0;
                for (int i = 0; i < this.maxMorphTargets; i++) {
                    String id = "morphTarget" + i;

                    if (attributes.get(id) >= 0) {
                        numSupportedMorphTargets++;
                    }
                }

                ((HasSkinning) material).setNumSupportedMorphTargets(numSupportedMorphTargets);
            }

            if (((HasSkinning) material).isMorphNormals()) {
                int numSupportedMorphNormals = 0;
                for (int i = 0; i < this.maxMorphNormals; i++) {
                    String id = "morphNormal" + i;

                    if (attributes.get(id) >= 0) {
                        numSupportedMorphNormals++;
                    }
                }

                ((HasSkinning) material).setNumSupportedMorphNormals(numSupportedMorphNormals);
            }
        }
    }

    private Shader setProgram(Camera camera, List<Light> lights, AbstractFog fog, Material material,
            GeometryObject object) {
        // Use new material units for new shader
        this._usedTextureUnits = 0;

        if (material.isNeedsUpdate()) {
            if (material.getShader() == null || material.getShader().getProgram() == null)
                material.deallocate(this);

            initMaterial(material, lights, fog, object);
            material.setNeedsUpdate(false);
        }

        if (material instanceof HasSkinning && ((HasSkinning) material).isMorphTargets()) {
            if (object instanceof Mesh && ((Mesh) object).__webglMorphTargetInfluences == null) {
                ((Mesh) object).__webglMorphTargetInfluences = Float32Array.create(this.maxMorphTargets);
            }
        }

        boolean refreshProgram = false;
        boolean refreshMaterial = false;
        boolean refreshLights = false;

        Shader shader = material.getShader();
        WebGLProgram program = shader.getProgram();
        Map<String, Uniform> m_uniforms = shader.getUniforms();

        if (!program.equals(_currentProgram)) {
            getGL().useProgram(program);
            this._currentProgram = program;

            refreshProgram = true;
            refreshMaterial = true;
            refreshLights = true;
        }

        if (material.getId() != this._currentMaterialId) {
            if (_currentMaterialId == -1)
                refreshLights = true;

            this._currentMaterialId = material.getId();
            refreshMaterial = true;
        }

        if (refreshProgram || !camera.equals(this._currentCamera)) {
            getGL().uniformMatrix4fv(m_uniforms.get("projectionMatrix").getLocation(), false,
                    camera.getProjectionMatrix().getArray());

            if (_logarithmicDepthBuffer) {

                gl.uniform1f(m_uniforms.get("logDepthBufFC").getLocation(),
                        2.0 / (Math.log(((HasNearFar) camera).getFar() + 1.0) / 0.6931471805599453 /*Math.LN2*/ ));

            }

            if (!camera.equals(this._currentCamera))
                this._currentCamera = camera;

            // load material specific uniforms
            // (shader material also gets them for the sake of genericity)
            if (material.getClass() == ShaderMaterial.class || material.getClass() == MeshPhongMaterial.class
                    || material instanceof HasEnvMap && ((HasEnvMap) material).getEnvMap() != null) {

                if (m_uniforms.get("cameraPosition").getLocation() != null) {
                    _vector3.setFromMatrixPosition(camera.getMatrixWorld());
                    getGL().uniform3f(m_uniforms.get("cameraPosition").getLocation(), _vector3.getX(),
                            _vector3.getY(), _vector3.getZ());
                }
            }

            if (material.getClass() == MeshPhongMaterial.class || material.getClass() == MeshLambertMaterial.class
                    || material.getClass() == MeshBasicMaterial.class || material.getClass() == ShaderMaterial.class
                    || material instanceof HasSkinning && ((HasSkinning) material).isSkinning()) {

                if (m_uniforms.get("viewMatrix").getLocation() != null) {
                    getGL().uniformMatrix4fv(m_uniforms.get("viewMatrix").getLocation(), false,
                            camera.getMatrixWorldInverse().getArray());
                }
            }
        }

        // skinning uniforms must be set even if material didn't change
        // auto-setting of texture unit for bone texture must go before other textures
        // not sure why, but otherwise weird things happen
        if (material instanceof HasSkinning && ((HasSkinning) material).isSkinning()) {
            if (object instanceof SkinnedMesh && ((SkinnedMesh) object).isUseVertexTexture()
                    && this._supportsBoneTextures) {
                if (m_uniforms.get("boneTexture").getLocation() != null) {
                    int textureUnit = getTextureUnit();

                    getGL().uniform1i(m_uniforms.get("boneTexture").getLocation(), textureUnit);
                    setTexture(((SkinnedMesh) object).boneTexture, textureUnit);
                }
            } else {
                if (m_uniforms.get("boneGlobalMatrices").getLocation() != null) {
                    getGL().uniformMatrix4fv(m_uniforms.get("boneGlobalMatrices").getLocation(), false,
                            ((SkinnedMesh) object).boneMatrices);
                }
            }
        }

        if (refreshMaterial) {
            // refresh uniforms common to several materials
            if (fog != null && material instanceof HasFog && ((HasFog) material).isFog())
                fog.refreshUniforms(m_uniforms);

            if (material.getClass() == MeshPhongMaterial.class || material.getClass() == MeshLambertMaterial.class
                    || (material.getClass() == ShaderMaterial.class && ((ShaderMaterial) material).isLights())) {

                if (this._lightsNeedUpdate) {
                    refreshLights = true;
                    this._lights.setupLights(lights, this.gammaInput);
                    this._lightsNeedUpdate = false;
                }

                if (refreshLights) {
                    this._lights.refreshUniformsLights(m_uniforms);
                    //               markUniformsLightsNeedsUpdate( m_uniforms, true );
                } else {
                    //               markUniformsLightsNeedsUpdate( m_uniforms, false );
                }
            }

            material.refreshUniforms(camera, this.gammaInput);

            if (object.isReceiveShadow() && !material.isShadowPass())
                refreshUniformsShadow(m_uniforms, lights);

            // load common uniforms
            loadUniformsGeneric(m_uniforms);

        }

        loadUniformsMatrices(m_uniforms, object);

        if (m_uniforms.get("modelMatrix").getLocation() != null)
            getGL().uniformMatrix4fv(m_uniforms.get("modelMatrix").getLocation(), false,
                    object.getMatrixWorld().getArray());

        return shader;
    }

    private void refreshUniformsShadow(Map<String, Uniform> uniforms, List<Light> lights) {
        if (uniforms.containsKey("shadowMatrix")) {
            // Make them zero
            uniforms.get("shadowMap").setValue(new ArrayList<Texture>());
            uniforms.get("shadowMapSize").setValue(new ArrayList<Vector2>());
            uniforms.get("shadowMatrix").setValue(new ArrayList<Matrix4>());
            List<Texture> shadowMap = (List<Texture>) uniforms.get("shadowMap").getValue();
            List<Vector2> shadowMapSize = (List<Vector2>) uniforms.get("shadowMapSize").getValue();
            List<Matrix4> shadowMatrix = (List<Matrix4>) uniforms.get("shadowMatrix").getValue();

            int j = 0;
            for (Light light : lights) {
                if (!light.isCastShadow())
                    continue;

                if (light instanceof ShadowLight && !((ShadowLight) light).isShadowCascade()) {
                    ShadowLight shadowLight = (ShadowLight) light;

                    shadowMap.add(shadowLight.getShadowMap());
                    shadowMapSize.add(shadowLight.getShadowMapSize());
                    shadowMatrix.add(shadowLight.getShadowMatrix());

                    ((Float32Array) uniforms.get("shadowDarkness").getValue()).set(j,
                            shadowLight.getShadowDarkness());
                    ((Float32Array) uniforms.get("shadowBias").getValue()).set(j, shadowLight.getShadowBias());
                    j++;
                }
            }
        }
    }

    // Uniforms (load to GPU)

    private void loadUniformsMatrices(Map<String, Uniform> uniforms, GeometryObject object) {
        GeometryObject objectImpl = (GeometryObject) object;
        getGL().uniformMatrix4fv(uniforms.get("modelViewMatrix").getLocation(), false,
                objectImpl._modelViewMatrix.getArray());

        if (uniforms.containsKey("normalMatrix"))
            getGL().uniformMatrix3fv(uniforms.get("normalMatrix").getLocation(), false,
                    objectImpl._normalMatrix.getArray());
    }

    @SuppressWarnings("unchecked")
    private void loadUniformsGeneric(Map<String, Uniform> materialUniforms) {
        WebGLRenderingContext gl = getGL();

        for (Uniform uniform : materialUniforms.values()) {
            //         for ( String key: materialUniforms.keySet() ) 
            //         {
            //             Uniform uniform = materialUniforms.get(key);
            WebGLUniformLocation location = uniform.getLocation();

            if (location == null)
                continue;

            Object value = uniform.getValue();
            Uniform.TYPE type = uniform.getType();

            // Up textures also for undefined values
            if (type != Uniform.TYPE.T && value == null)
                continue;

            if (type == TYPE.I) // single integer
            {
                gl.uniform1i(location, (value instanceof Boolean) ? ((Boolean) value) ? 1 : 0 : (Integer) value);
            } else if (type == TYPE.F) // single double
            {
                gl.uniform1f(location, (Double) value);
            } else if (type == TYPE.V2) // single Vector2
            {
                gl.uniform2f(location, ((Vector2) value).getX(), ((Vector2) value).getX());
            } else if (type == TYPE.V3) // single Vector3
            {
                gl.uniform3f(location, ((Vector3) value).getX(), ((Vector3) value).getY(),
                        ((Vector3) value).getZ());
            } else if (type == TYPE.V4) // single Vector4
            {
                gl.uniform4f(location, ((Vector4) value).getX(), ((Vector4) value).getY(), ((Vector4) value).getZ(),
                        ((Vector4) value).getW());
            } else if (type == TYPE.C) // single Color
            {
                gl.uniform3f(location, ((Color) value).getR(), ((Color) value).getG(), ((Color) value).getB());
            } else if (type == TYPE.FV1) // flat array of floats (JS or typed array)
            {
                gl.uniform1fv(location, (Float32Array) value);
            } else if (type == TYPE.FV) // flat array of floats with 3 x N size (JS or typed array)
            {
                gl.uniform3fv(location, (Float32Array) value);
            } else if (type == TYPE.V2V) // List of Vector2
            {
                List<Vector2> listVector2f = (List<Vector2>) value;
                if (uniform.getCacheArray() == null)
                    uniform.setCacheArray(Float32Array.create(2 * listVector2f.size()));

                for (int i = 0, il = listVector2f.size(); i < il; i++) {
                    int offset = i * 2;

                    uniform.getCacheArray().set(offset, listVector2f.get(i).getX());
                    uniform.getCacheArray().set(offset + 1, listVector2f.get(i).getY());
                }

                gl.uniform2fv(location, uniform.getCacheArray());
            } else if (type == TYPE.V3V) // List of Vector3
            {
                List<Vector3> listVector3f = (List<Vector3>) value;
                if (uniform.getCacheArray() == null)
                    uniform.setCacheArray(Float32Array.create(3 * listVector3f.size()));

                for (int i = 0, il = listVector3f.size(); i < il; i++) {
                    int offset = i * 3;

                    uniform.getCacheArray().set(offset, listVector3f.get(i).getX());
                    uniform.getCacheArray().set(offset + 1, listVector3f.get(i).getY());
                    uniform.getCacheArray().set(offset + 2, listVector3f.get(i).getZ());
                }

                gl.uniform3fv(location, uniform.getCacheArray());
            } else if (type == TYPE.V4V) // List of Vector4
            {
                List<Vector4> listVector4f = (List<Vector4>) value;
                if (uniform.getCacheArray() == null)
                    uniform.setCacheArray(Float32Array.create(4 * listVector4f.size()));

                for (int i = 0, il = listVector4f.size(); i < il; i++) {
                    int offset = i * 4;

                    uniform.getCacheArray().set(offset, listVector4f.get(i).getX());
                    uniform.getCacheArray().set(offset + 1, listVector4f.get(i).getY());
                    uniform.getCacheArray().set(offset + 2, listVector4f.get(i).getZ());
                    uniform.getCacheArray().set(offset + 3, listVector4f.get(i).getW());
                }

                gl.uniform4fv(location, uniform.getCacheArray());
            } else if (type == TYPE.M4) // single Matrix4
            {
                Matrix4 matrix4 = (Matrix4) value;
                if (uniform.getCacheArray() == null)
                    uniform.setCacheArray(Float32Array.create(16));

                matrix4.flattenToArrayOffset(uniform.getCacheArray());
                gl.uniformMatrix4fv(location, false, uniform.getCacheArray());
            } else if (type == TYPE.M4V) // List of Matrix4
            {
                List<Matrix4> listMatrix4f = (List<Matrix4>) value;
                if (uniform.getCacheArray() == null)
                    uniform.setCacheArray(Float32Array.create(16 * listMatrix4f.size()));

                for (int i = 0, il = listMatrix4f.size(); i < il; i++)
                    listMatrix4f.get(i).flattenToArrayOffset(uniform.getCacheArray(), i * 16);

                gl.uniformMatrix4fv(location, false, uniform.getCacheArray());
            } else if (type == TYPE.T) // single Texture (2d or cube)
            {
                Texture texture = (Texture) value;
                int textureUnit = getTextureUnit();

                gl.uniform1i(location, textureUnit);

                if (texture != null) {
                    if (texture.getClass() == CubeTexture.class)
                        setCubeTexture((CubeTexture) texture, textureUnit);

                    else if (texture.getClass() == RenderTargetCubeTexture.class)
                        setCubeTextureDynamic((RenderTargetCubeTexture) texture, textureUnit);

                    else
                        setTexture(texture, textureUnit);
                }
            } else if (type == TYPE.TV) //List of Texture (2d)
            {
                List<Texture> textureList = (List<Texture>) value;
                int[] units = new int[textureList.size()];

                for (int i = 0, il = textureList.size(); i < il; i++) {
                    units[i] = getTextureUnit();
                }

                gl.uniform1iv(location, units);

                for (int i = 0, il = textureList.size(); i < il; i++) {
                    Texture texture = textureList.get(i);
                    int textureUnit = units[i];

                    if (texture == null)
                        continue;

                    setTexture(texture, textureUnit);
                }
            }
        }
    }

    public int getTextureUnit() {
        int textureUnit = this._usedTextureUnits++;

        if (textureUnit >= this._maxTextures) {
            Log.warn("Trying to use " + textureUnit + " texture units while this GPU supports only "
                    + this._maxTextures);
        }

        return textureUnit;
    }

    private void setupMatrices(Object3D object, Camera camera) {
        object._modelViewMatrix.multiply(camera.getMatrixWorldInverse(), object.getMatrixWorld());
        object._normalMatrix.getNormalMatrix(object._modelViewMatrix);
    }

    public void setMaterialFaces(Material material) {
        if (this.cache_oldMaterialSided == null || this.cache_oldMaterialSided != material.getSides()) {
            if (material.getSides() == Material.SIDE.DOUBLE)
                getGL().disable(EnableCap.CULL_FACE);
            else
                getGL().enable(EnableCap.CULL_FACE);

            if (material.getSides() == Material.SIDE.BACK)
                getGL().frontFace(FrontFaceDirection.CW);
            else
                getGL().frontFace(FrontFaceDirection.CCW);

            this.cache_oldMaterialSided = material.getSides();
        }
    }

    public void setDepthTest(boolean depthTest) {
        if (this._oldDepthTest == null || this._oldDepthTest != depthTest) {
            if (depthTest)
                getGL().enable(EnableCap.DEPTH_TEST);
            else
                getGL().disable(EnableCap.DEPTH_TEST);

            this._oldDepthTest = depthTest;
        }
    }

    public void setDepthWrite(boolean depthWrite) {
        if (this._oldDepthWrite == null || this._oldDepthWrite != depthWrite) {
            getGL().depthMask(depthWrite);
            _oldDepthWrite = depthWrite;
        }
    }

    private void setPolygonOffset(boolean polygonoffset, double factor, double units) {
        if (this._oldPolygonOffset == null || this._oldPolygonOffset != polygonoffset) {
            if (polygonoffset)
                getGL().enable(EnableCap.POLYGON_OFFSET_FILL);
            else
                getGL().disable(EnableCap.POLYGON_OFFSET_FILL);

            this._oldPolygonOffset = polygonoffset;
        }

        if (polygonoffset && (_oldPolygonOffsetFactor == null || _oldPolygonOffsetUnits == null
                || _oldPolygonOffsetFactor != factor || _oldPolygonOffsetUnits != units)) {
            getGL().polygonOffset(factor, units);

            this._oldPolygonOffsetFactor = factor;
            this._oldPolygonOffsetUnits = units;
        }
    }

    public void setBlending(Material.BLENDING blending) {
        if (blending != this._oldBlending) {
            if (blending == Material.BLENDING.NO) {
                getGL().disable(EnableCap.BLEND);

            } else if (blending == Material.BLENDING.ADDITIVE) {

                getGL().enable(EnableCap.BLEND);
                getGL().blendEquation(BlendEquationMode.FUNC_ADD);
                getGL().blendFunc(BlendingFactorSrc.SRC_ALPHA, BlendingFactorDest.ONE);

                // TODO: Find blendFuncSeparate() combination
            } else if (blending == Material.BLENDING.SUBTRACTIVE) {
                getGL().enable(EnableCap.BLEND);
                getGL().blendEquation(BlendEquationMode.FUNC_ADD);
                getGL().blendFunc(BlendingFactorSrc.ZERO, BlendingFactorDest.ONE_MINUS_SRC_COLOR);

                // TODO: Find blendFuncSeparate() combination
            } else if (blending == Material.BLENDING.MULTIPLY) {
                getGL().enable(EnableCap.BLEND);
                getGL().blendEquation(BlendEquationMode.FUNC_ADD);
                getGL().blendFunc(BlendingFactorSrc.ZERO, BlendingFactorDest.SRC_COLOR);

            } else if (blending == Material.BLENDING.CUSTOM) {
                getGL().enable(EnableCap.BLEND);

            }
            // NORMAL
            else {
                getGL().enable(EnableCap.BLEND);
                getGL().blendEquationSeparate(BlendEquationMode.FUNC_ADD, BlendEquationMode.FUNC_ADD);
                getGL().blendFuncSeparate(BlendingFactorSrc.SRC_ALPHA, BlendingFactorDest.ONE_MINUS_SRC_ALPHA,
                        BlendingFactorSrc.ONE, BlendingFactorDest.ONE_MINUS_SRC_ALPHA);
            }

            this._oldBlending = blending;
        }
    }

    private void setBlending(Material.BLENDING blending, BlendEquationMode blendEquation,
            BlendingFactorSrc blendSrc, BlendingFactorDest blendDst) {
        setBlending(blending);

        if (blending == Material.BLENDING.CUSTOM) {
            if (blendEquation != this._oldBlendEquation) {
                getGL().blendEquation(blendEquation);
                this._oldBlendEquation = blendEquation;
            }

            if (blendSrc != _oldBlendSrc || blendDst != _oldBlendDst) {
                getGL().blendFunc(blendSrc, blendDst);

                this._oldBlendSrc = blendSrc;
                this._oldBlendDst = blendDst;
            }
        } else {
            this._oldBlendEquation = null;
            this._oldBlendSrc = null;
            this._oldBlendDst = null;
        }
    }

    // Textures

    private void setCubeTextureDynamic(RenderTargetCubeTexture texture, int slot) {
        getGL().activeTexture(TextureUnit.TEXTURE0, slot);
        getGL().bindTexture(TextureTarget.TEXTURE_CUBE_MAP, texture.getWebGlTexture());
    }

    public void setTexture(Texture texture, int slot) {
        if (texture.isNeedsUpdate()) {
            if (texture.getWebGlTexture() == null) {
                texture.setWebGlTexture(getGL().createTexture());

                this.getInfo().getMemory().textures++;
            }

            getGL().activeTexture(TextureUnit.TEXTURE0, slot);
            getGL().bindTexture(TextureTarget.TEXTURE_2D, texture.getWebGlTexture());

            getGL().pixelStorei(PixelStoreParameter.UNPACK_FLIP_Y_WEBGL, texture.isFlipY() ? 1 : 0);
            getGL().pixelStorei(PixelStoreParameter.UNPACK_PREMULTIPLY_ALPHA_WEBGL,
                    texture.isPremultiplyAlpha() ? 1 : 0);
            getGL().pixelStorei(PixelStoreParameter.UNPACK_ALIGNMENT, texture.getUnpackAlignment());

            Element image = texture.getImage();
            boolean isImagePowerOfTwo = Mathematics.isPowerOfTwo(image.getOffsetWidth())
                    && Mathematics.isPowerOfTwo(image.getOffsetHeight());

            texture.setTextureParameters(getGL(), getMaxAnisotropy(), TextureTarget.TEXTURE_2D, isImagePowerOfTwo);

            if (texture instanceof CompressedTexture) {
                List<DataTexture> mipmaps = ((CompressedTexture) texture).getMipmaps();

                for (int i = 0, il = mipmaps.size(); i < il; i++) {
                    DataTexture mipmap = mipmaps.get(i);
                    getGL().compressedTexImage2D(TextureTarget.TEXTURE_2D, i,
                            ((CompressedTexture) texture).getCompressedFormat(), mipmap.getWidth(),
                            mipmap.getHeight(), 0, mipmap.getData());
                }
            } else if (texture instanceof DataTexture) {
                getGL().texImage2D(TextureTarget.TEXTURE_2D, 0, ((DataTexture) texture).getWidth(),
                        ((DataTexture) texture).getHeight(), 0, texture.getFormat(), texture.getType(),
                        ((DataTexture) texture).getData());
            } else {
                getGL().texImage2D(TextureTarget.TEXTURE_2D, 0, texture.getFormat(), texture.getType(),
                        (ImageElement) image);
            }

            if (texture.isGenerateMipmaps() && isImagePowerOfTwo)
                getGL().generateMipmap(TextureTarget.TEXTURE_2D);

            texture.setNeedsUpdate(false);
        }
        // Needed to check webgl texture in case deferred loading
        else if (texture.getWebGlTexture() != null) {
            getGL().activeTexture(TextureUnit.TEXTURE0, slot);
            getGL().bindTexture(TextureTarget.TEXTURE_2D, texture.getWebGlTexture());
        }
    }

    private CanvasElement createPowerOfTwoImage(Element image) {
        int width = image.getOffsetWidth();
        int height = image.getOffsetHeight();

        CanvasElement canvas = Document.get().createElement("canvas").cast();

        // Scale up the texture to the next highest power of two dimensions.
        canvas.setWidth(Mathematics.getNextHighestPowerOfTwo(width));
        canvas.setHeight(Mathematics.getNextHighestPowerOfTwo(height));

        Context2d context = canvas.getContext2d();
        context.drawImage((ImageElement) image, 0, 0, width, height);

        return canvas;
    }

    /**
     * Warning: Scaling through the canvas will only work with images that use
     * premultiplied alpha.
     * 
     * @param image    the image element
     * @param maxSize  the max size of absoluteWidth or absoluteHeight
     * 
     * @return the image element (Canvas or Image)
     */
    private Element clampToMaxSize(Element image, int maxSize) {
        int imgWidth = image.getOffsetWidth();
        int imgHeight = image.getOffsetHeight();

        if (imgWidth <= maxSize && imgHeight <= maxSize)
            return image;

        int maxDimension = Math.max(imgWidth, imgHeight);
        int newWidth = (int) Math.floor(imgWidth * maxSize / maxDimension);
        int newHeight = (int) Math.floor(imgHeight * maxSize / maxDimension);

        CanvasElement canvas = Document.get().createElement("canvas").cast();
        canvas.setWidth(newWidth);
        canvas.setHeight(newHeight);

        Context2d context = canvas.getContext2d();
        context.drawImage((ImageElement) image, 0, 0, imgWidth, imgHeight, 0, 0, newWidth, newHeight);

        return canvas;
    }

    private void setCubeTexture(CubeTexture texture, int slot) {
        if (!texture.isValid())
            return;

        if (texture.isNeedsUpdate()) {
            if (texture.getWebGlTexture() == null) {
                texture.setWebGlTexture(getGL().createTexture());
                this.getInfo().getMemory().textures += 6;
            }

            getGL().activeTexture(TextureUnit.TEXTURE0, slot);
            getGL().bindTexture(TextureTarget.TEXTURE_CUBE_MAP, texture.getWebGlTexture());
            getGL().pixelStorei(PixelStoreParameter.UNPACK_FLIP_Y_WEBGL, texture.isFlipY() ? 1 : 0);

            List<Element> cubeImage = new ArrayList<Element>();

            for (int i = 0; i < 6; i++) {
                if (this.autoScaleCubemaps)
                    cubeImage.add(clampToMaxSize(texture.getImage(i), this._maxCubemapSize));

                else
                    cubeImage.add(texture.getImage(i));
            }

            Element image = cubeImage.get(0);
            boolean isImagePowerOfTwo = Mathematics.isPowerOfTwo(image.getOffsetWidth())
                    && Mathematics.isPowerOfTwo(image.getOffsetHeight());

            texture.setTextureParameters(getGL(), getMaxAnisotropy(), TextureTarget.TEXTURE_CUBE_MAP,
                    true /*power of two*/ );

            for (int i = 0; i < 6; i++) {
                if (!isImagePowerOfTwo) {
                    getGL().texImage2D(TextureTarget.TEXTURE_CUBE_MAP_POSITIVE_X, i, 0, texture.getFormat(),
                            texture.getType(), createPowerOfTwoImage(cubeImage.get(i)));
                } else {
                    getGL().texImage2D(TextureTarget.TEXTURE_CUBE_MAP_POSITIVE_X, i, 0, texture.getFormat(),
                            texture.getType(), (ImageElement) cubeImage.get(i));
                }
            }

            if (texture.isGenerateMipmaps())
                getGL().generateMipmap(TextureTarget.TEXTURE_CUBE_MAP);

            texture.setNeedsUpdate(false);
        } else {
            getGL().activeTexture(TextureUnit.TEXTURE0, slot);
            getGL().bindTexture(TextureTarget.TEXTURE_CUBE_MAP, texture.getWebGlTexture());
        }

    }

    /**
     * Setup render target
     * 
     * @param renderTarget the render target
     */
    public void setRenderTarget(RenderTargetTexture renderTarget) {
        Log.debug("  ----> Called setRenderTarget(params)");
        WebGLFramebuffer framebuffer = null;

        int width, height, vx, vy;

        if (renderTarget != null) {
            renderTarget.setRenderTarget(getGL());
            framebuffer = renderTarget.getWebGLFramebuffer();

            width = renderTarget.getWidth();
            height = renderTarget.getHeight();

            vx = 0;
            vy = 0;

        } else {
            width = this._viewportWidth;
            height = this._viewportHeight;

            vx = _viewportX;
            vy = _viewportY;

        }

        if (framebuffer != this._currentFramebuffer) {
            getGL().bindFramebuffer(framebuffer);
            getGL().viewport(vx, vy, width, height);

            this._currentFramebuffer = framebuffer;
        }

        _currentWidth = width;
        _currentHeight = height;
    }

    /**
     * Default for when object is not specified
     * ( for example when prebuilding shader to be used with multiple objects )
     * 
     * - leave some extra space for other uniforms
     * - limit here is ANGLE's 254 max uniform vectors (up to 54 should be safe)
     * 
     * @param object
     * @return
     */
    private int allocateBones(GeometryObject object) {
        if (this._supportsBoneTextures && object instanceof SkinnedMesh
                && ((SkinnedMesh) object).isUseVertexTexture()) {
            return 1024;
        } else {
            // default for when object is not specified
            // ( for example when prebuilding shader
            //   to be used with multiple objects )
            //
            //    - leave some extra space for other uniforms
            //  - limit here is ANGLE's 254 max uniform vectors
            //    (up to 54 should be safe)

            int nVertexUniforms = getGL().getParameteri(WebGLConstants.MAX_VERTEX_UNIFORM_VECTORS);
            int nVertexMatrices = (int) Math.floor((nVertexUniforms - 20) / 4);

            int maxBones = nVertexMatrices;

            if (object instanceof SkinnedMesh) {
                maxBones = Math.min(((SkinnedMesh) object).getBones().size(), maxBones);

                if (maxBones < ((SkinnedMesh) object).getBones().size()) {
                    Log.warn("WebGLRenderer: too many bones - " + ((SkinnedMesh) object).getBones().size()
                            + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)");
                }
            }

            return maxBones;
        }
    }

    private Map<String, Integer> allocateLights(List<Light> lights) {
        int dirLights = 0, pointLights = 0, spotLights = 0, hemiLights = 0;

        for (Light light : lights) {
            if (light instanceof ShadowLight && ((ShadowLight) light).isOnlyShadow())
                continue;

            if (light instanceof DirectionalLight)
                dirLights++;
            if (light instanceof PointLight)
                pointLights++;
            if (light instanceof SpotLight)
                spotLights++;
            if (light instanceof HemisphereLight)
                hemiLights++;
        }

        Map<String, Integer> retval = GWT.isScript() ? new FastMap<Integer>() : new HashMap<String, Integer>();
        retval.put("directional", dirLights);
        retval.put("point", pointLights);
        retval.put("spot", spotLights);
        retval.put("hemi", hemiLights);

        return retval;
    }

    private int allocateShadows(List<Light> lights) {
        int maxShadows = 0;

        for (Light light : lights) {
            if (light instanceof ShadowLight) {
                if (!((ShadowLight) light).isCastShadow())
                    continue;

                maxShadows++;
            }
        }

        return maxShadows;
    }
}