Zpět

React & Three.js & react-three-fiber

Ukázka #1 - Data-binding do 3D scény

  • // Vyžaduje kompilaci pomocí: npm run build
    import React, { Suspense, useMemo, useRef } from 'react';
    import ReactDOM from 'react-dom';
    import * as THREE from 'three';
    import { Canvas, useLoader, useUpdate, useThree, useRender, extend } from 'react-three-fiber';
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
    
    
    // Bezstavový komponent pro 3D objekt textu
    const Text = ({ children, vAlign = 'center', hAlign = 'center', size = 1, diffuseColor = '#dd3300', emissiveColor = '#ee9966', ...props }) => {
      const font = useLoader(THREE.FontLoader, '../../../libs/threejs/three.helvetiker.json');
      const config = useMemo(
        () => ({ font, size: 20, height: 4, curveSegments: 32 }),
        [font]
      );
      const mesh = useUpdate(
        self => {
          const size = new THREE.Vector3();
          self.geometry.computeBoundingBox();
          self.geometry.boundingBox.getSize(size);
          self.position.x = hAlign === 'center' ? -size.x / 2 : hAlign === 'right' ? 0 : -size.x;
          self.position.y = vAlign === 'center' ? -size.y / 2 : vAlign === 'top' ? 0 : -size.y;
    	  self.material.color.setStyle(diffuseColor);
    	  self.material.emissive.setStyle(emissiveColor);
        },
        [children]
      );
      return (
        <group {...props} scale={[0.1 * size, 0.1 * size, 0.1]}>
          <mesh ref={mesh}>
            <textGeometry attach="geometry" args={[children, config]} />
            <meshStandardMaterial attach="material" color={diffuseColor} emissive={emissiveColor} emissiveIntensity={0.4} />
          </mesh>
        </group>
      )
    }
    
    
    // Bezstavový komponent ovládání uvnitř scény
    extend({ OrbitControls });
    const Controls = (props) => {
        const { camera, gl } = useThree();
        const controls = useRef();
        useRender(({ camera }) => {
            controls.current && controls.current.update();
            camera.updateMatrixWorld();
        });
        return <orbitControls ref={controls} args={[camera, gl.domElement]} {...props} />;
    };
    
    
    // Hlavní komponent aplikace
    class ExampleApp1 extends React.Component {
    
    	// konstruktor komponentu
    	constructor(props) {
    		super(props);
    		this.state = {
    			text: 'Hello world!',
    			diffuseColor: '#dd3300',
    			emissiveColor: '#ee9966'
    		};
    	}
    	
        // metoda pro vykreslení scény
    	renderScene() {
    		return (
    			<div className="fixed-ratio">
    				<Canvas camera={{ position: [0, 0, 5] }}>
    					<Controls />
    					<pointLight position={[2, 2, 5]} />
    					<Suspense fallback={null}>
    						<Text hAlign="center" position={[0, 0, 0]} children={this.state.text} diffuseColor={this.state.diffuseColor} emissiveColor={this.state.emissiveColor} />
    					</Suspense>
    				</Canvas>
    			</div>
    		);
    	}
    
    	// metoda pro vykreslení ovládacích prvků
    	renderControls() {
    		return (
    			<form>
    				<div>
    					<label>Text</label>
    					<!-- data-binding textu do vstupu, listener pro změnu -->
    					<input type="text"
                            value={this.state.text}
                            onChange={(e) => this.setState({ text: e.target.value })}
                        />
    				</div>
    				<div className="grid">
    				    <div>
    					    <label>Základní barva</label>
    					    <div>
    						    <input 
                                    type="color"
                                    value={this.state.diffuseColor}
                                    onChange={(e) => this.setState({ diffuseColor: e.target.value })} 
                                />
    						    <span style={{ color: this.state.diffuseColor }}>{this.state.diffuseColor}</span>
    					    </div>
    				    </div>
    				    <div>
    					    <label>Vyzařovaná barva</label>
    					    <div>
    						    <input 
                                    type="color"
                                    value={this.state.emissiveColor}
                                    onChange={(e) => this.setState({ emissiveColor: e.target.value })} 
                                />
    						    <span style={{ color: this.state.emissiveColor }}>{this.state.emissiveColor}</span>
    					    </div>
    				    </div>
    				</div>
    			</form>
    		);
    	}
    	
    	// hlavní metoda pro vykreslování
    	render() {
    		return (
    			<div>
    				{this.renderScene()}
    				{this.renderControls()}
    			</div>
    		);
    	}
    }
    
    // vykreslení aplikace do HTML elementu
    ReactDOM.render(<ExampleApp1 />, document.getElementById('app1'));
    
  • 
    npm install
    npm install react
    npm install react-dom
    npm install react-scripts
    npm install three
    npm install react-three-fiber
    npm run start
    

Ukázka #2 - Práce s polem

  • // Vyžaduje kompilaci pomocí: npm run build
        
    // importy
    import React, { useRef } from 'react';
    import ReactDOM from 'react-dom';
    import * as THREE from 'three';
    import { Canvas, useThree, useRender, extend } from 'react-three-fiber';
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
    
    // Bezstavový komponent ovládání uvnitř scény
    extend({ OrbitControls });
    const Controls = (props) => {
        const { camera, gl } = useThree();
        const controls = useRef();
        useRender(({ camera }) => {
            controls.current && controls.current.update();
            camera.updateMatrixWorld();
        })
        return <orbitControls ref={controls} args={[camera, gl.domElement]} {...props} />;
    }
    
    // Bezstavový komponent kostky
    const BoxThree = ({ instance, index }) => {
        const { position, size, color } = instance;
        return (
            <group position={new THREE.Vector3(index * 2, 0, 0)}>
                <mesh position={new THREE.Vector3(position.x, position.y, position.z)}>
                    <boxBufferGeometry attach="geometry" args={[size.x, size.y, size.z]} />
                    <meshStandardMaterial attach="material" color={new THREE.Color(color)} />
                </mesh>
            </group>
        );
    }
    
    // Bezstavový komponent ovládání kostky
    const BoxControl = ({ instance, index, onChange, remove }) => {
        const { position, size, color } = instance;
        return (
    		<li>
    			<div>
    				<div className="grid">
    					<div>
    						<label>Objekt {index + 1}</label>
    						<div>
    							<input type="color" value={color} onChange={onChange((box, value) => box.color = value)} />
    							<span style={{ color: color }}> { color } </span>
    						</div>
    					</div>
    					<div>
    						<div className="grid">
    							<div>
    								<label>↔ W { Math.floor(size.x * 100) }%</label>
    								<input type="range" value={size.x} min={0.1} max={2} step={0.1} onChange={onChange((box, value) => box.size.x = value)} />
    							</div>
    							<div>
    								<label>↕ H { Math.floor(size.y * 100) }%</label>
    								<input type="range" value={size.y} min={0.1} max={2} step={0.1} onChange={onChange((box, value) => box.size.y = value)} />
    							</div>
    							<div>
    								<label>⤢ L { Math.floor(size.z * 100) }%</label>
    								<input type="range" value={size.z} min={0.1} max={2} step={0.1} onChange={onChange((box, value) => box.size.z = value)} />
    							</div>
    							<div>
    								<label>→ X { position.x }</label>
    								<input type="range" value={position.x} min={-1} max={1} step={0.1} onChange={onChange((box, value) => box.position.x = value)} />
    							</div>
    							<div>
    								<label">↑ Y { position.y }</label>
    								<input type="range" value={position.y} min={-1} max={1} step={0.1} onChange={onChange((box, value) => box.position.y = value)} />
    							</div>
    							<div>
    								<label">↗ Z { position.z }</label>
    								<input type="range" value={position.z} min={-1} max={1} step={0.1} onChange={onChange((box, value) => box.position.z = value)} />
    							</div>
    						</div>
    					</div>
    					<div>
    						<a onClick={() => remove()}>Smazat</a>
    					</div>
    				</div>
    			</div>
    		</li>
        );
    }
    
    
    // Hlavní komponent aplikace
    class ExampleApp2 extends React.Component {
    
        // konstruktor komponentu
        constructor(props) {
            super(props);
            this.state = {
                boxes: []
            };
        }
    
        // metoda pro získání funkce pro změnu stavu kostky
        changeState(index) {
            var that = this;
            return (boxStateHandler) => {
                return (e) => {
                    var value = e.target.value;
                    return that.setState((state) => {
                        boxStateHandler(state.boxes[index], value);
                        return state;
                    });
                }
            }
        }
    
        // metoda pro smazání kostky
        remove(index) {
            var that = this;
            return () => {
                return that.setState((state) => {
                    state.boxes.splice(index, 1);
                    return state;
                });
            }
        }
    
        // metoda pro přidání kostky
        add() {
            this.setState((state) => {
                state.boxes.push({
                    position: { x: 0, y: 0, z: 0 },
                    size: { x: 1, y: 1, z: 1 },
                    color: "#ff9900" // nebo jiná náhodná barva
                });
                return state;
            });
        };
    
        // metoda volaná při připojení komponentu
    	componentDidMount() {
    		this.add();
    	}
    
        // metoda pro vykreslení
        render() {
            return (
                <div>
                     <div className="fixed-ratio">
                        <Canvas camera={{ position: [0, 0, 5] }}>
                            <Controls />
                            <pointLight position={[2, 2, 5]} />
                            <group position={new THREE.Vector3((- this.state.boxes.length + 1), 0, 0)}>
                                {this.state.boxes.map((box, index) => (
                                    <BoxThree instance={box} index={index} key={index} />
                                ))}
                            </group>
                        </Canvas>
                    </div>
                    <formgt;
                        <ul>
                            {this.state.boxes.map((box, index) => (
                                <BoxControl instance={box} index={index} key={index} onChange={this.changeState(index)} remove={this.remove(index)} />
                            ))}
                        </ul>
                        <div onClick={(e) => this.add()}>Přidat nový objekt</div>
                    </form>
                </div>
            );
        }
    }
    
    // vykreslení aplikace do HTML elementu
    ReactDOM.render(<ExampleApp2 />, document.getElementById('app2'));
    
  • 
    npm install
    npm install react
    npm install react-dom
    npm install react-scripts
    npm install three
    npm install react-three-fiber
    npm run start