Guest User

Cobbledex.info - Cobblemon renderer

a guest
Jul 25th, 2025
33
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. "use client";
  2.  
  3. import { useEffect, useRef } from "react";
  4. import {
  5.   Engine,
  6.   Scene,
  7.   ArcRotateCamera,
  8.   Vector3,
  9.   HemisphericLight,
  10.   MeshBuilder,
  11.   StandardMaterial,
  12.   Texture,
  13.   Vector4,
  14.   Mesh,
  15. } from "@babylonjs/core";
  16.  
  17. // Enhanced Molang implementation with proper math functions
  18. class Molang {
  19.   private context: any;
  20.   private options: any;
  21.  
  22.   constructor(context: any = {}, options: any = {}) {
  23.     this.context = context;
  24.     this.options = options;
  25.   }
  26.  
  27.   execute(expression: any, time: number = 0): number {
  28.     if (typeof expression === "number") return expression;
  29.     if (typeof expression !== "string") return 0;
  30.  
  31.     // Replace Molang variables and functions with JavaScript equivalents
  32.     let jsExpression = expression
  33.       .replace(/q\.anim_time/g, time.toString())
  34.       .replace(/math\.sin/g, "Math.sin")
  35.       .replace(/math\.cos/g, "Math.cos")
  36.       .replace(/math\.clamp/g, "Math.min(Math.max")
  37.       .replace(/math\.min/g, "Math.min")
  38.       .replace(/math\.max/g, "Math.max")
  39.       .replace(/math\.abs/g, "Math.abs")
  40.       .replace(/math\.floor/g, "Math.floor")
  41.       .replace(/math\.ceil/g, "Math.ceil");
  42.  
  43.     // Handle math.clamp function properly - convert clamp(value, min, max) to Math.min(Math.max(value, min), max)
  44.     jsExpression = jsExpression.replace(
  45.       /Math\.min\(Math\.max\(([^,]+),([^,]+),([^)]+)\)/g,
  46.       "Math.min(Math.max($1, $2), $3)"
  47.     );
  48.  
  49.     try {
  50.       // Safely evaluate the expression
  51.       return Function(`"use strict"; return (${jsExpression})`)();
  52.     } catch (error) {
  53.       console.warn("Failed to evaluate Molang expression:", expression, error);
  54.       return 0;
  55.     }
  56.   }
  57. }
  58.  
  59. // Extend Mesh type to include userData
  60. interface ExtendedMesh extends Mesh {
  61.   userData?: any;
  62. }
  63.  
  64. // Define props interface for the component
  65. interface BlockbenchRendererProps {
  66.   pokemonName: string;
  67.   geoData: any;
  68.   animData: any;
  69.   size?: { width: number; height: number };
  70. }
  71.  
  72. export default function BlockbenchRenderer({
  73.   pokemonName,
  74.   geoData,
  75.   animData,
  76.   size = { width: 150, height: 150 },
  77. }: BlockbenchRendererProps) {
  78.   const canvasRef = useRef<HTMLCanvasElement>(null);
  79.   const pokemon = pokemonName;
  80.   const doPosAnim = true;
  81.   const doRotAnim = true;
  82.  
  83.   useEffect(() => {
  84.     if (!canvasRef.current || !geoData || !animData) return;
  85.  
  86.     const engine = new Engine(canvasRef.current, true, {
  87.       preserveDrawingBuffer: true,
  88.       stencil: true,
  89.     });
  90.  
  91.     const scene = new Scene(engine);
  92.  
  93.     // Helper functions
  94.     const toRadians = (degrees: number) => degrees * (Math.PI / 180);
  95.  
  96.     const buildFaceUv = (
  97.       x1: number,
  98.       y1: number,
  99.       x2: number,
  100.       y2: number,
  101.       tw: number,
  102.       th: number
  103.     ) => {
  104.       return new Vector4(x1 / tw, (th - y2) / th, x2 / tw, (th - y1) / th);
  105.     };
  106.  
  107.     const buildTexture = ({ url }: { url: string }) => {
  108.       const mat = new StandardMaterial("mat", scene);
  109.       const texture = new Texture(url, scene, {
  110.         noMipmap: true,
  111.         samplingMode: Texture.NEAREST_SAMPLINGMODE,
  112.       });
  113.       mat.diffuseTexture = texture;
  114.       texture.hasAlpha = true;
  115.       return mat;
  116.     };
  117.  
  118.     const buildCube = ({
  119.       size = [0.1, 0.1, 0.1],
  120.       position = [0, 0, 0],
  121.       rotation = [0, 0, 0],
  122.       uv,
  123.       textureSize,
  124.       texture,
  125.       inflate = 0,
  126.       name,
  127.     }: {
  128.       size?: number[];
  129.       position?: number[];
  130.       rotation?: number[];
  131.       uv?: number[];
  132.       textureSize?: number[];
  133.       texture?: StandardMaterial;
  134.       inflate?: number;
  135.       name?: string;
  136.     } = {}) => {
  137.       const [sx, sy, sz] = size;
  138.       const rsx = sx || 0.01;
  139.       const rsy = sy || 0.01;
  140.       const rsz = sz || 0.01;
  141.       let faceUv;
  142.  
  143.       if (uv && textureSize) {
  144.         const [ox, oy] = uv;
  145.         const [tw, th] = textureSize;
  146.  
  147.         faceUv = [];
  148.         faceUv[0] = buildFaceUv(
  149.           ox + sx + 2 * sz,
  150.           oy + sz,
  151.           ox + 2 * sx + 2 * sz,
  152.           oy + sz + sy,
  153.           tw,
  154.           th
  155.         );
  156.         faceUv[1] = buildFaceUv(
  157.           ox + sz,
  158.           oy + sz,
  159.           ox + sz + sx,
  160.           oy + sz + sy,
  161.           tw,
  162.           th
  163.         );
  164.         faceUv[2] = buildFaceUv(
  165.           ox + sz + sx,
  166.           oy + sz,
  167.           ox + 2 * sz + sx,
  168.           oy + sz + sy,
  169.           tw,
  170.           th
  171.         );
  172.         faceUv[3] = buildFaceUv(ox, oy + sz, ox + sz, oy + sz + sy, tw, th);
  173.         faceUv[4] = buildFaceUv(ox + sz, oy, ox + sz + sx, oy + sz, tw, th);
  174.         faceUv[5] = buildFaceUv(
  175.           ox + sz + sx,
  176.           oy + sz,
  177.           ox + sz + 2 * sx,
  178.           oy,
  179.           tw,
  180.           th
  181.         );
  182.       }
  183.       const actualInflate = inflate + 1;
  184.       const box = MeshBuilder.CreateBox(
  185.         name || "box",
  186.         {
  187.           faceUV: faceUv,
  188.           wrap: true,
  189.           width: rsx,
  190.           height: rsy,
  191.           depth: rsz,
  192.         },
  193.         scene
  194.       ) as ExtendedMesh;
  195.  
  196.       if (texture) {
  197.         box.material = texture;
  198.       }
  199.       box.position.x = position[0] + rsx / 2;
  200.       box.position.y = position[1] + rsy / 2;
  201.       box.position.z = position[2] + rsz / 2;
  202.  
  203.       box.rotation.x = toRadians(rotation[0]);
  204.       box.rotation.y = toRadians(rotation[1]);
  205.       box.rotation.z = toRadians(rotation[2]);
  206.  
  207.       box.scaling.setAll(actualInflate);
  208.  
  209.       return box;
  210.     };
  211.  
  212.     const maybeMolang = (v: any, molang: Molang, time: number) => {
  213.       return typeof v === "number" ? v : molang.execute(v, time);
  214.     };
  215.  
  216.     const evalAnimPart = (
  217.       animPart: any,
  218.       molang: Molang,
  219.       time: number
  220.     ): number[] => {
  221.       if (Array.isArray(animPart)) {
  222.         const [rx, ry, rz] = animPart;
  223.         const result = [
  224.           maybeMolang(rx, molang, time),
  225.           maybeMolang(ry, molang, time),
  226.           maybeMolang(rz, molang, time),
  227.         ];
  228.         return result;
  229.       } else if (typeof animPart === "object" && animPart !== null) {
  230.         const keys = Object.keys(animPart)
  231.           .map(Number)
  232.           .sort((a, b) => a - b);
  233.  
  234.         if (keys.length === 0) return [0, 0, 0];
  235.  
  236.         const maxTime = keys[keys.length - 1];
  237.         const currentTime = time % maxTime;
  238.  
  239.         let prevKeyIndex = 0;
  240.         for (let i = 0; i < keys.length; i++) {
  241.           if (keys[i] <= currentTime) {
  242.             prevKeyIndex = i;
  243.           } else {
  244.             break;
  245.           }
  246.         }
  247.  
  248.         const prevKey = keys[prevKeyIndex];
  249.         const nextKey = keys[(prevKeyIndex + 1) % keys.length] || prevKey;
  250.  
  251.         const prevValue = animPart[prevKey];
  252.         const nextValue = animPart[nextKey];
  253.  
  254.         if (!Array.isArray(prevValue) || !Array.isArray(nextValue)) {
  255.           if (Array.isArray(prevValue)) return prevValue;
  256.           if (Array.isArray(nextValue)) return nextValue;
  257.           return [0, 0, 0];
  258.         }
  259.  
  260.         if (prevKey === nextKey || currentTime === prevKey) {
  261.           return [
  262.             maybeMolang(prevValue[0], molang, time),
  263.             maybeMolang(prevValue[1], molang, time),
  264.             maybeMolang(prevValue[2], molang, time),
  265.           ];
  266.         }
  267.  
  268.         let alpha = (currentTime - prevKey) / (nextKey - prevKey);
  269.         if (nextKey < prevKey) {
  270.           alpha = (currentTime - prevKey) / (maxTime - prevKey);
  271.         }
  272.  
  273.         alpha = Math.max(0, Math.min(1, alpha));
  274.  
  275.         return [
  276.           maybeMolang(prevValue[0], molang, time) +
  277.             (maybeMolang(nextValue[0], molang, time) -
  278.               maybeMolang(prevValue[0], molang, time)) *
  279.               alpha,
  280.           maybeMolang(prevValue[1], molang, time) +
  281.             (maybeMolang(nextValue[1], molang, time) -
  282.               maybeMolang(prevValue[1], molang, time)) *
  283.               alpha,
  284.           maybeMolang(prevValue[2], molang, time) +
  285.             (maybeMolang(nextValue[2], molang, time) -
  286.               maybeMolang(prevValue[2], molang, time)) *
  287.               alpha,
  288.         ];
  289.       }
  290.  
  291.       return [0, 0, 0];
  292.     };
  293.  
  294.     function applyAnimation(
  295.       model: ExtendedMesh,
  296.       animName: string,
  297.       molang: Molang,
  298.       boneCubes: Record<string, ExtendedMesh>,
  299.       time: number
  300.     ) {
  301.       if (!model?.userData?.anims) {
  302.         return;
  303.       }
  304.  
  305.       const anims = model.userData.anims;
  306.       const animEntry = Object.entries(anims.animations).find(
  307.         ([k]) => k.split(".").pop() === animName
  308.       ) as [string, any] | undefined;
  309.  
  310.       if (!animEntry?.[1]?.bones) {
  311.         return;
  312.       }
  313.  
  314.       const anim = animEntry[1];
  315.  
  316.       Object.entries(anim.bones).forEach(
  317.         ([boneName, boneAnim]: [string, any]) => {
  318.           const targetBone = boneCubes[boneName];
  319.           if (!targetBone) {
  320.             return;
  321.           }
  322.  
  323.           if (boneAnim.rotation && doRotAnim) {
  324.             const [frx, fry, frz] = evalAnimPart(
  325.               boneAnim.rotation,
  326.               molang,
  327.               time
  328.             );
  329.             const [brx, bry, brz] = targetBone.userData?.baseRot || [0, 0, 0];
  330.  
  331.             // Debug logging for problematic bones
  332.             if (boneName === "head" || boneName === "torso") {
  333.               console.log(`${boneName} rotation:`, {
  334.                 base: [brx, bry, brz],
  335.                 anim: [frx, fry, frz],
  336.                 final: [brx + frx, bry + fry, brz + frz],
  337.               });
  338.             }
  339.  
  340.             targetBone.rotation.x = toRadians(brx + frx);
  341.             targetBone.rotation.y = toRadians(bry + fry);
  342.             targetBone.rotation.z = toRadians(brz + frz);
  343.           }
  344.  
  345.           if (boneAnim.position && doPosAnim) {
  346.             const [frx, fry, frz] = evalAnimPart(
  347.               boneAnim.position,
  348.               molang,
  349.               time
  350.             );
  351.             const [brx, bry, brz] = targetBone.userData?.basePos || [0, 0, 0];
  352.  
  353.             // Debug logging for problematic bones
  354.             if (boneName === "head" || boneName === "torso") {
  355.               console.log(`${boneName} position:`, {
  356.                 base: [brx, bry, brz],
  357.                 anim: [frx, fry, frz],
  358.                 final: [brx + frx, bry + fry, brz + frz],
  359.               });
  360.             }
  361.  
  362.             targetBone.position.x = brx + frx;
  363.             targetBone.position.y = bry + fry;
  364.             targetBone.position.z = brz + frz;
  365.           }
  366.         }
  367.       );
  368.     }
  369.  
  370.     const light = new HemisphericLight("light", new Vector3(1, 3, -1), scene);
  371.  
  372.     // Get the geometry identifier to determine texture name
  373.     const geometryIdentifier =
  374.       geoData["minecraft:geometry"][0]?.description?.identifier || "";
  375.     const geometryName =
  376.       geometryIdentifier.split(".").pop() || pokemon.split("_")[1];
  377.  
  378.     const texture = buildTexture({
  379.       url: `../../3dmons/textures/${pokemon}/${geometryName}.png`.toLowerCase(),
  380.     });
  381.  
  382.     const boneCubes: Record<string, ExtendedMesh> = {};
  383.  
  384.     const buildModel = (geo: any, anims: any, texture: StandardMaterial) => {
  385.       const relevantGeoData = geo["minecraft:geometry"][0];
  386.       const bones = relevantGeoData.bones;
  387.       const textureSize = [
  388.         relevantGeoData.description.texture_width,
  389.         relevantGeoData.description.texture_height,
  390.       ];
  391.  
  392.       console.log("Model info:", {
  393.         totalBones: bones.length,
  394.         textureSize,
  395.         boneNames: bones.map((b: any) => b.name),
  396.       });
  397.  
  398.       bones.forEach(
  399.         ({ name, pivot, parent, rotation = [0, 0, 0], cubes = [] }: any) => {
  400.           const [pX, pY, pZ] = pivot;
  401.           const boneCube = buildCube({
  402.             position: [pX, pY, pZ],
  403.             rotation,
  404.             name,
  405.           });
  406.  
  407.           // Initialize userData
  408.           boneCube.userData = boneCube.userData || {};
  409.           boneCube.userData.state = { pivot, rotation };
  410.  
  411.           const parentBoneCube = boneCubes[parent];
  412.           boneCubes[name] = boneCube;
  413.  
  414.           // Debug bone hierarchy
  415.           console.log(`Bone "${name}":`, {
  416.             parent: parent || "ROOT",
  417.             pivot: [pX, pY, pZ],
  418.             rotation,
  419.             cubesCount: cubes.length,
  420.           });
  421.  
  422.           if (!parentBoneCube) {
  423.             return;
  424.           }
  425.  
  426.           cubes.forEach(
  427.             (
  428.               { origin, size, uv, rotation, inflate = 0 }: any,
  429.               index: number
  430.             ) => {
  431.               const cube = buildCube({
  432.                 size: size.map((e: number) => (e ? e : 0.01)),
  433.                 position: origin,
  434.                 rotation,
  435.                 texture,
  436.                 textureSize,
  437.                 uv,
  438.                 inflate,
  439.                 name: `${name}-${index}`,
  440.               });
  441.               boneCube.addChild(cube);
  442.             }
  443.           );
  444.  
  445.           parentBoneCube.addChild(boneCube);
  446.         }
  447.       );
  448.  
  449.       const rootBoneName = bones.find(({ parent }: any) => !parent).name;
  450.       const rootBone = boneCubes[rootBoneName];
  451.  
  452.       // Initialize userData
  453.       rootBone.userData = rootBone.userData || {};
  454.       rootBone.userData.anims = anims;
  455.       rootBone.userData.boneCubes = boneCubes;
  456.  
  457.       // Store base positions and rotations for each bone
  458.       Object.entries(boneCubes).forEach(([name, bone]) => {
  459.         bone.userData = bone.userData || {};
  460.         bone.userData.basePos = [
  461.           bone.position.x,
  462.           bone.position.y,
  463.           bone.position.z,
  464.         ];
  465.         bone.userData.baseRot = [
  466.           bone.rotation.x,
  467.           bone.rotation.y,
  468.           bone.rotation.z,
  469.         ];
  470.       });
  471.  
  472.       return rootBone;
  473.     };
  474.  
  475.     const model = buildModel(geoData, animData, texture);
  476.     scene.addMesh(model);
  477.     model.position.y = -15;
  478.  
  479.     // Force update bounding info after model is fully built
  480.     model.refreshBoundingInfo();
  481.     scene.registerBeforeRender(() => {}); // Force one frame to ensure everything is calculated
  482.  
  483.     // Calculate optimal camera distance based on model size
  484.     const boundingInfo = model.getBoundingInfo();
  485.     const boundingBox = boundingInfo.boundingBox;
  486.     const modelSize = boundingBox.maximumWorld.subtract(
  487.       boundingBox.minimumWorld
  488.     );
  489.     const maxDimension = Math.max(modelSize.x, modelSize.y, modelSize.z);
  490.  
  491.     // Much more aggressive zoom out calculation
  492.     const baseDistance = maxDimension * 8; // Simple multiplier approach
  493.     const finalDistance = Math.max(baseDistance, 50); // Much higher minimum distance
  494.  
  495.     console.log("🔍 Model sizing debug:", {
  496.       boundingBox: {
  497.         min: boundingBox.minimumWorld.toString(),
  498.         max: boundingBox.maximumWorld.toString(),
  499.         size: modelSize.toString(),
  500.       },
  501.       maxDimension,
  502.       baseDistance,
  503.       finalDistance,
  504.       modelPosition: model.position.toString(),
  505.     });
  506.  
  507.     // Create camera centered on the model's position with calculated distance
  508.     const camera = new ArcRotateCamera(
  509.       "Camera",
  510.       -Math.PI / 2,
  511.       Math.PI / 3,
  512.       finalDistance, // Use our calculated distance without any override
  513.       new Vector3(0, -1, 0), // Target where the model actually is
  514.       scene
  515.     );
  516.  
  517.     camera.attachControl(canvasRef.current, true);
  518.  
  519.     // Animation loop with slower timing
  520.     let time = 0;
  521.     let currentAnimationIndex = 0;
  522.     let animationDuration = 8; // Increased from 5 to 8 seconds per animation
  523.     let availableAnimations: string[] = [];
  524.  
  525.     scene.registerBeforeRender(() => {
  526.       if (model?.userData?.anims) {
  527.         if (availableAnimations.length === 0) {
  528.           availableAnimations = Object.keys(
  529.             model.userData.anims.animations
  530.           ).map((name) => {
  531.             return name.split(".").pop() || name;
  532.           });
  533.           console.log("Available animations:", availableAnimations);
  534.         }
  535.  
  536.         const currentAnimTime =
  537.           time % (animationDuration * availableAnimations.length);
  538.         const newAnimIndex = Math.floor(currentAnimTime / animationDuration);
  539.  
  540.         if (newAnimIndex !== currentAnimationIndex) {
  541.           currentAnimationIndex = newAnimIndex;
  542.           console.log(
  543.             `🎬 Now playing: ${availableAnimations[currentAnimationIndex]}`
  544.           );
  545.         }
  546.  
  547.         const currentAnim = availableAnimations[currentAnimationIndex];
  548.  
  549.         // Apply the animation
  550.         const molang = new Molang();
  551.         applyAnimation(model, currentAnim, molang, boneCubes, time);
  552.  
  553.         // Slower rotation for easier observation
  554.         model.rotation.y += 0.001;
  555.  
  556.         time += 0.000125; // Ultra slow - 1/8 of previous speed (128x slower than original)
  557.       }
  558.     });
  559.  
  560.     engine.runRenderLoop(() => {
  561.       scene.render();
  562.     });
  563.  
  564.     const handleResize = () => {
  565.       engine.resize();
  566.     };
  567.  
  568.     window.addEventListener("resize", handleResize);
  569.  
  570.     return () => {
  571.       window.removeEventListener("resize", handleResize);
  572.       engine.dispose();
  573.       scene.dispose();
  574.     };
  575.   }, [pokemonName, geoData, animData]);
  576.  
  577.   return (
  578.     <canvas
  579.       ref={canvasRef}
  580.       style={{
  581.         width: `${size.width}px`,
  582.         height: `${size.height}px`,
  583.         display: "block",
  584.       }}
  585.     />
  586.   );
  587. }
  588.  
Add Comment
Please, Sign In to add comment