[AI] Java to XML/VRML to renderer.

John Carlson yottzumm at gmail.com
Tue Feb 10 07:16:44 PST 2026


Cleaned up App.jsx.  Looks like X3D JSON, Three, and React play well
together!  Even animation!  Maybe we've got the beginnings of a real JSON
renderer!

No TypeScript yet!

Woohoo!

 Screen Recording 2026-02-10 090951.mp4
<https://drive.google.com/open?id=19T9FjsJf97YeQk2Sg8EzgQmrhe1Hw783>

On Tue, Feb 10, 2026 at 5:28 AM John Carlson <yottzumm at gmail.com> wrote:

> https://claude.ai/share/636ed092-ace5-4331-9222-4d0d85e80ada
>
>
> I asked claude.ai to create a X3D JSON WebGPU renderer using THREE.
> Then I asked it to create a scene that contains all X3D nodes and
> statements, then regenerate the renderer.
>
>
> Untested as I am in bed with a cold.  Maybe I can add this renderer to my
> bake-off.
>
> John
>
> ---------- Forwarded message ---------
> From: John Carlson <yottzumm at gmail.com>
> Date: Tue, Feb 10, 2026 at 4:50 AM
> Subject: Java to XML/VRML to renderer.
> To: X3D Ecosystem public discussion <x3d-ecosystem at web3d.org>, Don
> Brutzman <don.brutzman at gmail.com>, Holger Seelig <holger.seelig at yahoo.de>,
> Andreas Plesch <andreasplesch at gmail.com>
>
>
> So I get that one can go from Java to XML/VRML to render.  Even I go from
> JSON to DOM to renderer with JavaScript.
>
> AFAIK, Xj3D has a scenegraph that it renders.  I realize it’s based on
> VRML.   That’s cool.
>
> In x3danari, I import a X3DPSAIL scenegraph and render it with Pynari and
> ANARI.
>
> Is there anything that’s preventing us from doing that in Java and
> JavaScript scenegraphs?  I don’t know the rendering APIs for those.  Please
> answer for Xj3D, X_ITE, X3DOM and Sunrize.
>
> I have started on a visitor pattern that might help to render X3DJSAIL.
>
>
> https://github.com/coderextreme/X3DJSONLD/tree/master/src/main/java/net/coderextreme/visitor
>
> Also of interest is my recursive node remover.
>
>
> https://github.com/coderextreme/X3DJSONLD/blob/master/src/main/java/net/coderextreme/Remove.java
>
>
> Also my remove generator and visitor generator:
>
>
> https://github.com/coderextreme/X3DJSONLD/blob/master/src/main/python/Visitor.py
>
>
> https://github.com/coderextreme/X3DJSONLD/blob/master/src/main/python/removeGenerator.py
>
> Anyway, rendering is probably beyond me without AI assistance.  I would
> like to write a JSON-based renderer, but perhaps that’s not in the cards.
> Maybe I can do something with pynari?
>
> Maybe creating Blender JSON might be in the future.
>
> I don’t even have a clear picture of the OpenGL/WebGL/Vulkan/WebGPU
> drivers.   Can someone recommend a class?  I know stuff like creating
> images.
>
> John
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://web3d.org/pipermail/ai_web3d.org/attachments/20260210/b93991b5/attachment-0001.html>
-------------- next part --------------
import React, { useEffect, useRef, useState } from 'react';
import './App.css';

import * as THREE from 'three';
import { WebGPURenderer } from 'three/webgpu';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import WebGPU from 'three/addons/capabilities/WebGPU.js';

export default function App() {
  const containerRef = useRef(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);
  const [jsonInput, setJsonInput] = useState('');

  // --- RESTORED COMPREHENSIVE SCENE ---
  const defaultScene = {
    "X3D": {
      "version": "3.3",
      "profile": "Immersive",
      "Scene": {
        "children": [
          {
            "Background": {
              "DEF": "MainBackground",
              "skyColor": [[0.0, 0.2, 0.7], [0.0, 0.5, 1.0], [1.0, 1.0, 1.0]],
              "skyAngle": [1.309, 1.571],
              "groundColor": [[0.1, 0.1, 0.0], [0.4, 0.25, 0.2], [0.6, 0.6, 0.6]],
              "groundAngle": [1.309, 1.571]
            }
          },
          { "DirectionalLight": { "DEF": "SunLight", "direction": [0, -1, -1], "color": [1, 1, 0.9], "intensity": 0.8 } },
          { "PointLight": { "DEF": "RedLight", "location": [5, 3, 5], "color": [1, 0.2, 0.2], "intensity": 0.6 } },
          { "SpotLight": { "DEF": "SpotLight1", "location": [-5, 5, 5], "direction": [1, -1, -1], "color": [0.2, 0.2, 1], "intensity": 0.7, "angle": 0.78 } },

          // Animated Box
          {
            "Transform": {
              "DEF": "BoxTransform",
              "translation": [-8, 1, 0],
              "rotation": [0, 1, 0, 0.785],
              "children": [
                {
                  "Shape": {
                    "geometry": { "Box": { "size": [2, 2, 2] } },
                    "appearance": { "Appearance": { "material": { "Material": { "DEF": "RedMaterial", "diffuseColor": [1, 0.2, 0.2], "specularColor": [1, 1, 1], "shininess": 0.8 } } } }
                  }
                }
              ]
            }
          },
          // Transparent Sphere
          {
            "Transform": {
              "translation": [-4, 1.5, 0],
              "children": [{ "Shape": { "geometry": { "Sphere": { "radius": 1.5 } }, "appearance": { "Appearance": { "material": { "Material": { "diffuseColor": [0.2, 1, 0.2], "transparency": 0.3 } } } } } }]
            }
          },
          // Cone
          { "Transform": { "translation": [0, 1, 0], "children": [{ "Shape": { "geometry": { "Cone": { "height": 3, "bottomRadius": 1.5 } }, "appearance": { "Appearance": { "material": { "Material": { "diffuseColor": [0.2, 0.2, 1] } } } } } }] } },
          // Cylinder
          { "Transform": { "translation": [4, 1, 0], "rotation": [1, 0, 0, 1.57], "children": [{ "Shape": { "geometry": { "Cylinder": { "height": 3, "radius": 1 } }, "appearance": { "Appearance": { "material": { "Material": { "diffuseColor": [1, 1, 0.2] } } } } } }] } },
          // Torus
          { "Transform": { "translation": [8, 1, 0], "children": [{ "Shape": { "geometry": { "Torus": { "outerRadius": 1.5, "innerRadius": 0.5 } }, "appearance": { "Appearance": { "material": { "Material": { "diffuseColor": [1, 0.5, 0.2] } } } } } }] } },

          // IndexedFaceSet (Pyramid)
          {
            "Transform": {
              "translation": [0, 0, -5],
              "children": [{
                "Shape": {
                  "geometry": {
                    "IndexedFaceSet": {
                      "coord": { "Coordinate": { "point": [[0, 2, 0], [-1, 0, 1], [1, 0, 1], [1, 0, -1], [-1, 0, -1]] } },
                      "coordIndex": [0, 1, 2, -1, 0, 2, 3, -1, 0, 3, 4, -1, 0, 4, 1, -1, 1, 4, 3, 2, -1],
                      "color": { "Color": { "color": [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 0], [0.5, 0.5, 0.5]] } }
                    }
                  },
                  "appearance": { "Appearance": { "material": { "Material": { "diffuseColor": [0.8, 0.8, 0.8] } } } }
                }
              }]
            }
          },

          // IndexedLineSet
          {
            "Transform": {
              "translation": [0, 3, 5],
              "children": [{
                "Shape": {
                  "geometry": {
                    "IndexedLineSet": {
                      "coord": { "Coordinate": { "point": [[-2, 0, 0], [2, 0, 0], [0, 0, -2], [0, 0, 2], [0, -2, 0], [0, 2, 0]] } },
                      "coordIndex": [0, 1, -1, 2, 3, -1, 4, 5, -1],
                      "color": { "Color": { "color": [[1, 0, 0], [1, 0, 0], [0, 1, 0], [0, 1, 0], [0, 0, 1], [0, 0, 1]] } }
                    }
                  }
                }
              }]
            }
          },

          // Billboard Text
          {
            "Billboard": {
              "DEF": "TextBillboard",
              "children": [{
                "Transform": {
                  "translation": [0, 6, 0],
                  "children": [{
                    "Shape": {
                      "geometry": { "Text": { "string": ["X3D WebGPU"], "fontStyle": { "FontStyle": { "size": 0.8 } } } },
                      "appearance": { "Appearance": { "material": { "Material": { "diffuseColor": [1, 1, 0], "emissiveColor": [0.5, 0.5, 0] } } } }
                    }
                  }]
                }
              }]
            }
          },

          // Animation Logic
          { "TimeSensor": { "DEF": "Clock", "cycleInterval": 5, "loop": true, "enabled": true } },
          { "PositionInterpolator": { "DEF": "BoxMover", "key": [0, 0.25, 0.5, 0.75, 1], "keyValue": [[-8, 1, 0], [-8, 3, 0], [-8, 1, 0], [-8, -1, 0], [-8, 1, 0]] } },
          { "OrientationInterpolator": { "DEF": "BoxRotator", "key": [0, 0.5, 1], "keyValue": [[0, 1, 0, 0], [0, 1, 0, 3.14159], [0, 1, 0, 6.28318]] } },
          { "ColorInterpolator": { "DEF": "ColorChanger", "key": [0, 0.33, 0.66, 1], "keyValue": [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 0]] } },

          // Routes
          { "ROUTE": { "fromNode": "Clock", "fromField": "fraction_changed", "toNode": "BoxMover", "toField": "set_fraction" } },
          { "ROUTE": { "fromNode": "BoxMover", "fromField": "value_changed", "toNode": "BoxTransform", "toField": "set_translation" } },
          { "ROUTE": { "fromNode": "Clock", "fromField": "fraction_changed", "toNode": "BoxRotator", "toField": "set_fraction" } },
          { "ROUTE": { "fromNode": "BoxRotator", "fromField": "value_changed", "toNode": "BoxTransform", "toField": "set_rotation" } },
          { "ROUTE": { "fromNode": "Clock", "fromField": "fraction_changed", "toNode": "ColorChanger", "toField": "set_fraction" } },
          { "ROUTE": { "fromNode": "ColorChanger", "fromField": "value_changed", "toNode": "RedMaterial", "toField": "set_diffuseColor" } }
        ]
      }
    }
  };

  useEffect(() => {
    setJsonInput(JSON.stringify(defaultScene, null, 2));
  }, []);

  useEffect(() => {
    if (!jsonInput) return;

    if (WebGPU && !WebGPU.isAvailable()) {
      setError('WebGPU is not supported in this browser. Please use Chrome/Edge 113+');
      setLoading(false);
      return;
    }

    let renderer, scene, camera, controls, clock;
    let resizeObserver;

    // Registries
    const defRegistry = new Map();
    const routes = [];
    const timeSensors = [];
    const routeUpdates = [];
    const billboards = [];

    const init = async () => {
      try {
        setLoading(true);
        const container = containerRef.current;
        if (!container) return;

        // 1. Setup Three.js
        scene = new THREE.Scene();
        scene.background = new THREE.Color(0x1a1a2e);
        clock = new THREE.Clock();

        const width = container.clientWidth;
        const height = container.clientHeight;

        camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
        camera.position.set(0, 5, 20);

        // 2. Setup WebGPU Renderer
        renderer = new WebGPURenderer({ antialias: true });
        await renderer.init();
        renderer.setSize(width, height);
        renderer.setPixelRatio(window.devicePixelRatio);

        container.innerHTML = '';
        container.appendChild(renderer.domElement);

        controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;

        const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
        scene.add(ambientLight);

        // 3. Parse X3D
        const x3dData = JSON.parse(jsonInput);
        if (x3dData.X3D?.Scene) {
          await parseNode(x3dData.X3D.Scene, scene);
        }

        // 4. Process Routes
        processRoutes();

        // 5. Animation Loop
        const animate = () => {
          const elapsedTime = clock.getElapsedTime();

          // A. Update TimeSensors
          timeSensors.forEach(sensor => {
            if (sensor.enabled && sensor.loop) {
              const fraction = (elapsedTime % sensor.cycleInterval) / sensor.cycleInterval;
              sensor.fraction_changed = fraction;
            }
          });

          // B. Execute Route Transfers
          routeUpdates.forEach(fn => fn());

          // C. Update Billboards
          billboards.forEach(b => b.lookAt(camera.position));

          // D. Render
          controls.update();
          renderer.render(scene, camera);
        };

        renderer.setAnimationLoop(animate);

        // 6. Resize Handling
        resizeObserver = new ResizeObserver((entries) => {
          for (const entry of entries) {
            const { width, height } = entry.contentRect;
            if (width > 0 && height > 0) {
              camera.aspect = width / height;
              camera.updateProjectionMatrix();
              renderer.setSize(width, height);
            }
          }
        });
        resizeObserver.observe(container);

        setLoading(false);
        setError(null);

      } catch (err) {
        console.error(err);
        setError(err.message);
        setLoading(false);
      }
    };

    // --- PARSER LOGIC ---

    const parseNode = async (node, parent) => {
      if (node.children) {
        for (const child of node.children) {
          await parseChild(child, parent);
        }
      }
    };

    const parseChild = async (child, parent) => {
      const defName = child.DEF || Object.values(child)[0]?.DEF;
      let object = null;

      if (child.Transform) {
        object = createTransform(child.Transform);
        await parseNode(child.Transform, object);
      } else if (child.Group) {
        object = new THREE.Group();
        await parseNode(child.Group, object);
      } else if (child.Billboard) {
        object = new THREE.Group();
        billboards.push(object);
        await parseNode(child.Billboard, object);
      } else if (child.Shape) {
        object = await createShape(child.Shape);

      // Lights
      } else if (child.DirectionalLight) {
        const d = child.DirectionalLight;
        object = new THREE.DirectionalLight(new THREE.Color(...(d.color || [1,1,1])), d.intensity || 1);
        if (d.direction) object.position.copy(new THREE.Vector3(...d.direction).negate().multiplyScalar(10));
      } else if (child.PointLight) {
        const p = child.PointLight;
        object = new THREE.PointLight(new THREE.Color(...(p.color || [1,1,1])), p.intensity || 1);
        if (p.location) object.position.set(...p.location);
      } else if (child.SpotLight) {
        const s = child.SpotLight;
        object = new THREE.SpotLight(new THREE.Color(...(s.color || [1,1,1])), s.intensity || 1);
        if (s.location) object.position.set(...s.location);
        if (s.direction) {
          const target = new THREE.Object3D();
          target.position.copy(object.position).add(new THREE.Vector3(...s.direction));
          object.target = target;
          scene.add(target);
        }
        if (s.angle) object.angle = s.angle;

      // Environment
      } else if (child.Background) {
        if (child.Background.skyColor) scene.background = new THREE.Color(...child.Background.skyColor[0]);
        if (defName) defRegistry.set(defName, scene.background);

      // Animation
      } else if (child.TimeSensor) {
        const ts = { ...child.TimeSensor, fraction_changed: 0, enabled: true, loop: true };
        timeSensors.push(ts);
        if (defName) defRegistry.set(defName, ts);
      } else if (child.PositionInterpolator) {
        const pi = createInterpolator(child.PositionInterpolator, 3);
        if (defName) defRegistry.set(defName, pi);
      } else if (child.OrientationInterpolator) {
        const oi = createInterpolator(child.OrientationInterpolator, 4);
        if (defName) defRegistry.set(defName, oi);
      } else if (child.ColorInterpolator) {
        const ci = createInterpolator(child.ColorInterpolator, 3);
        if (defName) defRegistry.set(defName, ci);
      } else if (child.ROUTE) {
        routes.push(child.ROUTE);
      }

      if (object) {
        parent.add(object);
        if (defName) defRegistry.set(defName, object);
      }
    };

    // --- FACTORIES ---

    const createTransform = (data) => {
      const group = new THREE.Group();
      if (data.translation) group.position.set(...data.translation);
      if (data.rotation) {
        const [x, y, z, angle] = data.rotation;
        group.setRotationFromAxisAngle(new THREE.Vector3(x,y,z).normalize(), angle);
      }
      if (data.scale) group.scale.set(...data.scale);
      return group;
    };

    const createShape = async (data) => {
      let geo = await createGeometry(data.geometry);
      let mat = createMaterial(data.appearance);

      if (geo && mat) {
        if (geo.isLineGeometry) return new THREE.LineSegments(geo, mat);
        if (geo.isPointGeometry) return new THREE.Points(geo, mat);
        return new THREE.Mesh(geo, mat);
      }
      return null;
    };

    const createGeometry = async (data) => {
      if (!data) return null;
      if (data.Box) return new THREE.BoxGeometry(...(data.Box.size || [1,1,1]));
      if (data.Sphere) return new THREE.SphereGeometry(data.Sphere.radius || 1, 32, 32);
      if (data.Cone) return new THREE.ConeGeometry(data.Cone.bottomRadius || 1, data.Cone.height || 2, 32);
      if (data.Cylinder) return new THREE.CylinderGeometry(data.Cylinder.radius || 1, data.Cylinder.radius || 1, data.Cylinder.height || 2, 32);
      if (data.Torus) return new THREE.TorusGeometry(data.Torus.outerRadius || 1, data.Torus.innerRadius || 0.4, 16, 32);
      if (data.IndexedFaceSet) return createIndexedFaceSet(data.IndexedFaceSet);
      if (data.IndexedLineSet) return createIndexedLineSet(data.IndexedLineSet);
      if (data.PointSet) return createPointSet(data.PointSet);
      if (data.Text) return createTextGeometry(data.Text);
      if (data.Extrusion) return createExtrusion(data.Extrusion);
      if (data.ElevationGrid) return createElevationGrid(data.ElevationGrid);
      return null;
    };

    const createMaterial = (appearance) => {
      if (!appearance?.Appearance?.material?.Material) return new THREE.MeshStandardMaterial({ color: 0x888888 });
      const m = appearance.Appearance.material.Material;
      const params = {
        color: m.diffuseColor ? new THREE.Color(...m.diffuseColor) : 0xffffff,
        roughness: 1 - (m.shininess || 0.2),
        metalness: 0.1,
        vertexColors: false
      };

      if (m.emissiveColor) params.emissive = new THREE.Color(...m.emissiveColor);
      if (m.transparency) {
        params.transparent = true;
        params.opacity = 1 - m.transparency;
      }

      const mat = new THREE.MeshStandardMaterial(params);
      if (m.DEF) defRegistry.set(m.DEF, mat);
      return mat;
    };

    // --- GEOMETRY HELPERS ---

    const createIndexedFaceSet = (ifs) => {
      const geo = new THREE.BufferGeometry();
      const points = ifs.coord?.Coordinate?.point || [];
      const indices = ifs.coordIndex || [];
      const colors = ifs.color?.Color?.color || [];
      const vertices = [];
      const vertexColors = [];

      let currentFace = [];
      indices.forEach(idx => {
        if (idx === -1) {
          // Triangulate fan
          for (let i = 1; i < currentFace.length - 1; i++) {
            const a = currentFace[0];
            const b = currentFace[i];
            const c = currentFace[i+1];
            vertices.push(...points[a], ...points[b], ...points[c]);
            if (colors.length > 0) {
              vertexColors.push(...(colors[a] || [1,1,1]), ...(colors[b] || [1,1,1]), ...(colors[c] || [1,1,1]));
            }
          }
          currentFace = [];
        } else currentFace.push(idx);
      });

      geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
      if (vertexColors.length > 0) geo.setAttribute('color', new THREE.Float32BufferAttribute(vertexColors, 3));
      geo.computeVertexNormals();
      return geo;
    };

    const createIndexedLineSet = (ils) => {
      const geo = new THREE.BufferGeometry();
      const points = ils.coord?.Coordinate?.point || [];
      const indices = ils.coordIndex || [];
      const colors = ils.color?.Color?.color || [];
      const vertices = [];
      const vertexColors = [];

      let currentLine = [];
      indices.forEach(idx => {
        if (idx === -1) currentLine = [];
        else {
          currentLine.push(idx);
          if (currentLine.length === 2) {
            vertices.push(...points[currentLine[0]], ...points[currentLine[1]]);
            if (colors.length > 0) {
              vertexColors.push(...(colors[currentLine[0]] || [1,1,1]), ...(colors[currentLine[1]] || [1,1,1]));
            }
            currentLine.shift();
          }
        }
      });

      geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
      if (vertexColors.length > 0) geo.setAttribute('color', new THREE.Float32BufferAttribute(vertexColors, 3));
      geo.isLineGeometry = true;
      return geo;
    };

    const createPointSet = (ps) => {
      const geo = new THREE.BufferGeometry();
      const points = ps.coord?.Coordinate?.point || [];
      const colors = ps.color?.Color?.color || [];
      geo.setAttribute('position', new THREE.Float32BufferAttribute(points.flat(), 3));
      if (colors.length > 0) geo.setAttribute('color', new THREE.Float32BufferAttribute(colors.flat(), 3));
      geo.isPointGeometry = true;
      return geo;
    };

    const createTextGeometry = (textData) => {
      // Placeholder for TextGeometry to avoid font loading complexity
      // Returns a box representing the text bounds
      const strings = textData.string || ["Text"];
      const size = textData.fontStyle?.FontStyle?.size || 1;
      return new THREE.BoxGeometry(strings[0].length * size * 0.5, size, 0.1);
    };

    const createExtrusion = (ext) => {
      const crossSection = ext.crossSection || [[1, 1], [1, -1], [-1, -1], [-1, 1], [1, 1]];
      const spine = ext.spine || [[0, 0, 0], [0, 1, 0]];
      const shape = new THREE.Shape();
      crossSection.forEach((p, i) => {
        if (i === 0) shape.moveTo(p[0], p[1]);
        else shape.lineTo(p[0], p[1]);
      });
      return new THREE.ExtrudeGeometry(shape, {
        steps: spine.length,
        depth: 1, // Simplified depth mapping from spine
        bevelEnabled: false
      });
    };

    const createElevationGrid = (eg) => {
      const xDim = eg.xDimension || 2;
      const zDim = eg.zDimension || 2;
      const xSpacing = eg.xSpacing || 1;
      const zSpacing = eg.zSpacing || 1;
      const heights = eg.height || [];
      const geo = new THREE.BufferGeometry();
      const vertices = [];

      // Create quad faces
      for (let z = 0; z < zDim - 1; z++) {
        for (let x = 0; x < xDim - 1; x++) {
          const i1 = z * xDim + x;
          const i2 = z * xDim + (x + 1);
          const i3 = (z + 1) * xDim + (x + 1);
          const i4 = (z + 1) * xDim + x;

          const h1 = heights[i1] || 0;
          const h2 = heights[i2] || 0;
          const h3 = heights[i3] || 0;
          const h4 = heights[i4] || 0;

          // Triangle 1
          vertices.push(x*xSpacing, h1, z*zSpacing);
          vertices.push((x+1)*xSpacing, h2, z*zSpacing);
          vertices.push((x+1)*xSpacing, h3, (z+1)*zSpacing);

          // Triangle 2
          vertices.push(x*xSpacing, h1, z*zSpacing);
          vertices.push((x+1)*xSpacing, h3, (z+1)*zSpacing);
          vertices.push(x*xSpacing, h4, (z+1)*zSpacing);
        }
      }
      geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
      geo.computeVertexNormals();
      return geo;
    };

    // --- INTERPOLATOR LOGIC ---
    const createInterpolator = (data, dims) => ({
      key: data.key,
      keyValue: data.keyValue,
      value_changed: null,
      set_fraction: function(f) {
        for(let i=0; i<this.key.length-1; i++) {
          if (f >= this.key[i] && f <= this.key[i+1]) {
            const t = (f - this.key[i]) / (this.key[i+1] - this.key[i]);
            const v1 = this.keyValue[i];
            const v2 = this.keyValue[i+1];

            if (dims === 3) {
              this.value_changed = [
                v1[0] + t * (v2[0] - v1[0]),
                v1[1] + t * (v2[1] - v1[1]),
                v1[2] + t * (v2[2] - v1[2])
              ];
            } else if (dims === 4) {
              this.value_changed = [
                v1[0], v1[1], v1[2],
                v1[3] + t * (v2[3] - v1[3])
              ];
            }
            return;
          }
        }
        this.value_changed = this.keyValue[0];
      }
    });

    // --- ROUTE PROCESSING ---
    const processRoutes = () => {
      routes.forEach(r => {
        const from = defRegistry.get(r.fromNode);
        const to = defRegistry.get(r.toNode);

        if (from && to) {
          routeUpdates.push(() => {
            if (from.fraction_changed !== undefined && to.set_fraction) {
              to.set_fraction(from.fraction_changed);
            }
            if (from.value_changed) {
              if (r.toField === 'set_translation' && to.position) to.position.set(...from.value_changed);
              else if (r.toField === 'set_rotation' && to.setRotationFromAxisAngle) {
                const [x,y,z,a] = from.value_changed;
                to.setRotationFromAxisAngle(new THREE.Vector3(x,y,z).normalize(), a);
              }
              else if (r.toField === 'set_scale' && to.scale) to.scale.set(...from.value_changed);
              else if (r.toField === 'set_diffuseColor' && to.color) to.color.setRGB(...from.value_changed);
            }
          });
        }
      });
    };

    init();

    return () => {
      if (resizeObserver) resizeObserver.disconnect();
      if (renderer) {
        renderer.setAnimationLoop(null);
        try { renderer.dispose(); } catch(e){}
      }
    };
  }, [jsonInput]);

  return (
    <div className="app-container">
      <div className="header">
        <h1>X3D WebGPU Renderer</h1>
      </div>

      {error && <div className="error-bar">{error}</div>}

      <div className="main-content">
        <div className="sidebar">
          <textarea
            className="json-input"
            value={jsonInput}
            onChange={(e) => setJsonInput(e.target.value)}
            spellCheck={false}
          />
          <button className="btn" onClick={() => {
             const temp = jsonInput; setJsonInput(''); setTimeout(() => setJsonInput(temp), 10);
          }}>
            Reload Scene
          </button>
        </div>

        <div className="canvas-container">
          <div ref={containerRef} className="canvas-wrapper" />
          <div className="status">WebGPU Active</div>
        </div>
      </div>
    </div>
  );
}


More information about the AI mailing list