How to create a multidimensional array of CSS3DObjects according to the shape of an arbitrary tensor?

[Edit]
I realized that my question here is probably more relevant to Javascript. So, please bear with me since it’s still related to implementation of three.js.

[what I have]
I have a multidimensional array, say an array “A” of the shape of (2x3x2), which is extracted from a tensor created by TensorFlow.js, as shown below.

Tensor
    [[[-0.2881464, -0.4352563],
      [-0.7614531, -0.4675721],
      [-1.5179331, -0.0160513]],

     [[1.3503722 , 0.0018445 ],
      [0.5926082 , -0.7657703],
      [-0.4018943, 0.0711233 ]]]

I also created a Card class which defines a CSS3DObject “card”, each having a child div to store a value.

[what I want to do]
I want to create a new multidimensional array of card objects in the same shape of array A, and I want to assign the values in array A to the corresponding card objects in this new array of cards.

[what I’ve done so far]
I manage to get a “flattened” array from the tensor by using A.dataSync() meathod of TensorFlow.js to convert a tensor to a 1-dimensional array.

I can create a 1-dimensional array Cards to store the card objects and assign value to each of them from the “flattened” array of A.

I can arrange this 1D array of cards in a row, as shown below.

Screen Shot 2021-10-09 at 23.05.22 PM

[what I haven’t figured out]
I don’t know how to create a multi-dimentional array in the same shape of another multi-dimentional array, assuming the source array may arbitrary shape.

And, I also don’t know how to “reshape” the 1-dimenstional array of cards to a multi-dimentional array in the same shape of the source tensor, assuming it may have arbitrary shape.

Hope I explained my question clearly, and looking forward to hearing your advice. The example code is shown below.


[main.js]

import * as THREE from "three";
import {
    CSS3DRenderer,
    CSS3DObject,
} from "three/examples/jsm/renderers/CSS3DRenderer.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import * as tf from "@tensorflow/tfjs";
import Card from "./Card.js";

let camera, scene, rendererCSS3D, rendererWebGL;
let controlsCSS;

init();
animate();

function init() {
    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(
        50,
        window.innerWidth / window.innerHeight,
        0.1,
        100000
    );
    camera.position.set(500, 1000, 2500);

    // create multidimensional array with shape of (2x3x2)
    const A = tf.randomNormal([2, 3, 2]);

    // create cards
    const Cards = [];

    for (let i = 0; i < a.size; i += 1) {
        const card = new Card(i, "card " + String(i));

        card.value = A.dataSync()[i];

        card.createBaseDiv();
        card.createNumDiv();
        card.createCSS3DObj();
        card.addToScene(scene);
        card.setCSSObjPosition(i * 100, 0, 0);
        Cards.push(card);
    }

    rendererCSS3D = new CSS3DRenderer();
    rendererCSS3D.setSize(window.innerWidth, window.innerHeight);
    rendererCSS3D.domElement.style.position = "absolute";
    rendererCSS3D.domElement.style.top = "0px";
    document.getElementById("container").appendChild(rendererCSS3D.domElement);

    rendererWebGL = new THREE.WebGLRenderer({ antialias: true });
    rendererWebGL.setPixelRatio(window.devicePixelRatio);
    rendererWebGL.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(rendererWebGL.domElement);

    const controlsCSS = new OrbitControls(camera, rendererCSS3D.domElement);

    window.addEventListener("resize", onWindowResize, false);
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    rendererCSS3D.setSize(window.innerWidth, window.innerHeight);
    rendererWebGL.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
    requestAnimationFrame(animate);
    render();
    stats.update();
}

function render() {
    rendererCSS3D.render(scene, camera);
    rendererWebGL.render(scene, camera);
}

[Card.js]

import * as THREE from "three";
import { Vector3 } from "three";
import {
    CSS3DRenderer,
    CSS3DObject,
} from "three/examples/jsm/renderers/CSS3DRenderer.js";

export default class Card {
    // [+] constructor
    constructor(_idx, _name, _value) {
        this.idx = _idx;
        this.name = _name;
        this.value = _value;
    }

    // [+] create a base div
    createBaseDiv() {
        this.baseDiv = document.createElement("div");
        this.baseDiv.className = "cardBase";
    }

    // [+] create a child div for number
    createNumDiv() {
        this.numDiv = document.createElement("div");
        this.numDiv.className = "cardNum";
        this.numDiv.textContent = this.value;
        this.baseDiv.appendChild(this.numDiv);
    }

    // [+] create CSS3DObject
    createCSS3DObj() {
        this.css3DObj = new CSS3DObject(this.baseDiv);
    }

    // [+] set position of CSS3DObject
    setPosition(x, y, z) {
        this.css3DObj.position.set(x, y, z);
    }

    // [+] add card css3dobj to scene
    addToScene(scene) {
        scene.add(this.css3DObj);
    }

    // [+] set base div width
    setWidth(width) {
        this.baseDiv.style.width = String(width) + "px";
    }

    // [+] set base div height
    setHeight(height) {
        this.baseDiv.style.height = String(height) + "px";
    }
}

Do you want the cards to be positioned and grouped in a plane within the 3d environment, in a way that the shape of the tensor is evident from this?

Yes, @awonnink , that’s the ultimate goal here.

For example, from the index of a card in a three-dimensional array Cards, I should be able to infer its position corresponding to that in a 3D environment. E.g. for card with the index [1, 2, 1] I should know it should be in the 2nd position along x axis, and the 3rd along y axis, and the 2nd along z axis, and so on.

Is this perhaps what you’re looking for?:

1 Like

thanks, @awonnink

A nested for loop definitely works in this case when we know its “depth” and the number of items to loop through in each layer.

However, if the length and the numbers in the “shape” array changes, say shape=[3, 5, 4, 2], the code of the nested for loop needs to be rewritten.

What I’m thinking about is a “dynamic nested for loop” whose number of loops and the number of items to go through in each loop can vary, depending on inputs of these two quantity.

I did search around and I can only find the following discussion on stackoverflow what is related to what I intent to do. However, the solution as suggested is for the task at hand and it is not generally applicable.

Dynamic number of nested loops (in Javascript)

I think you can use recursion for this. But is not clear for me how you want to present the cards for dimension 4 and higher.