Search code examples
reactjsthree.jstexturesarraybuffer

How to Update THREE.DataTexture in react from arraybuffer


I need to render a videoStream coming from a websocket as arraybuffer. I can generate the texture and material but it will not update when a new chunk has arrived.

import React, {Component} from 'react';
import * as THREE from 'three';
import DragControls from 'three-dragcontrols';
import {MeshBasicMaterial} from "three";

const OrbitControls = require('three-orbitcontrols');

class Streamer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      videoData: null,
      resolution: {
        resolutionX: 320,
        resolutionY: 240,
      },
    };

    this.start = this.start.bind(this);
    this.animate = this.animate.bind(this);
  };

check if state changed

  static getDerivedStateFromProps(props, state) {
    if (props.videoData !== state.videoData) {
      return {
        videoData: new Uint8Array(props.videoData),
      };
    }
    return null;
  }

  async componentDidMount() {
    const width = 1024;
    const height = 768;
    let scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(
      90,
      width / height,
      0.1,
      1000
    );

    const renderer = new THREE.WebGLRenderer({antialias: false});
    let ambientLight = new THREE.AmbientLight(0xffffff, 0.7);

    let controls = new OrbitControls(camera, renderer.domElement);
    controls.enableRotate = false;

building asyncronus the mesh and wait for texture generation

    let plane = this.buildTexture(this.state.videoData).then((texture) ={

map texture to material

      return new MeshBasicMaterial({color: 0xFFFFFF, map: texture});
    }).then((material) => {
      let geometry = new THREE.PlaneBufferGeometry(320, 240);
      return new THREE.Mesh(geometry, material);
    });

    scene.add(await plane, ambientLight);

    camera.position.z = 150;
    renderer.setSize(width, height);
    renderer.setPixelRatio(window.devicePixelRatio);

    this.scene = scene;
    this.camera = camera;
    this.renderer = renderer;

    this.mount.appendChild(this.renderer.domElement);
    // window.addEventListener('resize', () => {
    //   camera.aspect = window.innerWidth / window.innerHeight;
    //   camera.updateProjectionMatrix();
    //   renderer.setSize(window.innerWidth, window.innerHeight);
    // }, false);
    this.start();
  }

  async buildTexture(data) {
    let texture = new THREE.DataTexture(data, 320, 240, 
  THREE.LuminanceAlphaFormat);

thought that this is for changing texture

    texture.needsUpdate = true;
    return texture;
  }

  start = () => {
    if (!this.frameId) {
      this.frameId = requestAnimationFrame(this.animate);
    }
  };

  animate() {
    this.renderScene();
    this.frameId = window.requestAnimationFrame(this.animate);
  };

  renderScene = () => {
    this.renderer.render(this.scene, this.camera);
  };

  render() {
    return (
      <div ref={(mount) => {
        this.mount = mount;
      }}
           style={{
             marginTop: '10%',
           }}>
      </div>
    );
  }
}

export default (Streamer);

Solution

  • i figured it out, so if someone is interested in this..

    import React, { Component } from 'react';
    
    import * as THREE from 'three';
    import DragControls from 'three-dragcontrols';
    import { MeshBasicMaterial } from "three";
    
    const OrbitControls = require('three-orbitcontrols');
    
    class Streamer extends Component {
      constructor(props) {
        super(props);
        this.state = {
          videoData: null,
          resolution: {
            resolutionX: 100,
            resolutionY: 100,
          },
        };
    
        this.start = this.start.bind(this);
        this.animate = this.animate.bind(this);
      };
    
    
      static getDerivedStateFromProps(props, state) {
        if (props.videoData !== state.videoData) {
          return {
            videoData: new Uint8Array(props.videoData),
          };
        }
        return null;
      }
    
      async componentDidMount() {
        const width = window.innerWidth;
        const height = window.innerHeight;
        let scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(
          90,
          width / height,
          0.1,
          1000
        );
    
        const renderer = new THREE.WebGLRenderer({ antialias: false });
        let ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
    
        this.renderer = renderer;
        this.scene = scene;
        this.camera = camera;
    
        let controls = new OrbitControls(this.camera, this.renderer.domElement);
        controls.enableRotate = false;
    
        this.texture = await this.buildTexture(this.state.videoData);    
        this.material = new MeshBasicMaterial({ color: 0xFFFFFF, map: this.texture });
        this.geometry = new THREE.PlaneBufferGeometry(800, 600);
        this.mesh = new THREE.Mesh(this.geometry, this.material);
    
        scene.add(this.mesh, ambientLight);
        new DragControls([this.mesh], this.camera, this.renderer.domElement);
    
        camera.position.z = 150;
        renderer.setSize(width, height);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.setClearColor('#FFFFFF');
    
        this.mount.appendChild(this.renderer.domElement);
        this.start();
      }
    
      async buildTexture(data) {
        let texture = new THREE.DataTexture(data, 800, 600, THREE.LuminanceFormat);
        texture.needsUpdate = true;
        return texture;
      }
    
      start = () => {
        if (!this.frameId) {
          this.frameId = requestAnimationFrame(this.animate);
        }
      };
    
      async renderStuff() {
        this.texture = this.buildTexture(this.state.videoData);
        this.material.map = await this.texture;
      };
    
      animate() {
        this.renderScene();
        this.frameId = window.requestAnimationFrame(this.animate);
      };
    
      renderScene = () => {
        this.renderer.render(this.scene, this.camera);
      };
    
      async componentDidUpdate(prevProps, prevState, snapshot) {
        await this.renderStuff()
      };
    
      render() {
        return (
          <div ref={ (mount) => {
            this.mount = mount;
          } }
               >
          </div>
        );
      }
    }
    
    export default (Streamer);