var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var image_exports = {};
__export(image_exports, {
  renderBoxelToDataURL: () => renderBoxelToDataURL
});
module.exports = __toCommonJS(image_exports);
var import_scene = require("../scene.js");
var import_glb = require("./glb.js");
var import_skinned_mesh = require("../skinned-mesh.js");
var import_animations = require("../animations.js");
function findNodeById(root, id) {
  if (root.id === id)
    return root;
  for (const child of root.children) {
    const found = findNodeById(child, id);
    if (found)
      return found;
  }
  return null;
}
const DIRECTION_VECTORS = {
  preview: [1.2, 1.4, 1.3],
  headshot: [-0.35, 0.05, 1],
  top: [0, 1, 0],
  bottom: [0, -1, 0],
  front: [0, 0, 1],
  back: [0, 0, -1],
  left: [-1, 0, 0],
  right: [1, 0, 0]
};
const UP_VECTORS = {
  top: [0, 0, -1],
  bottom: [0, 0, 1]
};
const CAMERA_DISTANCE_FACTOR = 1.25;
const DEFAULT_MIN_DISTANCE = 2.5;
const HEADSHOT_MIN_DISTANCE = 2;
const HEADSHOT_RADIUS_PADDING = 1.3;
const HEAD_NODE_IDS = ["head", "Head"];
const HEADSHOT_VERTICAL_OFFSET_RATIO = 2 / 3;
const AMBIENT_LIGHT_COLOR = 11847420;
const AMBIENT_LIGHT_INTENSITY = 2.2;
const DIRECTIONAL_LIGHT_COLOR = 16317180;
const DIRECTIONAL_LIGHT_INTENSITY = 1.3;
const DIRECTIONAL_LIGHT_MIN_HEIGHT_OFFSET = 2;
const SHADOW_MAP_SIZE = 2048;
const SHADOW_BIAS = -12e-5;
const SHADOW_NORMAL_BIAS = 6e-3;
const SUPER_SAMPLE_RATIO = 2;
async function renderBoxelToDataURL(options) {
  const { THREE: three, renderer, entity, mode = "hierarchical", animationId, width, height, direction = "preview", background = null } = options;
  const sizeTarget = new three.Vector2();
  renderer.getSize(sizeTarget);
  const previousWidth = sizeTarget.x;
  const previousHeight = sizeTarget.y;
  const previousPixelRatio = renderer.getPixelRatio();
  const previousShadowEnabled = renderer.shadowMap.enabled;
  const previousShadowType = renderer.shadowMap.type;
  const previousShadowAutoUpdate = renderer.shadowMap.autoUpdate;
  const clearColor = renderer.getClearColor(new three.Color());
  const clearAlpha = renderer.getClearAlpha();
  const targetWidth = width ?? (previousWidth || 1024);
  const targetHeight = height ?? (previousHeight || 1024);
  renderer.setPixelRatio(SUPER_SAMPLE_RATIO);
  renderer.setSize(targetWidth, targetHeight, false);
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.autoUpdate = true;
  renderer.shadowMap.type = three.PCFSoftShadowMap;
  if (background === null) {
    renderer.setClearColor(0, 0);
  } else if (background !== void 0) {
    const color = new three.Color(background);
    renderer.setClearColor(color, 1);
  }
  let disposeScene;
  let root;
  let animations = [];
  let nodeLookup = null;
  if (mode === "hierarchical") {
    const scene2 = (0, import_scene.createBoxelScene)({ THREE: three, entity });
    disposeScene = scene2.dispose;
    root = scene2.root;
    animations = scene2.animations;
    nodeLookup = /* @__PURE__ */ new Map();
    scene2.nodes.forEach((meta) => {
      nodeLookup.set(meta.id, meta.object);
    });
  } else if (mode === "skinned") {
    const skinned = (0, import_skinned_mesh.buildEntitySkinnedMesh)({ THREE: three, entity });
    const group = new three.Group();
    group.add(skinned.mesh);
    disposeScene = () => {
      skinned.dispose();
    };
    root = group;
    nodeLookup = /* @__PURE__ */ new Map();
    skinned.boneMap.forEach((bone, nodeId) => nodeLookup.set(nodeId, bone));
    animations = (0, import_animations.buildAnimations)(three, entity, nodeLookup);
  } else {
    const sceneGraph = (0, import_glb.createBoxelSceneGraph)({ THREE: three, model: entity, mode });
    disposeScene = sceneGraph.dispose;
    root = sceneGraph.root;
    animations = sceneGraph.animations;
    nodeLookup = sceneGraph.nodeLookup;
  }
  const scene = new three.Scene();
  scene.background = background === null ? null : background === void 0 ? null : new three.Color(background);
  scene.add(root);
  root.traverse((obj) => {
    const mesh = obj;
    if (mesh.isMesh) {
      mesh.castShadow = true;
      mesh.receiveShadow = true;
    }
  });
  const ambient = new three.AmbientLight(AMBIENT_LIGHT_COLOR, AMBIENT_LIGHT_INTENSITY);
  const directional = new three.DirectionalLight(DIRECTIONAL_LIGHT_COLOR, DIRECTIONAL_LIGHT_INTENSITY);
  directional.castShadow = true;
  directional.shadow.mapSize.set(SHADOW_MAP_SIZE, SHADOW_MAP_SIZE);
  directional.shadow.bias = SHADOW_BIAS;
  directional.shadow.normalBias = SHADOW_NORMAL_BIAS;
  scene.add(ambient);
  scene.add(directional);
  let dataUrl = "";
  const requestedAnimationId = typeof animationId === "string" && animationId.trim().length > 0 ? animationId.trim() : null;
  const normalizedRequestedAnimationId = requestedAnimationId?.toLowerCase();
  let sampleClip = null;
  if (requestedAnimationId) {
    sampleClip = animations.find((entry) => entry.id === requestedAnimationId || entry.id.toLowerCase() === normalizedRequestedAnimationId) ?? null;
    if (!sampleClip) {
      throw new Error(`Animation "${requestedAnimationId}" was not found for this entity.`);
    }
  } else if ((direction === "preview" || direction === "headshot") && animations.length > 0) {
    const idleAnimation = animations.find((entry) => entry.id.toLowerCase().includes("idle"));
    if (idleAnimation) {
      sampleClip = idleAnimation;
    }
  }
  let mixer = null;
  if (sampleClip) {
    mixer = new three.AnimationMixer(root);
    const action = mixer.clipAction(sampleClip.clip);
    action.play();
    action.paused = true;
    const firstKeyframe = sampleClip.channels.reduce((min, channel) => {
      const times = channel.track.times;
      if (!times || times.length === 0) {
        return min;
      }
      const firstTime = times[0];
      return Number.isFinite(firstTime) && firstTime < min ? firstTime : min;
    }, Number.POSITIVE_INFINITY);
    const sampleTime = Number.isFinite(firstKeyframe) ? firstKeyframe : 0;
    mixer.setTime(sampleTime);
    mixer.update(0);
  }
  try {
    root.updateMatrixWorld(true);
    const bounds = new three.Box3().setFromObject(root);
    const sphere = bounds.getBoundingSphere(new three.Sphere());
    const center = sphere.center.clone();
    let radius = sphere.radius || 1;
    let headCenter = null;
    if (direction === "headshot" && nodeLookup) {
      const headObject = HEAD_NODE_IDS.map((id) => nodeLookup.get(id)).find(Boolean);
      if (headObject) {
        let headBounds;
        if (mode === "skinned" && headObject.type === "Bone") {
          const headNode = findNodeById(entity.geometry.root, headObject.name);
          if (headNode?.size) {
            headObject.updateMatrixWorld(true);
            const nodeCenter = headNode.center ?? [0, 0, 0];
            const halfSize = headNode.size.map((s) => s / 2);
            const localMin = new three.Vector3(nodeCenter[0] - halfSize[0], nodeCenter[1] - halfSize[1], nodeCenter[2] - halfSize[2]);
            const localMax = new three.Vector3(nodeCenter[0] + halfSize[0], nodeCenter[1] + halfSize[1], nodeCenter[2] + halfSize[2]);
            localMin.applyMatrix4(headObject.matrixWorld);
            localMax.applyMatrix4(headObject.matrixWorld);
            headBounds = new three.Box3(localMin, localMax);
          } else {
            headBounds = new three.Box3().setFromObject(headObject);
          }
        } else {
          headBounds = new three.Box3().setFromObject(headObject);
        }
        if (!headBounds.isEmpty()) {
          headCenter = headBounds.getCenter(new three.Vector3());
          center.copy(headCenter);
          const headSphere = headBounds.getBoundingSphere(new three.Sphere());
          const headRadius = headSphere.radius || radius;
          radius = Math.max(headRadius * HEADSHOT_RADIUS_PADDING, headRadius);
          center.y -= radius * HEADSHOT_VERTICAL_OFFSET_RATIO;
        }
      }
    }
    const camera = new three.PerspectiveCamera(50, targetWidth / targetHeight, 0.1, Math.max(1e3, radius * 10));
    const dir = DIRECTION_VECTORS[direction] ?? DIRECTION_VECTORS.preview;
    const target = new three.Vector3().fromArray(dir).normalize();
    const minDistance = direction === "headshot" ? HEADSHOT_MIN_DISTANCE : DEFAULT_MIN_DISTANCE;
    let distance = Math.max(radius * CAMERA_DISTANCE_FACTOR, minDistance);
    const basePosition = center.clone().add(target.clone().multiplyScalar(distance));
    if (direction === "headshot" && headCenter) {
      const deltaY = headCenter.y - center.y;
      if (Math.abs(deltaY) >= distance) {
        distance = Math.abs(deltaY) + 0.1;
      }
      const horizontalMagnitude = Math.sqrt(Math.max(0, distance * distance - deltaY * deltaY));
      const horizontalDir = new three.Vector3(target.x, 0, target.z);
      if (horizontalDir.lengthSq() > 0) {
        horizontalDir.normalize().multiplyScalar(horizontalMagnitude);
        camera.position.set(center.x + horizontalDir.x, headCenter.y, center.z + horizontalDir.z);
      } else {
        camera.position.set(center.x, headCenter.y, center.z);
      }
    } else {
      camera.position.copy(basePosition);
    }
    camera.aspect = targetWidth / targetHeight;
    camera.updateProjectionMatrix();
    camera.lookAt(center);
    const up = UP_VECTORS[direction];
    if (up) {
      camera.up.set(up[0], up[1], up[2]);
    } else {
      camera.up.set(0, 1, 0);
    }
    const lightPosition = camera.position.clone();
    lightPosition.y = Math.max(lightPosition.y, center.y + DIRECTIONAL_LIGHT_MIN_HEIGHT_OFFSET);
    directional.position.copy(lightPosition);
    directional.target.position.copy(center);
    directional.target.updateMatrixWorld();
    const shadowCamera = directional.shadow.camera;
    const cameraRange = Math.max(10, radius * 4);
    shadowCamera.left = -cameraRange;
    shadowCamera.right = cameraRange;
    shadowCamera.top = cameraRange;
    shadowCamera.bottom = -cameraRange;
    shadowCamera.near = 0.1;
    shadowCamera.far = Math.max(50, radius * 8);
    shadowCamera.updateProjectionMatrix();
    scene.add(directional.target);
    renderer.render(scene, camera);
    if (typeof document !== "undefined") {
      const captureCanvas = document.createElement("canvas");
      captureCanvas.width = targetWidth;
      captureCanvas.height = targetHeight;
      const ctx = captureCanvas.getContext("2d");
      if (!ctx) {
        dataUrl = renderer.domElement.toDataURL("image/png");
      } else {
        ctx.clearRect(0, 0, targetWidth, targetHeight);
        ctx.drawImage(renderer.domElement, 0, 0, targetWidth, targetHeight);
        dataUrl = captureCanvas.toDataURL("image/png");
      }
    } else {
      dataUrl = renderer.domElement.toDataURL("image/png");
    }
    scene.remove(directional.target);
  } finally {
    if (mixer) {
      mixer.stopAllAction();
    }
    scene.remove(root);
    scene.remove(ambient);
    scene.remove(directional);
    disposeScene();
    renderer.setSize(previousWidth || targetWidth, previousHeight || targetHeight, false);
    renderer.setPixelRatio(previousPixelRatio || 1);
    renderer.shadowMap.enabled = previousShadowEnabled;
    renderer.shadowMap.type = previousShadowType;
    renderer.shadowMap.autoUpdate = previousShadowAutoUpdate;
    renderer.setClearColor(clearColor, clearAlpha);
  }
  return dataUrl;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  renderBoxelToDataURL
});
