UNPKG

@flyskywhy/react-native-gcanvas

Version:

A C++ native canvas 2D/WebGL component based on gpu opengl glsl shader GCanvas

350 lines (301 loc) 10.9 kB
import React, {Component} from 'react'; import { Platform, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import {GCanvasView} from '@flyskywhy/react-native-gcanvas'; export default class App extends Component { constructor(props) { super(props); this.canvas = null; this.state = { debugInfo: 'Click me to draw some on canvas', }; // only useful on Android, because it's always true on iOS this.isGReactTextureViewReady = true; } componentDidMount() { if (Platform.OS === 'web') { const resizeObserver = new ResizeObserver((entries) => { for (let entry of entries) { if (entry.target.id === 'canvasExample') { let {width, height} = entry.contentRect; this.onCanvasResize({width, height, canvas: entry.target}); } } }); resizeObserver.observe(document.getElementById('canvasExample')); } } initCanvas = (canvas) => { if (this.canvas) { return; } this.canvas = canvas; if (Platform.OS === 'web') { // canvas.width not equal canvas.clientWidth, so have to assign again this.canvas.width = this.canvas.clientWidth; this.canvas.height = this.canvas.clientHeight; } this.gl = this.canvas.getContext('webgl'); }; onCanvasResize = ({width, height, canvas}) => { canvas.width = width; canvas.height = height; // if isResetGlViewportAfterSetWidthOrHeight is true, you can use below this.interval && clearInterval(this.interval); this.drawSome(); }; drawSome = () => { // On Android, sometimes this.isGReactTextureViewReady is false e.g. // navigate from a canvas page into a drawer item page with // react-navigation on Android, the canvas page will be maintain // mounted by react-navigation, then if you continually call // this drawSome() in some loop, it's wasting CPU and GPU, // if you don't care about such wasting, you can delete // this.isGReactTextureViewReady and related onIsReady. if (this.gl && this.isGReactTextureViewReady) { this.startCube(); } }; // ref to https://github.com/flyskywhy/react-native-gcanvas/blob/f01fa6917c4a4938bf9b1bb8cab2358f93a7b4b4/examples/WeexGCanvasExample/examples/webgl/cube.vue startCube = () => { var gl = this.gl; var squareVerticesBuffer; var mvMatrix; var shaderProgram; var vertexPositionAttribute; var perspectiveMatrix; /*========== Defining and storing the geometry ==========*/ var vertices = [ -1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1, -1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1, -1,-1,-1, -1, 1,-1, -1, 1, 1, -1,-1, 1, 1,-1,-1, 1, 1,-1, 1, 1, 1, 1,-1, 1, -1,-1,-1, -1,-1, 1, 1,-1, 1, 1,-1,-1, -1, 1,-1, -1, 1, 1, 1, 1, 1, 1, 1,-1, ]; var colors = [ 5,3,7, 5,3,7, 5,3,7, 5,3,7, 1,1,3, 1,1,3, 1,1,3, 1,1,3, 0,0,1, 0,0,1, 0,0,1, 0,0,1, 1,0,0, 1,0,0, 1,0,0, 1,0,0, 1,1,0, 1,1,0, 1,1,0, 1,1,0, 0,1,0, 0,1,0, 0,1,0, 0,1,0, ]; var indices = [ 0,1,2, 0,2,3, 4,5,6, 4,6,7, 8,9,10, 8,10,11, 12,13,14, 12,14,15, 16,17,18, 16,18,19, 20,21,22, 20,22,23, ]; // Create and store data into vertex buffer var vertex_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); // Create and store data into color buffer var color_buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); // Create and store data into index buffer var index_buffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer); gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW, ); /*=================== Shaders =========================*/ var vertCode = 'attribute vec3 position;' + 'uniform mat4 Pmatrix;' + 'uniform mat4 Vmatrix;' + 'uniform mat4 Mmatrix;' + 'attribute vec3 color;' + //the color of the point 'varying vec3 vColor;' + 'void main(void) { ' + //pre-built function 'gl_Position = Pmatrix*Vmatrix*Mmatrix*vec4(position, 1.);' + 'vColor = color;' + '}'; var fragCode = 'precision mediump float;' + 'varying vec3 vColor;' + 'void main(void) {' + 'gl_FragColor = vec4(vColor, 1.);' + '}'; var vertShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertShader, vertCode); gl.compileShader(vertShader); var fragShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragShader, fragCode); gl.compileShader(fragShader); var shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertShader); gl.attachShader(shaderProgram, fragShader); gl.linkProgram(shaderProgram); /* ====== Associating attributes to vertex shader =====*/ var Pmatrix = gl.getUniformLocation(shaderProgram, 'Pmatrix'); var Vmatrix = gl.getUniformLocation(shaderProgram, 'Vmatrix'); var Mmatrix = gl.getUniformLocation(shaderProgram, 'Mmatrix'); gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer); var position = gl.getAttribLocation(shaderProgram, 'position'); gl.vertexAttribPointer(position, 3, gl.FLOAT, false, 0, 0); // Position gl.enableVertexAttribArray(position); gl.bindBuffer(gl.ARRAY_BUFFER, color_buffer); var color = gl.getAttribLocation(shaderProgram, 'color'); gl.vertexAttribPointer(color, 3, gl.FLOAT, false, 0, 0); // Color gl.enableVertexAttribArray(color); gl.useProgram(shaderProgram); /*==================== MATRIX =====================*/ function get_projection(angle, a, zMin, zMax) { var ang = Math.tan((angle * 0.5 * Math.PI) / 180); //angle*.5 return [ 0.5 / ang, 0 , 0, 0, 0, 0.5 * a / ang, 0, 0, 0, 0, -(zMax + zMin) / (zMax - zMin), -1, 0, 0, (-2 * zMax * zMin) / (zMax - zMin), 0, ]; } var width = this.canvas.width, height = this.canvas.height; var proj_matrix = get_projection(40, width / height, 1, 100); var mov_matrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; var view_matrix = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]; // translating z view_matrix[14] = view_matrix[14] - 6; //zoom /*==================== Rotation ====================*/ function rotateZ(m, angle) { var c = Math.cos(angle); var s = Math.sin(angle); var mv0 = m[0], mv4 = m[4], mv8 = m[8]; m[0] = c * m[0] - s * m[1]; m[4] = c * m[4] - s * m[5]; m[8] = c * m[8] - s * m[9]; m[1] = c * m[1] + s * mv0; m[5] = c * m[5] + s * mv4; m[9] = c * m[9] + s * mv8; } function rotateX(m, angle) { var c = Math.cos(angle); var s = Math.sin(angle); var mv1 = m[1], mv5 = m[5], mv9 = m[9]; m[1] = m[1] * c - m[2] * s; m[5] = m[5] * c - m[6] * s; m[9] = m[9] * c - m[10] * s; m[2] = m[2] * c + mv1 * s; m[6] = m[6] * c + mv5 * s; m[10] = m[10] * c + mv9 * s; } function rotateY(m, angle) { var c = Math.cos(angle); var s = Math.sin(angle); var mv0 = m[0], mv4 = m[4], mv8 = m[8]; m[0] = c * m[0] + s * m[2]; m[4] = c * m[4] + s * m[6]; m[8] = c * m[8] + s * m[10]; m[2] = c * m[2] - s * mv0; m[6] = c * m[6] - s * mv4; m[10] = c * m[10] - s * mv8; } /*=================Drawing===========================*/ var time_old = 0; var time = 0; function draw() { var dt = time - time_old; rotateZ(mov_matrix, dt * 0.005); //time rotateY(mov_matrix, dt * 0.002); rotateX(mov_matrix, dt * 0.003); time_old = time; time += 16; gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.clearColor(0.5, 0.5, 0.5, 0.9); gl.clearDepth(1.0); gl.viewport(0.0, 0.0, width, height); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); gl.uniformMatrix4fv(Pmatrix, false, proj_matrix); gl.uniformMatrix4fv(Vmatrix, false, view_matrix); gl.uniformMatrix4fv(Mmatrix, false, mov_matrix); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer); gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0); } this.interval = setInterval(draw, 16); }; takePicture = () => { if (this.canvas) { const data = this.canvas.toDataURL('image/jpeg', 0.77); console.warn(data); } }; render() { return ( <View style={styles.container}> <TouchableOpacity onPress={this.drawSome}> <Text style={styles.welcome}>{this.state.debugInfo}</Text> </TouchableOpacity> <View style={[styles.gcanvas, {backgroundColor: 'black'}]}> {Platform.OS === 'web' ? ( <canvas id={'canvasExample'} ref={this.initCanvas} style={ { flex: 1, width: '100%', // // width: 200, // height: 300, } /* canvas with react-native-web can't use width and height in styles.gcanvas */ } /> ) : ( <GCanvasView onCanvasResize={this.onCanvasResize} onCanvasCreate={this.initCanvas} onIsReady={(value) => (this.isGReactTextureViewReady = value)} isResetGlViewportAfterSetWidthOrHeight={true /* default is true, generally true for canvas 2d and false for webgl 3d */} disableAutoSwap={false /* Default is false. Only affect webgl. No matter true or false can offer 60 JS FPS. Most webgl APP has it's own gl.clear in it's own loop, so most webgl APP can set disableAutoSwap to true if display effect will be better */} style={styles.gcanvas} /> )} </View> <TouchableOpacity onPress={this.takePicture}> <Text style={styles.welcome}>Click me toDataURL('image/jpeg', 0.77)</Text> </TouchableOpacity> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#F5FCFF', }, gcanvas: { flex: 1, width: '100%', // above maybe will stuck APP // if let this component as a children of another component, // you can use below // width: 200, // height: 300, // backgroundColor: '#FF000030', // TextureView doesn't support displaying a background drawable since Android API 24 }, welcome: { fontSize: 20, textAlign: 'center', marginVertical: 20, }, });