(function (global, factory) {
  if (typeof define === "function" && define.amd) {
    define(["exports", "three"], factory);
  } else if (typeof exports !== "undefined") {
    factory(exports, require("three"));
  } else {
    var mod = {
      exports: {}
    };
    factory(mod.exports, global.three);
    global.OutlineEffect = mod.exports;
  }
})(typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : this, function (_exports, _three) {
  "use strict";

  Object.defineProperty(_exports, "__esModule", {
    value: true
  });
  _exports.OutlineEffect = void 0;

  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }

  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

  /**
   * Reference: https://en.wikipedia.org/wiki/Cel_shading
   *
   * API
   *
   * 1. Traditional
   *
   * const effect = new OutlineEffect( renderer );
   *
   * function render() {
   *
   * 	effect.render( scene, camera );
   *
   * }
   *
   * 2. VR compatible
   *
   * const effect = new OutlineEffect( renderer );
   * let renderingOutline = false;
   *
   * scene.onAfterRender = function () {
   *
   * 	if ( renderingOutline ) return;
   *
   * 	renderingOutline = true;
   *
   * 	effect.renderOutline( scene, camera );
   *
   * 	renderingOutline = false;
   *
   * };
   *
   * function render() {
   *
   * 	renderer.render( scene, camera );
   *
   * }
   *
   * // How to set default outline parameters
   * new OutlineEffect( renderer, {
   * 	defaultThickness: 0.01,
   * 	defaultColor: [ 0, 0, 0 ],
   * 	defaultAlpha: 0.8,
   * 	defaultKeepAlive: true // keeps outline material in cache even if material is removed from scene
   * } );
   *
   * // How to set outline parameters for each material
   * material.userData.outlineParameters = {
   * 	thickness: 0.01,
   * 	color: [ 0, 0, 0 ]
   * 	alpha: 0.8,
   * 	visible: true,
   * 	keepAlive: true
   * };
   */
  var OutlineEffect = /*#__PURE__*/_createClass(function OutlineEffect(renderer) {
    var parameters = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

    _classCallCheck(this, OutlineEffect);

    this.enabled = true;
    var defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003;
    var defaultColor = new _three.Color().fromArray(parameters.defaultColor !== undefined ? parameters.defaultColor : [0, 0, 0]);
    var defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0;
    var defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false; // object.material.uuid -> outlineMaterial or
    // object.material[ n ].uuid -> outlineMaterial
    // save at the outline material creation and release
    // if it's unused removeThresholdCount frames
    // unless keepAlive is true.

    var cache = {};
    var removeThresholdCount = 60; // outlineMaterial.uuid -> object.material or
    // outlineMaterial.uuid -> object.material[ n ]
    // save before render and release after render.

    var originalMaterials = {}; // object.uuid -> originalOnBeforeRender
    // save before render and release after render.

    var originalOnBeforeRenders = {}; //this.cache = cache;  // for debug

    var uniformsOutline = {
      outlineThickness: {
        value: defaultThickness
      },
      outlineColor: {
        value: defaultColor
      },
      outlineAlpha: {
        value: defaultAlpha
      }
    };
    var vertexShader = ['#include <common>', '#include <uv_pars_vertex>', '#include <displacementmap_pars_vertex>', '#include <fog_pars_vertex>', '#include <morphtarget_pars_vertex>', '#include <skinning_pars_vertex>', '#include <logdepthbuf_pars_vertex>', '#include <clipping_planes_pars_vertex>', 'uniform float outlineThickness;', 'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {', '	float thickness = outlineThickness;', '	const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex
    '	vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );', // NOTE: subtract pos2 from pos because BackSide objectNormal is negative
    '	vec4 norm = normalize( pos - pos2 );', '	return pos + norm * thickness * pos.w * ratio;', '}', 'void main() {', '	#include <uv_vertex>', '	#include <beginnormal_vertex>', '	#include <morphnormal_vertex>', '	#include <skinbase_vertex>', '	#include <skinnormal_vertex>', '	#include <begin_vertex>', '	#include <morphtarget_vertex>', '	#include <skinning_vertex>', '	#include <displacementmap_vertex>', '	#include <project_vertex>', '	vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with BackSide
    '	gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );', '	#include <logdepthbuf_vertex>', '	#include <clipping_planes_vertex>', '	#include <fog_vertex>', '}'].join('\n');
    var fragmentShader = ['#include <common>', '#include <fog_pars_fragment>', '#include <logdepthbuf_pars_fragment>', '#include <clipping_planes_pars_fragment>', 'uniform vec3 outlineColor;', 'uniform float outlineAlpha;', 'void main() {', '	#include <clipping_planes_fragment>', '	#include <logdepthbuf_fragment>', '	gl_FragColor = vec4( outlineColor, outlineAlpha );', '	#include <tonemapping_fragment>', '	#include <encodings_fragment>', '	#include <fog_fragment>', '	#include <premultiplied_alpha_fragment>', '}'].join('\n');

    function createMaterial() {
      return new _three.ShaderMaterial({
        type: 'OutlineEffect',
        uniforms: _three.UniformsUtils.merge([_three.UniformsLib['fog'], _three.UniformsLib['displacementmap'], uniformsOutline]),
        vertexShader: vertexShader,
        fragmentShader: fragmentShader,
        side: _three.BackSide
      });
    }

    function getOutlineMaterialFromCache(originalMaterial) {
      var data = cache[originalMaterial.uuid];

      if (data === undefined) {
        data = {
          material: createMaterial(),
          used: true,
          keepAlive: defaultKeepAlive,
          count: 0
        };
        cache[originalMaterial.uuid] = data;
      }

      data.used = true;
      return data.material;
    }

    function getOutlineMaterial(originalMaterial) {
      var outlineMaterial = getOutlineMaterialFromCache(originalMaterial);
      originalMaterials[outlineMaterial.uuid] = originalMaterial;
      updateOutlineMaterial(outlineMaterial, originalMaterial);
      return outlineMaterial;
    }

    function isCompatible(object) {
      var geometry = object.geometry;
      var hasNormals = false;

      if (object.geometry !== undefined) {
        if (geometry.isBufferGeometry) {
          hasNormals = geometry.attributes.normal !== undefined;
        } else {
          hasNormals = true; // the renderer always produces a normal attribute for Geometry
        }
      }

      return object.isMesh === true && object.material !== undefined && hasNormals === true;
    }

    function setOutlineMaterial(object) {
      if (isCompatible(object) === false) return;

      if (Array.isArray(object.material)) {
        for (var i = 0, il = object.material.length; i < il; i++) {
          object.material[i] = getOutlineMaterial(object.material[i]);
        }
      } else {
        object.material = getOutlineMaterial(object.material);
      }

      originalOnBeforeRenders[object.uuid] = object.onBeforeRender;
      object.onBeforeRender = onBeforeRender;
    }

    function restoreOriginalMaterial(object) {
      if (isCompatible(object) === false) return;

      if (Array.isArray(object.material)) {
        for (var i = 0, il = object.material.length; i < il; i++) {
          object.material[i] = originalMaterials[object.material[i].uuid];
        }
      } else {
        object.material = originalMaterials[object.material.uuid];
      }

      object.onBeforeRender = originalOnBeforeRenders[object.uuid];
    }

    function onBeforeRender(renderer, scene, camera, geometry, material) {
      var originalMaterial = originalMaterials[material.uuid]; // just in case

      if (originalMaterial === undefined) return;
      updateUniforms(material, originalMaterial);
    }

    function updateUniforms(material, originalMaterial) {
      var outlineParameters = originalMaterial.userData.outlineParameters;
      material.uniforms.outlineAlpha.value = originalMaterial.opacity;

      if (outlineParameters !== undefined) {
        if (outlineParameters.thickness !== undefined) material.uniforms.outlineThickness.value = outlineParameters.thickness;
        if (outlineParameters.color !== undefined) material.uniforms.outlineColor.value.fromArray(outlineParameters.color);
        if (outlineParameters.alpha !== undefined) material.uniforms.outlineAlpha.value = outlineParameters.alpha;
      }

      if (originalMaterial.displacementMap) {
        material.uniforms.displacementMap.value = originalMaterial.displacementMap;
        material.uniforms.displacementScale.value = originalMaterial.displacementScale;
        material.uniforms.displacementBias.value = originalMaterial.displacementBias;
      }
    }

    function updateOutlineMaterial(material, originalMaterial) {
      if (material.name === 'invisible') return;
      var outlineParameters = originalMaterial.userData.outlineParameters;
      material.fog = originalMaterial.fog;
      material.toneMapped = originalMaterial.toneMapped;
      material.premultipliedAlpha = originalMaterial.premultipliedAlpha;
      material.displacementMap = originalMaterial.displacementMap;

      if (outlineParameters !== undefined) {
        if (originalMaterial.visible === false) {
          material.visible = false;
        } else {
          material.visible = outlineParameters.visible !== undefined ? outlineParameters.visible : true;
        }

        material.transparent = outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ? true : originalMaterial.transparent;
        if (outlineParameters.keepAlive !== undefined) cache[originalMaterial.uuid].keepAlive = outlineParameters.keepAlive;
      } else {
        material.transparent = originalMaterial.transparent;
        material.visible = originalMaterial.visible;
      }

      if (originalMaterial.wireframe === true || originalMaterial.depthTest === false) material.visible = false;

      if (originalMaterial.clippingPlanes) {
        material.clipping = true;
        material.clippingPlanes = originalMaterial.clippingPlanes;
        material.clipIntersection = originalMaterial.clipIntersection;
        material.clipShadows = originalMaterial.clipShadows;
      }

      material.version = originalMaterial.version; // update outline material if necessary
    }

    function cleanupCache() {
      var keys; // clear originialMaterials

      keys = Object.keys(originalMaterials);

      for (var i = 0, il = keys.length; i < il; i++) {
        originalMaterials[keys[i]] = undefined;
      } // clear originalOnBeforeRenders


      keys = Object.keys(originalOnBeforeRenders);

      for (var _i = 0, _il = keys.length; _i < _il; _i++) {
        originalOnBeforeRenders[keys[_i]] = undefined;
      } // remove unused outlineMaterial from cache


      keys = Object.keys(cache);

      for (var _i2 = 0, _il2 = keys.length; _i2 < _il2; _i2++) {
        var key = keys[_i2];

        if (cache[key].used === false) {
          cache[key].count++;

          if (cache[key].keepAlive === false && cache[key].count > removeThresholdCount) {
            delete cache[key];
          }
        } else {
          cache[key].used = false;
          cache[key].count = 0;
        }
      }
    }

    this.render = function (scene, camera) {
      var renderTarget;
      var forceClear = false;

      if (arguments[2] !== undefined) {
        console.warn('THREE.OutlineEffect.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.');
        renderTarget = arguments[2];
      }

      if (arguments[3] !== undefined) {
        console.warn('THREE.OutlineEffect.render(): the forceClear argument has been removed. Use .clear() instead.');
        forceClear = arguments[3];
      }

      if (renderTarget !== undefined) renderer.setRenderTarget(renderTarget);
      if (forceClear) renderer.clear();

      if (this.enabled === false) {
        renderer.render(scene, camera);
        return;
      }

      var currentAutoClear = renderer.autoClear;
      renderer.autoClear = this.autoClear;
      renderer.render(scene, camera);
      renderer.autoClear = currentAutoClear;
      this.renderOutline(scene, camera);
    };

    this.renderOutline = function (scene, camera) {
      var currentAutoClear = renderer.autoClear;
      var currentSceneAutoUpdate = scene.autoUpdate;
      var currentSceneBackground = scene.background;
      var currentShadowMapEnabled = renderer.shadowMap.enabled;
      scene.autoUpdate = false;
      scene.background = null;
      renderer.autoClear = false;
      renderer.shadowMap.enabled = false;
      scene.traverse(setOutlineMaterial);
      renderer.render(scene, camera);
      scene.traverse(restoreOriginalMaterial);
      cleanupCache();
      scene.autoUpdate = currentSceneAutoUpdate;
      scene.background = currentSceneBackground;
      renderer.autoClear = currentAutoClear;
      renderer.shadowMap.enabled = currentShadowMapEnabled;
    };
    /*
     * See #9918
     *
     * The following property copies and wrapper methods enable
     * OutlineEffect to be called from other *Effect, like
     *
     * effect = new StereoEffect( new OutlineEffect( renderer ) );
     *
     * function render () {
     *
    	 * 	effect.render( scene, camera );
     *
     * }
     */


    this.autoClear = renderer.autoClear;
    this.domElement = renderer.domElement;
    this.shadowMap = renderer.shadowMap;

    this.clear = function (color, depth, stencil) {
      renderer.clear(color, depth, stencil);
    };

    this.getPixelRatio = function () {
      return renderer.getPixelRatio();
    };

    this.setPixelRatio = function (value) {
      renderer.setPixelRatio(value);
    };

    this.getSize = function (target) {
      return renderer.getSize(target);
    };

    this.setSize = function (width, height, updateStyle) {
      renderer.setSize(width, height, updateStyle);
    };

    this.setViewport = function (x, y, width, height) {
      renderer.setViewport(x, y, width, height);
    };

    this.setScissor = function (x, y, width, height) {
      renderer.setScissor(x, y, width, height);
    };

    this.setScissorTest = function (boolean) {
      renderer.setScissorTest(boolean);
    };

    this.setRenderTarget = function (renderTarget) {
      renderer.setRenderTarget(renderTarget);
    };
  });

  _exports.OutlineEffect = OutlineEffect;
});