text: Object.assign(pc, function () {
  
      var maxSize = 2048;
      var maskBaked = 2;
      var maskLightmap = 4;
  
      var sceneLightmaps = [];
      var sceneLightmapsNode = [];
      var lmCamera;
      var tempVec = new pc.Vec3();
      var bounds = new pc.BoundingBox();
      var lightBounds = new pc.BoundingBox();
      var tempSphere = {};
  
      var PASS_COLOR = 0;
      var PASS_DIR = 1;
  
      var passTexName = ["texture_lightMap", "texture_dirLightMap"];
      var passMaterial = [];
  
  
      function collectModels(node, nodes, nodesMeshInstances, allNodes) {
          if (!node.enabled) return;
  
          var i;
          if (node.model && node.model.model && node.model.enabled) {
              if (allNodes) allNodes.push(node);
              if (node.model.lightmapped) {
                  if (nodes) {
                      var hasUv1 = true;
                      var meshInstances = node.model.model.meshInstances;
                      for (i = 0; i < meshInstances.length; i++) {
                          if (!meshInstances[i].mesh.vertexBuffer.format.hasUv1) {
                              hasUv1 = false;
                              break;
                          }
                      }
                      if (hasUv1) {
  
                          var j;
                          var isInstance;
                          var notInstancedMeshInstances = [];
                          for (i = 0; i < meshInstances.length; i++) {
                              isInstance = false;
                              for (j = 0; j < meshInstances.length; j++) {
                                  if (i !== j) {
                                      if (meshInstances[i].mesh === meshInstances[j].mesh) {
                                          isInstance = true;
                                      }
                                  }
                              }
                              // collect each instance (object with shared VB) as separate "node"
                              if (isInstance) {
                                  nodes.push(node);
                                  nodesMeshInstances.push([meshInstances[i]]);
                              } else {
                                  notInstancedMeshInstances.push(meshInstances[i]);
                              }
                          }
  
                          // collect all non-shared objects as one "node"
                          if (notInstancedMeshInstances.length > 0) {
                              nodes.push(node);
                              nodesMeshInstances.push(notInstancedMeshInstances);
                          }
                      }
                  }
              }
          }
          for (i = 0; i < node._children.length; i++) {
              collectModels(node._children[i], nodes, nodesMeshInstances, allNodes);
          }
      }
  
      /**
       * @constructor
       * @name pc.Lightmapper
       * @classdesc The lightmapper is used to bake scene lights into textures.
       * @param {pc.GraphicsDevice} device The grahpics device used by the lightmapper.
       * @param {pc.Entity} root The root entity of the scene.
       * @param {pc.Scene} scene The scene to lightmap.
       * @param {pc.ForwardRenderer} renderer The renderer.
       * @param {Array} assets Array of assets to lightmap.
       */
      var Lightmapper = function (device, root, scene, renderer, assets) {
          this.device = device;
          this.root = root;
          this.scene = scene;
          this.renderer = renderer;
          this.assets = assets;
  
          // #ifdef PROFILER
          this._stats = {
              renderPasses: 0,
              lightmapCount: 0,
              totalRenderTime: 0,
              forwardTime: 0,
              fboTime: 0,
              shadowMapTime: 0,
              compileTime: 0,
              shadersLinked: 0
          };
          // #endif
      };
  
      Object.assign(Lightmapper.prototype, {
          destroy: function () {
              this.device = null;
              this.root = null;
              this.scene = null;
              this.renderer = null;
              this.assets = null;
          },
  
          calculateLightmapSize: function (node) {
              var data, parent;
              var sizeMult = this.scene.lightmapSizeMultiplier || 16;
              var scale = tempVec;
              var area = { x: 1, y: 1, z: 1, uv: 1 };
  
              if (node.model.asset) {
                  data = this.assets.get(node.model.asset).data;
                  if (data.area) {
                      area.x = data.area.x;
                      area.y = data.area.y;
                      area.z = data.area.z;
                      area.uv = data.area.uv;
                  }
              } else if (node.model._area) {
                  data = node.model;
                  if (data._area) {
                      area.x = data._area.x;
                      area.y = data._area.y;
                      area.z = data._area.z;
                      area.uv = data._area.uv;
                  }
              }
              var areaMult = node.model.lightmapSizeMultiplier || 1;
              area.x *= areaMult;
              area.y *= areaMult;
              area.z *= areaMult;
  
              scale.copy(node.localScale);
              parent = node._parent;
              while (parent) {
                  scale.mul(parent.localScale);
                  parent = parent._parent;
              }
  
              // Negatively scaled nodes still need full size lightmaps.
              scale.x = Math.abs(scale.x);
              scale.y = Math.abs(scale.y);
              scale.z = Math.abs(scale.z);
  
              var totalArea = area.x * scale.y * scale.z +
                              area.y * scale.x * scale.z +
                              area.z * scale.x * scale.y;
              totalArea /= area.uv;
              totalArea = Math.sqrt(totalArea);
  
              return Math.min(pc.math.nextPowerOfTwo(totalArea * sizeMult), this.scene.lightmapMaxResolution || maxSize);
          },
  
          /**
           * @function
           * @name pc.Lightmapper#bake
           * @description Generates and applies the lightmaps.
           * @param {pc.Entity} nodes An array of models to render lightmaps for. If not supplied, full scene will be baked.
           * @param {Number} mode Baking mode. Possible values:
           * <ul>
           *     <li>pc.BAKE_COLOR: single color lightmap
           *     <li>pc.BAKE_COLORDIR: single color lightmap + dominant light direction (used for bump/specular)
           * </ul>
           * Only lights with bakeDir=true will be used for generating the dominant light direction.
           */
          bake: function (nodes, mode) {
  
              // #ifdef PROFILER
              var startTime = pc.now();
              this.device.fire('lightmapper:start', {
                  timestamp: startTime,
                  target: this
              });
              var stats = this._stats;
              // #endif
  
              var i, j;
              var device = this.device;
              var scene = this.scene;
  
              var passCount = 1;
              if (mode === undefined) mode = pc.BAKE_COLORDIR;
              if (mode === pc.BAKE_COLORDIR) passCount = 2;
              var pass;
  
              // #ifdef PROFILER
              stats.renderPasses = stats.shadowMapTime = stats.forwardTime = 0;
              var startShaders = device._shaderStats.linked;
              var startFboTime = device._renderTargetCreationTime;
              var startCompileTime = device._shaderStats.compileTime;
              // #endif
  
              var allNodes = [];
              var nodesMeshInstances = [];
              if (!nodes) {
                  // ///// Full bake /////
  
                  // delete old lightmaps, if present
                  for (i = 0; i < sceneLightmaps.length; i++) {
                      for (j = 0; j < sceneLightmaps[i].length; j++) {
                          sceneLightmaps[i][j].destroy();
                      }
                  }
                  sceneLightmaps = [];
                  sceneLightmapsNode = [];
  
                  // collect
                  nodes = [];
                  collectModels(this.root, nodes, nodesMeshInstances, allNodes);
              } else {
                  // ///// Selected bake /////
  
                  // delete old lightmaps, if present
                  var k;
                  for (i = sceneLightmapsNode.length - 1; i >= 0; i--) {
                      for (j = 0; j < nodes.length; j++) {
                          if (sceneLightmapsNode[i] === nodes[j]) {
                              for (k = 0; k < sceneLightmaps[i].length; k++) {
                                  sceneLightmaps[i][k].destroy();
                              }
                              sceneLightmaps.splice(i, 1);
                              sceneLightmapsNode.splice(i, 1);
                          }
                      }
                  }
  
                  // collect
                  var _nodes = [];
                  for (i = 0; i < nodes.length; i++) {
                      collectModels(nodes[i], _nodes, nodesMeshInstances);
                  }
                  nodes = _nodes;
  
                  collectModels(this.root, null, null, allNodes);
              }
  
              if (nodes.length === 0) {
                  device.fire('lightmapper:end', {
                      timestamp: pc.now(),
                      target: this
                  });
  
                  return;
              }
  
              // #ifdef PROFILER
              stats.lightmapCount = nodes.length;
              // #endif
  
              // Disable static preprocessing (lightmapper needs original model draw calls)
              var revertStatic = false;
              if (scene._needsStaticPrepare) {
                  scene._needsStaticPrepare = false;
                  revertStatic = true;
              }
  
              // Calculate lightmap sizes and allocate textures
              var texSize = [];
              var lmaps = [[], []];
              var texPool = {};
              var size;
              var tex;
              var blackTex = new pc.Texture(this.device, {
                  width: 4,
                  height: 4,
                  format: pc.PIXELFORMAT_R8_G8_B8_A8,
                  rgbm: true
              });
              for (i = 0; i < nodes.length; i++) {
                  size = this.calculateLightmapSize(nodes[i]);
                  texSize.push(size);
                  for (pass = 0; pass < passCount; pass++) {
                      tex = new pc.Texture(device, {
                          // #ifdef PROFILER
                          profilerHint: pc.TEXHINT_LIGHTMAP,
                          // #endif
                          width: size,
                          height: size,
                          format: pc.PIXELFORMAT_R8_G8_B8_A8,
                          mipmaps: false,
                          rgbm: (pass === PASS_COLOR),
                          minFilter: pc.FILTER_NEAREST,
                          magFilter: pc.FILTER_NEAREST
                      });
  
                      lmaps[pass].push(tex);
                  }
  
                  if (!texPool[size]) {
                      var tex2 = new pc.Texture(device, {
                          // #ifdef PROFILER
                          profilerHint: pc.TEXHINT_LIGHTMAP,
                          // #endif
                          width: size,
                          height: size,
                          format: pc.PIXELFORMAT_R8_G8_B8_A8,
                          mipmaps: false,
                          rgbm: true,
                          minFilter: pc.FILTER_NEAREST,
                          magFilter: pc.FILTER_NEAREST
                      });
  
                      var targ2 = new pc.RenderTarget(device, tex2, {
                          depth: false
                      });
                      texPool[size] = targ2;
                  }
              }
  
              var activeComp = scene.layers;
              activeComp._update();
  
              // Collect bakeable lights
              var lights = [];
              var origMask = [];
              var origShadowMode = [];
              var origEnabled = [];
              var sceneLights = activeComp._lights;
              var mask;
              for (i = 0; i < sceneLights.length; i++) {
                  if (sceneLights[i]._enabled) {
                      mask = sceneLights[i]._mask;
                      if ((mask & maskLightmap) !== 0) {
                          origMask.push(mask);
                          origShadowMode.push(sceneLights[i].shadowUpdateMode);
                          sceneLights[i]._mask = 0xFFFFFFFF;
                          sceneLights[i].shadowUpdateMode =
                              sceneLights[i]._type === pc.LIGHTTYPE_DIRECTIONAL ? pc.SHADOWUPDATE_REALTIME : pc.SHADOWUPDATE_THISFRAME;
                          lights.push(sceneLights[i]);
                          sceneLights[i].isStatic = false; // if baked, can't be used as static
                      }
                  }
                  origEnabled.push(sceneLights[i]._enabled);
                  sceneLights[i].enabled = false;
              }
  
  
              // Init shaders
              var chunks = pc.shaderChunks;
              var xformUv1 = "#define UV1LAYOUT\n" + chunks.transformVS;
              var bakeLmEnd = chunks.bakeLmEndPS;
              var dilate = chunks.dilatePS;
  
              var dilateShader = chunks.createShaderFromCode(device, chunks.fullscreenQuadVS, dilate, "lmDilate");
              var constantTexSource = device.scope.resolve("source");
              var constantPixelOffset = device.scope.resolve("pixelOffset");
              var constantBakeDir = device.scope.resolve("bakeDir");
  
              var pixelOffset = new Float32Array(2);
  
              var drawCalls = activeComp._meshInstances;
  
              // update scene matrices
              for (i = 0; i < drawCalls.length; i++) {
                  if (drawCalls[i].node) drawCalls[i].node.getWorldTransform();
              }
  
              // Store scene values
              var origFog = scene.fog;
              var origAmbientR = scene.ambientLight.r;
              var origAmbientG = scene.ambientLight.g;
              var origAmbientB = scene.ambientLight.b;
  
              scene.fog = pc.FOG_NONE;
              scene.ambientLight.set(0, 0, 0);
  
              // Create pseudo-camera
              if (!lmCamera) {
                  lmCamera = new pc.Camera();
                  lmCamera._node = new pc.GraphNode();
                  lmCamera.clearColor[0] = 0;
                  lmCamera.clearColor[1] = 0;
                  lmCamera.clearColor[2] = 0;
                  lmCamera.clearColor[3] = 0;
                  lmCamera.clearDepth = 1;
                  lmCamera.clearFlags = pc.CLEARFLAG_COLOR;
                  lmCamera.clearStencil = null;
                  lmCamera.frustumCulling = false;
              }
  
              var node;
              var lm, rcv, m;
  
              // Disable existing scene lightmaps
              var origShaderDefs = [];
              origShaderDefs.length = sceneLightmapsNode.length;
              var shaderDefs;
              for (node = 0; node < allNodes.length; node++) {
                  rcv = allNodes[node].model.model.meshInstances;
                  shaderDefs = [];
                  for (i = 0; i < rcv.length; i++) {
                      shaderDefs.push(rcv[i]._shaderDefs);
                      rcv[i]._shaderDefs &= ~(pc.SHADERDEF_LM | pc.SHADERDEF_DIRLM);
                  }
                  for (i = 0; i < sceneLightmapsNode.length; i++) {
                      if (sceneLightmapsNode[i] === allNodes[node]) {
                          origShaderDefs[i] = shaderDefs;
                          break;
                      }
                  }
              }
  
              // Change shadow casting
              var origCastShadows = [];
              var casters = [];
              var meshes;
              for (node = 0; node < allNodes.length; node++) {
                  origCastShadows[node] = allNodes[node].model.castShadows;
                  allNodes[node].model.castShadows = allNodes[node].model.castShadowsLightmap;
                  if (allNodes[node].model.castShadowsLightmap) {
                      meshes = allNodes[node].model.meshInstances;
                      for (i = 0; i < meshes.length; i++) {
                          meshes[i].visibleThisFrame = true;
                          casters.push(meshes[i]);
                      }
                  }
              }
  
              this.renderer.updateCpuSkinMatrices(casters);
              this.renderer.gpuUpdate(casters);
  
              var origMat = [];
  
              // Prepare models
              var nodeBounds = [];
              var nodeTarg = [[], []];
              var targ, targTmp, texTmp;
              var light, shadowCam;
              var nodeLightCount = [];
              nodeLightCount.length = nodes.length;
  
              var lmMaterial;
              for (pass = 0; pass < passCount; pass++) {
                  if (!passMaterial[pass]) {
                      lmMaterial = new pc.StandardMaterial();
                      lmMaterial.chunks.transformVS = xformUv1; // draw UV1
  
                      if (pass === PASS_COLOR) {
                          lmMaterial.chunks.endPS = bakeLmEnd; // encode to RGBM
                          // don't bake ambient
                          lmMaterial.ambient = new pc.Color(0, 0, 0);
                          lmMaterial.ambientTint = true;
                          lmMaterial.lightMap = blackTex;
                      } else {
                          lmMaterial.chunks.basePS = chunks.basePS + "\nuniform sampler2D texture_dirLightMap;\nuniform float bakeDir;\n";
                          lmMaterial.chunks.endPS = chunks.bakeDirLmEndPS;
                      }
  
                      // avoid writing unrelated things to alpha
                      lmMaterial.chunks.outputAlphaPS = "\n";
                      lmMaterial.chunks.outputAlphaOpaquePS = "\n";
                      lmMaterial.chunks.outputAlphaPremulPS = "\n";
                      lmMaterial.cull = pc.CULLFACE_NONE;
                      lmMaterial.forceUv1 = true; // provide data to xformUv1
                      lmMaterial.update();
                      lmMaterial.updateShader(device, scene);
                      lmMaterial.name = "lmMaterial" + pass;
  
                      passMaterial[pass] = lmMaterial;
                  }
              }
  
              for (node = 0; node < nodes.length; node++) {
                  rcv = nodesMeshInstances[node];
                  nodeLightCount[node] = 0;
  
                  // Calculate model AABB
                  if (rcv.length > 0) {
                      bounds.copy(rcv[0].aabb);
                      for (i = 0; i < rcv.length; i++) {
                          rcv[i].node.getWorldTransform();
                          bounds.add(rcv[i].aabb);
                      }
                  }
                  var nbounds = new pc.BoundingBox();
                  nbounds.copy(bounds);
                  nodeBounds.push(nbounds);
  
                  for (i = 0; i < rcv.length; i++) {
                      // patch meshInstance
                      m = rcv[i];
                      m._shaderDefs &= ~(pc.SHADERDEF_LM | pc.SHADERDEF_DIRLM); // disable LM define, if set, to get bare ambient on first pass
                      m.mask = maskLightmap; // only affected by LM lights
                      m.deleteParameter("texture_lightMap");
                      m.deleteParameter("texture_dirLightMap");
  
                      // patch material
                      m.setParameter("texture_lightMap", m.material.lightMap ? m.material.lightMap : blackTex);
                      m.setParameter("texture_dirLightMap", blackTex);
                  }
  
                  for (pass = 0; pass < passCount; pass++) {
                      lm = lmaps[pass][node];
                      targ = new pc.RenderTarget(device, lm, {
                          depth: false
                      });
                      nodeTarg[pass].push(targ);
                  }
              }
  
              // Disable all bakeable lights
              for (j = 0; j < lights.length; j++)
                  lights[j].enabled = false;
  
              var lightArray = [[], [], []];
  
              // Accumulate lights into RGBM textures
              var shadersUpdatedOn1stPass = false;
              var shadowMapRendered;
              for (i = 0; i < lights.length; i++) {
  
                  lights[i].enabled = true; // enable next light
                  shadowMapRendered = false;
  
                  lights[i]._cacheShadowMap = true;
                  if (lights[i]._type !== pc.LIGHTTYPE_DIRECTIONAL) {
                      lights[i]._node.getWorldTransform();
                      lights[i].getBoundingSphere(tempSphere);
                      lightBounds.center = tempSphere.center;
                      lightBounds.halfExtents.x = tempSphere.radius;
                      lightBounds.halfExtents.y = tempSphere.radius;
                      lightBounds.halfExtents.z = tempSphere.radius;
                  }
                  if (lights[i]._type === pc.LIGHTTYPE_SPOT) {
                      light = lights[i];
                      shadowCam = this.renderer.getShadowCamera(device, light);
  
                      shadowCam._node.setPosition(light._node.getPosition());
                      shadowCam._node.setRotation(light._node.getRotation());
                      shadowCam._node.rotateLocal(-90, 0, 0);
  
                      shadowCam.projection = pc.PROJECTION_PERSPECTIVE;
                      shadowCam.nearClip = light.attenuationEnd / 1000;
                      shadowCam.farClip = light.attenuationEnd;
                      shadowCam.aspectRatio = 1;
                      shadowCam.fov = light._outerConeAngle * 2;
  
                      this.renderer.updateCameraFrustum(shadowCam);
                  }
  
                  if (nodesMeshInstances.length > 0) {
                      this.renderer.updateShaders(nodesMeshInstances[0]);
                  }
  
                  for (node = 0; node < nodes.length; node++) {
  
                      rcv = nodesMeshInstances[node];
                      bounds = nodeBounds[node];
  
                      // Tweak camera to fully see the model, so directional light frustum will also see it
                      if (lights[i]._type === pc.LIGHTTYPE_DIRECTIONAL) {
                          tempVec.copy(bounds.center);
                          tempVec.y += bounds.halfExtents.y;
  
                          lmCamera._node.setPosition(tempVec);
                          lmCamera._node.setEulerAngles(-90, 0, 0);
  
                          var frustumSize = Math.max(bounds.halfExtents.x, bounds.halfExtents.z);
  
                          lmCamera.projection = pc.PROJECTION_ORTHOGRAPHIC;
                          lmCamera.nearClip = 0;
                          lmCamera.farClip = bounds.halfExtents.y * 2;
                          lmCamera.aspectRatio = 1;
                          lmCamera.orthoHeight = frustumSize;
                      } else {
                          if (!lightBounds.intersects(bounds)) {
                              continue;
                          }
                      }
  
                      if (lights[i]._type === pc.LIGHTTYPE_SPOT) {
                          var nodeVisible = false;
                          for (j = 0; j < rcv.length; j++) {
                              if (this.renderer._isVisible(shadowCam, rcv[j])) {
                                  nodeVisible = true;
                                  break;
                              }
                          }
                          if (!nodeVisible) {
                              continue;
                          }
                      }
  
                      if (lights[i]._type === pc.LIGHTTYPE_DIRECTIONAL) {
                          lightArray[pc.LIGHTTYPE_DIRECTIONAL][0] = lights[i];
                          lightArray[pc.LIGHTTYPE_POINT].length = 0;
                          lightArray[pc.LIGHTTYPE_SPOT].length = 0;
                          if (!shadowMapRendered && lights[i].castShadows) {
                              this.renderer.cullDirectionalShadowmap(lights[i], casters, lmCamera, 0);
                              this.renderer.renderShadows(lightArray[pc.LIGHTTYPE_DIRECTIONAL], 0);
                              shadowMapRendered = true;
                          }
                      } else {
                          lightArray[pc.LIGHTTYPE_DIRECTIONAL].length = 0;
                          if (lights[i]._type === pc.LIGHTTYPE_POINT) {
                              lightArray[pc.LIGHTTYPE_POINT][0] = lights[i];
                              lightArray[pc.LIGHTTYPE_SPOT].length = 0;
                              if (!shadowMapRendered && lights[i].castShadows) {
                                  this.renderer.cullLocalShadowmap(lights[i], casters);
                                  this.renderer.renderShadows(lightArray[pc.LIGHTTYPE_POINT]);
                                  shadowMapRendered = true;
                              }
                          } else {
                              lightArray[pc.LIGHTTYPE_POINT].length = 0;
                              lightArray[pc.LIGHTTYPE_SPOT][0] = lights[i];
                              if (!shadowMapRendered && lights[i].castShadows) {
                                  this.renderer.cullLocalShadowmap(lights[i], casters);
                                  this.renderer.renderShadows(lightArray[pc.LIGHTTYPE_SPOT]);
                                  shadowMapRendered = true;
                              }
                          }
                      }
  
                      // Store original materials
                      for (j = 0; j < rcv.length; j++) {
                          origMat[j] = rcv[j].material;
                      }
  
                      for (pass = 0; pass < passCount; pass++) {
                          lm = lmaps[pass][node];
                          targ = nodeTarg[pass][node];
                          targTmp = texPool[lm.width];
                          texTmp = targTmp.colorBuffer;
  
                          if (pass === 0) {
                              shadersUpdatedOn1stPass = scene.updateShaders;
                          } else if (shadersUpdatedOn1stPass) {
                              scene.updateShaders = true;
                          }
  
                          for (j = 0; j < rcv.length; j++) {
                              rcv[j].material = passMaterial[pass];
                          }
                          if (passCount > 1) {
                              this.renderer.updateShaders(rcv); // update between passes
                          }
  
                          // ping-ponging output
                          this.renderer.setCamera(lmCamera, targTmp, true);
  
                          if (pass === PASS_DIR) {
                              constantBakeDir.setValue(lights[i].bakeDir ? 1 : 0);
                          }
  
                          // console.log("Baking light "+lights[i]._node.name + " on model " + nodes[node].name);
  
                          this.renderer._forwardTime = 0;
                          this.renderer._shadowMapTime = 0;
  
                          this.renderer.renderForward(lmCamera,
                                                      rcv, rcv.length,
                                                      lightArray,
                                                      pc.SHADER_FORWARDHDR);
  
                          // #ifdef PROFILER
                          stats.shadowMapTime += this.renderer._shadowMapTime;
                          stats.forwardTime += this.renderer._forwardTime;
                          stats.renderPasses++;
                          // #endif
  
                          lmaps[pass][node] = texTmp;
                          nodeTarg[pass][node] = targTmp;
                          texPool[lm.width] = targ;
  
                          for (j = 0; j < rcv.length; j++) {
                              m = rcv[j];
                              m.setParameter(passTexName[pass], texTmp); // ping-ponging input
                              m._shaderDefs |= pc.SHADERDEF_LM; // force using LM even if material doesn't have it
                          }
                      }
  
                      nodeLightCount[node]++;
  
                      // Revert original materials
                      for (j = 0; j < rcv.length; j++) {
                          rcv[j].material = origMat[j];
                      }
                  }
  
                  lights[i].enabled = false; // disable that light
                  lights[i]._cacheShadowMap = false;
                  if (lights[i]._isCachedShadowMap) {
                      lights[i]._destroyShadowMap();
                  }
              }
  
  
              var sceneLmaps;
              for (node = 0; node < nodes.length; node++) {
                  rcv = nodesMeshInstances[node];
                  sceneLmaps = [];
  
                  for (pass = 0; pass < passCount; pass++) {
                      lm = lmaps[pass][node];
                      targ = nodeTarg[pass][node];
                      targTmp = texPool[lm.width];
                      texTmp = targTmp.colorBuffer;
  
                      // Dilate
                      var numDilates2x = 4; // 8 dilates
                      pixelOffset[0] = 1 / lm.width;
                      pixelOffset[1] = 1 / lm.height;
                      constantPixelOffset.setValue(pixelOffset);
                      for (i = 0; i < numDilates2x; i++) {
                          constantTexSource.setValue(lm);
                          pc.drawQuadWithShader(device, targTmp, dilateShader);
  
                          constantTexSource.setValue(texTmp);
                          pc.drawQuadWithShader(device, targ, dilateShader);
                      }
  
  
                      for (i = 0; i < rcv.length; i++) {
                          m = rcv[i];
                          m.mask = maskBaked;
  
                          // Set lightmap
                          rcv[i].setParameter(passTexName[pass], lm);
                          if (pass === PASS_DIR) rcv[i]._shaderDefs |= pc.SHADERDEF_DIRLM;
                      }
                      sceneLmaps[pass] = lm;
  
                      // Clean up
                      if (pass === passCount - 1) targ.destroy();
                  }
  
                  sceneLightmaps.push(sceneLmaps);
                  sceneLightmapsNode.push(nodes[node]);
              }
  
              for (var key in texPool) {
                  if (texPool.hasOwnProperty(key)) {
                      texPool[key].colorBuffer.destroy();
                      texPool[key].destroy();
                  }
              }
  
              // Set up linear filtering
              for (i = 0; i < sceneLightmaps.length; i++) {
                  for (j = 0; j < sceneLightmaps[i].length; j++) {
                      tex = sceneLightmaps[i][j];
                      tex.minFilter = pc.FILTER_LINEAR;
                      tex.magFilter = pc.FILTER_LINEAR;
                  }
              }
  
              // Revert shadow casting
              for (node = 0; node < allNodes.length; node++) {
                  allNodes[node].model.castShadows = origCastShadows[node];
              }
  
              // Enable existing scene lightmaps
              for (i = 0; i < origShaderDefs.length; i++) {
                  if (origShaderDefs[i]) {
                      rcv = sceneLightmapsNode[i].model.model.meshInstances;
                      for (j = 0; j < rcv.length; j++) {
                          rcv[j]._shaderDefs |= origShaderDefs[i][j] & (pc.SHADERDEF_LM | pc.SHADERDEF_DIRLM);
                      }
                  }
              }
  
              // Enable all lights back
              for (i = 0; i < lights.length; i++) {
                  lights[i]._mask = origMask[i];
                  lights[i].shadowUpdateMode = origShadowMode[i];
              }
  
              for (i = 0; i < sceneLights.length; i++) {
                  sceneLights[i].enabled = origEnabled[i];
              }
  
              // Roll back scene stuff
              scene.fog = origFog;
              scene.ambientLight.set(origAmbientR, origAmbientG, origAmbientB);
  
              // Revert static preprocessing
              if (revertStatic) {
                  scene._needsStaticPrepare = true;
              }
  
              // #ifdef PROFILER
              this.device.fire('lightmapper:end', {
                  timestamp: pc.now(),
                  target: this
              });
  
              stats.totalRenderTime = pc.now() - startTime;
              stats.shadersLinked = device._shaderStats.linked - startShaders;
              stats.compileTime = device._shaderStats.compileTime - startCompileTime;
              stats.fboTime = device._renderTargetCreationTime - startFboTime;
              // #endif
          }
      });
  
      return {
          Lightmapper: Lightmapper
      };
  }());
  