1import { ExpoWebGLRenderingContext, GLView } from 'expo-gl';
2import { Renderer, TextureLoader } from 'expo-three';
3import * as React from 'react';
4import { LayoutChangeEvent, PixelRatio, StyleSheet, View } from 'react-native';
5import { PerspectiveCamera, Scene, Sprite, SpriteMaterial } from 'three';
6
7export default function GLThreeSprite() {
8  const animationFrameId = React.useRef(-1);
9  const gl = React.useRef<null | ExpoWebGLRenderingContext>(null);
10  const camera = React.useRef<null | PerspectiveCamera>(null);
11  const scene = React.useRef<null | Scene>(null);
12  const renderer = React.useRef<null | Renderer>(null);
13
14  React.useEffect(() => {
15    return () => {
16      if (animationFrameId.current >= 0) {
17        cancelAnimationFrame(animationFrameId.current);
18      }
19    };
20  }, []);
21
22  const animate = React.useCallback(() => {
23    animationFrameId.current = requestAnimationFrame(animate);
24
25    if (renderer.current && scene.current && camera.current)
26      renderer.current.render(scene.current, camera.current);
27
28    if (gl.current) {
29      gl.current.endFrameEXP();
30    }
31  }, []);
32
33  React.useEffect(() => {
34    if (animationFrameId.current) {
35      cancelAnimationFrame(animationFrameId.current);
36      animate();
37    }
38  }, [animate]);
39
40  const onLayout = React.useCallback(({ nativeEvent: { layout } }: LayoutChangeEvent) => {
41    if (camera.current) {
42      camera.current.aspect = layout.width / layout.height;
43      camera.current.updateProjectionMatrix();
44    }
45    if (renderer.current) {
46      const scale = PixelRatio.get();
47      renderer.current.setSize(layout.width * scale, layout.height * scale);
48    }
49  }, []);
50
51  const onContextCreate = React.useCallback(
52    async (context: ExpoWebGLRenderingContext) => {
53      gl.current = context;
54      scene.current = new Scene();
55      camera.current = new PerspectiveCamera(
56        75,
57        gl.current.drawingBufferWidth / gl.current.drawingBufferHeight,
58        0.1,
59        1000
60      );
61
62      renderer.current = new Renderer({ gl: gl.current });
63      renderer.current.setSize(gl.current.drawingBufferWidth, gl.current.drawingBufferHeight);
64      renderer.current.setClearColor(0xffffff);
65
66      const spriteMaterial = new SpriteMaterial({
67        map: new TextureLoader().load(require('../../../assets/images/nikki.png')),
68        color: 0xffffff,
69      });
70      const sprite = new Sprite(spriteMaterial);
71      scene.current.add(sprite);
72
73      camera.current.position.z = 3;
74
75      animate();
76      renderer.current.render(scene.current, camera.current);
77      gl.current.endFrameEXP();
78    },
79    [animate]
80  );
81
82  return (
83    <View style={styles.flex}>
84      <GLView style={styles.flex} onLayout={onLayout} onContextCreate={onContextCreate} />
85    </View>
86  );
87}
88
89GLThreeSprite.title = 'three.js sprite rendering';
90
91const styles = StyleSheet.create({
92  flex: {
93    flex: 1,
94  },
95});
96