From 31ec889565d0425ce10ff1bb8bea126ed7ad276f Mon Sep 17 00:00:00 2001 From: btzr-io Date: Wed, 6 Jun 2018 00:06:03 -0600 Subject: [PATCH 1/8] add threeViewer component --- package.json | 1 + .../component/common/loading-screen.jsx | 5 +- src/renderer/component/threeViewer/index.jsx | 269 ++++++++++++++++++ .../threeViewer/internal/detector.js | 10 + .../component/threeViewer/internal/loader.js | 35 +++ .../threeViewer/internal/renderer.js | 15 + .../component/threeViewer/internal/scene.js | 57 ++++ .../component/threeViewer/internal/three.js | 10 + yarn.lock | 4 + 9 files changed, 404 insertions(+), 2 deletions(-) create mode 100644 src/renderer/component/threeViewer/index.jsx create mode 100644 src/renderer/component/threeViewer/internal/detector.js create mode 100644 src/renderer/component/threeViewer/internal/loader.js create mode 100644 src/renderer/component/threeViewer/internal/renderer.js create mode 100644 src/renderer/component/threeViewer/internal/scene.js create mode 100644 src/renderer/component/threeViewer/internal/three.js diff --git a/package.json b/package.json index cfcc220bd..117d9114b 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "shapeshift.io": "^1.3.1", "source-map-support": "^0.5.4", "stream-to-blob-url": "^2.1.1", + "three": "^0.93.0", "tree-kill": "^1.1.0", "y18n": "^4.0.0" }, diff --git a/src/renderer/component/common/loading-screen.jsx b/src/renderer/component/common/loading-screen.jsx index c8eb3f3fb..f313b412d 100644 --- a/src/renderer/component/common/loading-screen.jsx +++ b/src/renderer/component/common/loading-screen.jsx @@ -3,8 +3,8 @@ import React from 'react'; import Spinner from 'component/spinner'; type Props = { - status: string, spinner: boolean, + status: string, }; class LoadingScreen extends React.PureComponent { @@ -17,7 +17,8 @@ class LoadingScreen extends React.PureComponent { return (
{spinner && } - {status && {status}} + + {status}
); } diff --git a/src/renderer/component/threeViewer/index.jsx b/src/renderer/component/threeViewer/index.jsx new file mode 100644 index 000000000..28afb5c86 --- /dev/null +++ b/src/renderer/component/threeViewer/index.jsx @@ -0,0 +1,269 @@ +// @flow +import * as React from 'react'; +import * as THREE from './internal/three.js'; +import detectWebGL from './internal/detector.js'; +import ThreeScene from './internal/scene.js'; +import ThreeLoader from './internal/loader.js'; +import ThreeRenderer from './internal/renderer.js'; +import LoadingScreen from 'component/common/loading-screen'; + +type Props = { + theme: string, + autoRotate: boolean, + source: { + fileType: string, + filePath: string, + }, +}; + +class ThreeViewer extends React.PureComponent { + constructor(props: Props) { + super(props); + //Main container + this.viewer = React.createRef(); + // Object colors + this.materialColors = { + red: '#e74c3c', + blue: '#3498db', + green: '#44b098', + orange: '#f39c12', + }; + + this.state = { + error: null, + isReady: false, + isLoading: false, + }; + + this.themes = { + dark: { + gridColor: '#414e5c', + groundColor: '#13233C', + backgroundColor: '#13233C', + centerLineColor: '#7f8c8d', + }, + light: { + gridColor: '#7f8c8d', + groundColor: '#DDD', + backgroundColor: '#EEE', + centerLineColor: '#2F2F2F', + }, + }; + + const { theme } = this.props; + this.theme = this.themes[theme] || this.themes.light; + } + + createOrbitControls(camera, canvas) { + const { autoRotate } = this.props; + const controls = new THREE.OrbitControls(camera, canvas); + // Controls configuration + controls.enableDamping = true; + controls.dampingFactor = 0.75; + controls.enableZoom = true; + controls.minDistance = 1; + controls.maxDistance = 50; + controls.autoRotate = autoRotate; + return controls; + } + + createGeometry(data) { + const geometry = new THREE.Geometry(); + geometry.fromBufferGeometry(data); + geometry.computeBoundingBox(); + geometry.computeVertexNormals(); + geometry.center(); + geometry.rotateX(-Math.PI / 2); + geometry.lookAt(new THREE.Vector3(0, 0, 1)); + return geometry; + } + + createWireFrame(group) { + const wireframe = new THREE.WireframeGeometry(group.geometry); + this.wireframe = new THREE.LineSegments(wireframe); + this.wireframe.material.depthTest = false; + this.wireframe.material.opacity = 0; + this.wireframe.transparent = true; + group.add(this.wireframe); + } + + createMesh(geometry) { + const material = new THREE.MeshPhongMaterial({ + opacity: 1, + transparent: true, + depthWrite: true, + vertexColors: THREE.FaceColors, + // Positive value pushes polygon further away + polygonOffsetFactor: 1, + polygonOffsetUnits: 1, + }); + + // Set material color + material.color.set(this.materialColors.green); + + const mesh = new THREE.Mesh(geometry, material); + mesh.name = 'objectGroup'; + this.scene.add(mesh); + this.fitMeshToCamera(mesh); + this.setControlsTarget(mesh.position); + return mesh; + } + + toggleWireFrame(show = false) { + this.wireframe.opacity = show ? 1 : 0; + this.mesh.material.opacity = show ? 0 : 1; + //this.mesh.material.shading(THREE.FlatShading); + } + + fitMeshToCamera(group) { + let max = { x: 0, y: 0, z: 0 }; + let min = { x: 0, y: 0, z: 0 }; + + group.traverse(child => { + if (child instanceof THREE.Mesh) { + const box = new THREE.Box3().setFromObject(group); + // Max + max.x = box.max.x > max.x ? box.max.x : max.x; + max.y = box.max.y > max.y ? box.max.y : max.y; + max.z = box.max.z > max.z ? box.max.z : max.z; + // Min + min.x = box.min.x < min.x ? box.min.x : min.x; + min.y = box.min.y < min.y ? box.min.y : min.y; + min.z = box.min.z < min.z ? box.min.z : min.z; + } + }); + + const meshY = Math.abs(max.y - min.y); + const meshX = Math.abs(max.x - min.x); + const meshZ = Math.abs(max.z - min.z); + const scaleFactor = 10 / Math.max(meshX, meshY); + + group.scale.set(scaleFactor, scaleFactor, scaleFactor); + group.position.y = meshY / 2 * scaleFactor; + group.position.multiplyScalar(-1); + group.position.y += meshY * scaleFactor; + this.createWireFrame(group); + } + + setControlsTarget(point) { + this.controls.target.fromArray([point.x, point.y, point.z]); + this.controls.update(); + } + + startLoader() { + const { source } = this.props; + source && + ThreeLoader(source, this.renderModel.bind(this), { + onStart: this.handleStart(this), + onLoad: this.handleReady.bind(this), + onError: this.handleError.bind(this), + onProgress: this.handleProgress.bind(this), + }); + } + + handleStart() { + this.setState({ isLoading: true }); + } + + handleReady() { + // Handle load ready + this.setState({ isReady: true, isLoading: false }); + this.toggleWireFrame(); + } + + handleResize = () => { + const { offsetWidth: width, offsetHeight: height } = this.viewer.current; + this.camera.aspect = width / height; + this.camera.updateProjectionMatrix(); + this.controls.update(); + this.renderer.setSize(width, height); + }; + + handleError(url) { + this.setState({ error: "Sorry, looks like we can't load this file" }); + } + + handleProgress(url, currentItem, totalItems) { + /// Handle progress + } + + handleColorChange(color) { + if (!this.mesh) return; + const pickColor = this.materialColors[color] || this.materialColors.green; + this.mesh.material.color.set(pickColor); + this.wireframe.material.color.set(pickColor); + } + + renderModel(fileType, data) { + const geometry = this.createGeometry(data); + this.mesh = this.createMesh(geometry); + } + + renderScene() { + this.renderer = ThreeRenderer({ + antialias: true, + shadowMap: true, + }); + + this.scene = ThreeScene({ + showFog: true, + showGrid: true, + ...this.theme, + }); + + const viewer = this.viewer.current; + const canvas = this.renderer.domElement; + const { offsetWidth: width, offsetHeight: height } = viewer; + // Camera + this.camera = new THREE.PerspectiveCamera(80, width / height, 0.1, 1000); + this.camera.position.set(-9.5, 14, 11); + // Controls + this.controls = this.createOrbitControls(this.camera, canvas); + // Set viewer size + this.renderer.setSize(width, height); + // Load file and render mesh + this.startLoader(); + + const updateScene = () => { + requestAnimationFrame(updateScene); + this.controls.update(); + this.renderer.render(this.scene, this.camera); + }; + + updateScene(); + // Append canvas + viewer.appendChild(canvas); + } + + componentDidMount() { + if (detectWebGL()) { + this.renderScene(); + // Update render on resize window + window.addEventListener('resize', this.handleResize, false); + } else { + // No webgl support, handle Error... + this.state({ error: 'No webgl support!' }); + } + } + + componentWillUnmount() { + window.removeEventListener('resize', this.handleResize, false); + } + + render() { + const { isReady, isLoading, error } = this.state; + const loadingMessage = 'Rendering model.'; + const showViewer = isReady && !error; + const showLoading = isLoading && !error; + + return ( + + {error && } + {showLoading && } +
+ + ); + } +} + +export default ThreeViewer; diff --git a/src/renderer/component/threeViewer/internal/detector.js b/src/renderer/component/threeViewer/internal/detector.js new file mode 100644 index 000000000..f75d46c3f --- /dev/null +++ b/src/renderer/component/threeViewer/internal/detector.js @@ -0,0 +1,10 @@ +const detectWebGL = () => { + // Create canvas element. + const canvas = document.createElement('canvas'); + // Get WebGLRenderingContext from canvas element. + const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); + // Return the result. + return gl && gl instanceof WebGLRenderingContext; +}; + +export default detectWebGL; diff --git a/src/renderer/component/threeViewer/internal/loader.js b/src/renderer/component/threeViewer/internal/loader.js new file mode 100644 index 000000000..2ba2dc83e --- /dev/null +++ b/src/renderer/component/threeViewer/internal/loader.js @@ -0,0 +1,35 @@ +import { LoadingManager, STLLoader, OBJLoader } from './three.js'; + +const Manager = ({ onLoad, onStart, onProgress, onError }) => { + const manager = new THREE.LoadingManager(); + manager.onLoad = onLoad; + manager.onStart = onStart; + //manager.onProgress = onProgress; + manager.onError = onError; + + return manager; +}; + +const Loader = (fileType, manager) => { + const fileTypes = { + stl: () => new STLLoader(manager), + obj: () => new OBJLoader(manager), + }; + return fileTypes[fileType] ? fileTypes[fileType]() : null; +}; + +const ThreeLoader = ({ fileType, filePath }, renderModel, managerEvents) => { + if (!fileType) return; + + const manager = Manager(managerEvents); + const loader = Loader(fileType, manager); + + // Unsuported loader + if (!loader) return false; + + loader.load(filePath, data => { + renderModel(fileType, data); + }); +}; + +export default ThreeLoader; diff --git a/src/renderer/component/threeViewer/internal/renderer.js b/src/renderer/component/threeViewer/internal/renderer.js new file mode 100644 index 000000000..11b6c0545 --- /dev/null +++ b/src/renderer/component/threeViewer/internal/renderer.js @@ -0,0 +1,15 @@ +import { WebGLRenderer } from './three'; + +const ThreeRenderer = ({ antialias, shadowMap }) => { + const renderer = new WebGLRenderer({ + antialias, + }); + // Renderer configuration + renderer.setPixelRatio(window.devicePixelRatio); + renderer.gammaInput = true; + renderer.gammaOutput = true; + renderer.shadowMap.enabled = shadowMap; + return renderer; +}; + +export default ThreeRenderer; diff --git a/src/renderer/component/threeViewer/internal/scene.js b/src/renderer/component/threeViewer/internal/scene.js new file mode 100644 index 000000000..c6e79c731 --- /dev/null +++ b/src/renderer/component/threeViewer/internal/scene.js @@ -0,0 +1,57 @@ +import * as THREE from './three.js'; + +const addGrid = (scene, { gridColor, centerLineColor, size }) => { + const divisions = size / 2; + const grid = new THREE.GridHelper( + size, + divisions, + new THREE.Color(centerLineColor), + new THREE.Color(gridColor) + ); + grid.material.opacity = 0.4; + grid.material.transparent = true; + scene.add(grid); +}; + +const addLights = (scene, color, groundColor) => { + // Light color + const lightColor = new THREE.Color(color); + // Main light + const light = new THREE.HemisphereLight(lightColor, groundColor, 0.4); + // Shadow light + const shadowLight = new THREE.DirectionalLight(lightColor, 0.4); + shadowLight.position.set(100, 50, 100); + // Back light + const backLight = new THREE.DirectionalLight(lightColor, 0.6); + backLight.position.set(-100, 200, 50); + // Add lights to scene + scene.add(backLight); + scene.add(light); + scene.add(shadowLight); +}; + +const Scene = ({ backgroundColor, groundColor, showFog, showGrid, gridColor, centerLineColor }) => { + // Convert colors + backgroundColor = new THREE.Color(backgroundColor); + groundColor = new THREE.Color(groundColor); + // New scene + const scene = new THREE.Scene(); + // Background color + scene.background = backgroundColor; + // Fog effect + scene.fog = showFog === true ? new THREE.Fog(backgroundColor, 1, 95) : null; + + showGrid && + addGrid(scene, { + size: 100, + gridColor, + centerLineColor, + }); + + // Add basic lights + addLights(scene, '#FFFFFF', groundColor); + // Return new three scene + return scene; +}; + +export default Scene; diff --git a/src/renderer/component/threeViewer/internal/three.js b/src/renderer/component/threeViewer/internal/three.js new file mode 100644 index 000000000..6cb0051d0 --- /dev/null +++ b/src/renderer/component/threeViewer/internal/three.js @@ -0,0 +1,10 @@ +import * as THREE from 'three'; + +// Currently it's not possible to import the files within the "examples/js" directory. +// Fix: https://github.com/mrdoob/three.js/issues/9562#issuecomment-383390251 +global.THREE = THREE; +require('three/examples/js/controls/OrbitControls'); +require('three/examples/js/loaders/OBJLoader'); +require('three/examples/js/loaders/STLLoader'); + +module.exports = global.THREE; diff --git a/yarn.lock b/yarn.lock index b25d9d41d..160424789 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8940,6 +8940,10 @@ text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" +three@^0.93.0: + version "0.93.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.93.0.tgz#3fd6c367ef4554abbb6e16ad69936283e895c123" + throttleit@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" -- 2.45.3 From 87ffac9a52c8d9ba2d71c4308a982e6b0f03f879 Mon Sep 17 00:00:00 2001 From: btzr-io Date: Sat, 9 Jun 2018 14:33:31 -0600 Subject: [PATCH 2/8] minor fixes --- src/renderer/component/threeViewer/index.jsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/renderer/component/threeViewer/index.jsx b/src/renderer/component/threeViewer/index.jsx index 28afb5c86..1c3c202f5 100644 --- a/src/renderer/component/threeViewer/index.jsx +++ b/src/renderer/component/threeViewer/index.jsx @@ -50,6 +50,7 @@ class ThreeViewer extends React.PureComponent { }, }; + // Select current theme const { theme } = this.props; this.theme = this.themes[theme] || this.themes.light; } @@ -102,9 +103,13 @@ class ThreeViewer extends React.PureComponent { material.color.set(this.materialColors.green); const mesh = new THREE.Mesh(geometry, material); + + // Assign name mesh.name = 'objectGroup'; + this.scene.add(mesh); this.fitMeshToCamera(mesh); + this.createWireFrame(mesh); this.setControlsTarget(mesh.position); return mesh; } @@ -112,7 +117,6 @@ class ThreeViewer extends React.PureComponent { toggleWireFrame(show = false) { this.wireframe.opacity = show ? 1 : 0; this.mesh.material.opacity = show ? 0 : 1; - //this.mesh.material.shading(THREE.FlatShading); } fitMeshToCamera(group) { @@ -142,7 +146,6 @@ class ThreeViewer extends React.PureComponent { group.position.y = meshY / 2 * scaleFactor; group.position.multiplyScalar(-1); group.position.y += meshY * scaleFactor; - this.createWireFrame(group); } setControlsTarget(point) { @@ -168,7 +171,6 @@ class ThreeViewer extends React.PureComponent { handleReady() { // Handle load ready this.setState({ isReady: true, isLoading: false }); - this.toggleWireFrame(); } handleResize = () => { @@ -184,7 +186,8 @@ class ThreeViewer extends React.PureComponent { } handleProgress(url, currentItem, totalItems) { - /// Handle progress + // Handle progress + // TODO: Show progressbar... } handleColorChange(color) { @@ -242,6 +245,7 @@ class ThreeViewer extends React.PureComponent { window.addEventListener('resize', this.handleResize, false); } else { // No webgl support, handle Error... + // TODO: Use a better error message this.state({ error: 'No webgl support!' }); } } -- 2.45.3 From e1c10964581bb07512d497d5a90c75e67d085591 Mon Sep 17 00:00:00 2001 From: btzr-io Date: Sun, 10 Jun 2018 21:51:39 -0600 Subject: [PATCH 3/8] add progress-bar --- .../component/common/loading-screen.jsx | 8 +++-- .../component/common/progress-bar.jsx | 23 +++++++++++++++ src/renderer/component/threeViewer/index.jsx | 29 +++++++++++-------- .../scss/component/_progress-bar.scss | 16 ++++++++++ 4 files changed, 61 insertions(+), 15 deletions(-) create mode 100644 src/renderer/component/common/progress-bar.jsx create mode 100644 src/renderer/scss/component/_progress-bar.scss diff --git a/src/renderer/component/common/loading-screen.jsx b/src/renderer/component/common/loading-screen.jsx index f313b412d..24bc33f2e 100644 --- a/src/renderer/component/common/loading-screen.jsx +++ b/src/renderer/component/common/loading-screen.jsx @@ -1,10 +1,12 @@ // @flow import React from 'react'; import Spinner from 'component/spinner'; +import ProgressBar from 'component/common/progress-bar'; type Props = { - spinner: boolean, status: string, + spinner: boolean, + progress?: number, }; class LoadingScreen extends React.PureComponent { @@ -17,8 +19,8 @@ class LoadingScreen extends React.PureComponent { return (
{spinner && } - - {status} + {progress && } + {status && {status}}
); } diff --git a/src/renderer/component/common/progress-bar.jsx b/src/renderer/component/common/progress-bar.jsx new file mode 100644 index 000000000..8b63206ba --- /dev/null +++ b/src/renderer/component/common/progress-bar.jsx @@ -0,0 +1,23 @@ +// @flow +import React from 'react'; + +type Props = { + progress: number, +}; + +class progressBar extends React.PureComponent { + static defaultProps = { + progress: 0, + }; + + render() { + const { progress } = this.props; + return ( +
+
+
+ ); + } +} + +export default LoadingScreen; diff --git a/src/renderer/component/threeViewer/index.jsx b/src/renderer/component/threeViewer/index.jsx index 1c3c202f5..0db371092 100644 --- a/src/renderer/component/threeViewer/index.jsx +++ b/src/renderer/component/threeViewer/index.jsx @@ -19,8 +19,12 @@ type Props = { class ThreeViewer extends React.PureComponent { constructor(props: Props) { super(props); + + const { theme } = this.props; + //Main container this.viewer = React.createRef(); + // Object colors this.materialColors = { red: '#e74c3c', @@ -29,12 +33,7 @@ class ThreeViewer extends React.PureComponent { orange: '#f39c12', }; - this.state = { - error: null, - isReady: false, - isLoading: false, - }; - + // Viewer themes this.themes = { dark: { gridColor: '#414e5c', @@ -51,8 +50,14 @@ class ThreeViewer extends React.PureComponent { }; // Select current theme - const { theme } = this.props; this.theme = this.themes[theme] || this.themes.light; + + // State + this.state = { + error: null, + isReady: false, + isLoading: false, + }; } createOrbitControls(camera, canvas) { @@ -186,8 +191,8 @@ class ThreeViewer extends React.PureComponent { } handleProgress(url, currentItem, totalItems) { - // Handle progress - // TODO: Show progressbar... + const progress = (currentItem / totalItems) * 100; + this.setState({progress}); } handleColorChange(color) { @@ -255,15 +260,15 @@ class ThreeViewer extends React.PureComponent { } render() { - const { isReady, isLoading, error } = this.state; + const { error, progress, isReady, isLoading } = this.state; const loadingMessage = 'Rendering model.'; const showViewer = isReady && !error; const showLoading = isLoading && !error; return ( - {error && } - {showLoading && } + {error && } + {showLoading && }
); diff --git a/src/renderer/scss/component/_progress-bar.scss b/src/renderer/scss/component/_progress-bar.scss new file mode 100644 index 000000000..4ee4b8dbe --- /dev/null +++ b/src/renderer/scss/component/_progress-bar.scss @@ -0,0 +1,16 @@ +.progress-bar { + width: 75%; + height: 5px; + display: block; + background: rgba(255, 255, 255, 0.25); + border-radius: 3px; + overflow: hidden; +} + +.progress-bar__progress { + width: 20px; + height: 5px; + background: var(--color-primary); + border-radius: 3px; + transition: width 0.3s ease; +} -- 2.45.3 From 064e28a02eebad69dab6fb29e9d9fc230d9031e4 Mon Sep 17 00:00:00 2001 From: btzr-io Date: Tue, 12 Jun 2018 16:40:55 -0600 Subject: [PATCH 4/8] more fixes --- .../component/common/loading-screen.jsx | 3 - .../component/common/progress-bar.jsx | 23 ---- src/renderer/component/threeViewer/index.jsx | 106 ++++++++++-------- .../component/threeViewer/internal/loader.js | 25 ++--- .../component/threeViewer/internal/scene.js | 18 +-- .../scss/component/_progress-bar.scss | 16 --- 6 files changed, 79 insertions(+), 112 deletions(-) delete mode 100644 src/renderer/component/common/progress-bar.jsx delete mode 100644 src/renderer/scss/component/_progress-bar.scss diff --git a/src/renderer/component/common/loading-screen.jsx b/src/renderer/component/common/loading-screen.jsx index 24bc33f2e..c8eb3f3fb 100644 --- a/src/renderer/component/common/loading-screen.jsx +++ b/src/renderer/component/common/loading-screen.jsx @@ -1,12 +1,10 @@ // @flow import React from 'react'; import Spinner from 'component/spinner'; -import ProgressBar from 'component/common/progress-bar'; type Props = { status: string, spinner: boolean, - progress?: number, }; class LoadingScreen extends React.PureComponent { @@ -19,7 +17,6 @@ class LoadingScreen extends React.PureComponent { return (
{spinner && } - {progress && } {status && {status}}
); diff --git a/src/renderer/component/common/progress-bar.jsx b/src/renderer/component/common/progress-bar.jsx deleted file mode 100644 index 8b63206ba..000000000 --- a/src/renderer/component/common/progress-bar.jsx +++ /dev/null @@ -1,23 +0,0 @@ -// @flow -import React from 'react'; - -type Props = { - progress: number, -}; - -class progressBar extends React.PureComponent { - static defaultProps = { - progress: 0, - }; - - render() { - const { progress } = this.props; - return ( -
-
-
- ); - } -} - -export default LoadingScreen; diff --git a/src/renderer/component/threeViewer/index.jsx b/src/renderer/component/threeViewer/index.jsx index 0db371092..8c74efa10 100644 --- a/src/renderer/component/threeViewer/index.jsx +++ b/src/renderer/component/threeViewer/index.jsx @@ -1,11 +1,12 @@ // @flow import * as React from 'react'; -import * as THREE from './internal/three.js'; -import detectWebGL from './internal/detector.js'; -import ThreeScene from './internal/scene.js'; -import ThreeLoader from './internal/loader.js'; -import ThreeRenderer from './internal/renderer.js'; import LoadingScreen from 'component/common/loading-screen'; +// ThreeJS +import * as THREE from './internal/three'; +import detectWebGL from './internal/detector'; +import ThreeScene from './internal/scene'; +import ThreeLoader from './internal/loader'; +import ThreeRenderer from './internal/renderer'; type Props = { theme: string, @@ -22,7 +23,7 @@ class ThreeViewer extends React.PureComponent { const { theme } = this.props; - //Main container + // Main container this.viewer = React.createRef(); // Object colors @@ -60,6 +61,22 @@ class ThreeViewer extends React.PureComponent { }; } + componentDidMount() { + if (detectWebGL()) { + this.renderScene(); + // Update render on resize window + window.addEventListener('resize', this.handleResize, false); + } else { + // No webgl support, handle Error... + // TODO: Use a better error message + this.state({ error: "Sorry, your computer doesn't support WebGL." }); + } + } + + componentWillUnmount() { + window.removeEventListener('resize', this.handleResize, false); + } + createOrbitControls(camera, canvas) { const { autoRotate } = this.props; const controls = new THREE.OrbitControls(camera, canvas); @@ -85,11 +102,15 @@ class ThreeViewer extends React.PureComponent { } createWireFrame(group) { - const wireframe = new THREE.WireframeGeometry(group.geometry); - this.wireframe = new THREE.LineSegments(wireframe); - this.wireframe.material.depthTest = false; - this.wireframe.material.opacity = 0; - this.wireframe.transparent = true; + const wireframeGeometry = new THREE.WireframeGeometry(group.geometry); + const wireframeMaterial = new THREE.LineBasicMaterial({ + opacity: 0, + transparent: true, + linewidth: 1, + }); + // Set material color + wireframeMaterial.color.set(this.materialColors.green); + this.wireframe = new THREE.LineSegments(wireframeGeometry, wireframeMaterial); group.add(this.wireframe); } @@ -115,7 +136,7 @@ class ThreeViewer extends React.PureComponent { this.scene.add(mesh); this.fitMeshToCamera(mesh); this.createWireFrame(mesh); - this.setControlsTarget(mesh.position); + this.updateControlsTarget(mesh.position); return mesh; } @@ -125,8 +146,8 @@ class ThreeViewer extends React.PureComponent { } fitMeshToCamera(group) { - let max = { x: 0, y: 0, z: 0 }; - let min = { x: 0, y: 0, z: 0 }; + const max = { x: 0, y: 0, z: 0 }; + const min = { x: 0, y: 0, z: 0 }; group.traverse(child => { if (child instanceof THREE.Mesh) { @@ -144,29 +165,25 @@ class ThreeViewer extends React.PureComponent { const meshY = Math.abs(max.y - min.y); const meshX = Math.abs(max.x - min.x); - const meshZ = Math.abs(max.z - min.z); - const scaleFactor = 10 / Math.max(meshX, meshY); + const scaleFactor = 15 / Math.max(meshX, meshY); group.scale.set(scaleFactor, scaleFactor, scaleFactor); - group.position.y = meshY / 2 * scaleFactor; + group.position.setY((meshY / 2) * scaleFactor); group.position.multiplyScalar(-1); - group.position.y += meshY * scaleFactor; - } - - setControlsTarget(point) { - this.controls.target.fromArray([point.x, point.y, point.z]); - this.controls.update(); + group.position.setY(meshY * scaleFactor); } startLoader() { const { source } = this.props; - source && + + if (source) { ThreeLoader(source, this.renderModel.bind(this), { onStart: this.handleStart(this), onLoad: this.handleReady.bind(this), onError: this.handleError.bind(this), onProgress: this.handleProgress.bind(this), }); + } } handleStart() { @@ -186,13 +203,13 @@ class ThreeViewer extends React.PureComponent { this.renderer.setSize(width, height); }; - handleError(url) { + handleError() { this.setState({ error: "Sorry, looks like we can't load this file" }); } - handleProgress(url, currentItem, totalItems) { - const progress = (currentItem / totalItems) * 100; - this.setState({progress}); + handleProgress() { + // const progress = (currentItem / totalItems) * 100; + // console.info(currentItem, totalItems, progress); } handleColorChange(color) { @@ -202,6 +219,11 @@ class ThreeViewer extends React.PureComponent { this.wireframe.material.color.set(pickColor); } + updateControlsTarget(point) { + this.controls.target.fromArray([point.x, point.y, point.z]); + this.controls.update(); + } + renderModel(fileType, data) { const geometry = this.createGeometry(data); this.mesh = this.createMesh(geometry); @@ -243,33 +265,21 @@ class ThreeViewer extends React.PureComponent { viewer.appendChild(canvas); } - componentDidMount() { - if (detectWebGL()) { - this.renderScene(); - // Update render on resize window - window.addEventListener('resize', this.handleResize, false); - } else { - // No webgl support, handle Error... - // TODO: Use a better error message - this.state({ error: 'No webgl support!' }); - } - } - - componentWillUnmount() { - window.removeEventListener('resize', this.handleResize, false); - } - render() { - const { error, progress, isReady, isLoading } = this.state; - const loadingMessage = 'Rendering model.'; + const { error, isReady, isLoading } = this.state; + const loadingMessage = 'Loading 3D model.'; const showViewer = isReady && !error; const showLoading = isLoading && !error; return ( {error && } - {showLoading && } -
+ {showLoading && } +
); } diff --git a/src/renderer/component/threeViewer/internal/loader.js b/src/renderer/component/threeViewer/internal/loader.js index 2ba2dc83e..96bdf26b6 100644 --- a/src/renderer/component/threeViewer/internal/loader.js +++ b/src/renderer/component/threeViewer/internal/loader.js @@ -1,10 +1,10 @@ -import { LoadingManager, STLLoader, OBJLoader } from './three.js'; +import { LoadingManager, STLLoader, OBJLoader } from './three'; const Manager = ({ onLoad, onStart, onProgress, onError }) => { - const manager = new THREE.LoadingManager(); + const manager = new LoadingManager(); manager.onLoad = onLoad; manager.onStart = onStart; - //manager.onProgress = onProgress; + manager.onProgress = onProgress; manager.onError = onError; return manager; @@ -19,17 +19,16 @@ const Loader = (fileType, manager) => { }; const ThreeLoader = ({ fileType, filePath }, renderModel, managerEvents) => { - if (!fileType) return; + if (fileType) { + const manager = Manager(managerEvents); + const loader = Loader(fileType, manager); - const manager = Manager(managerEvents); - const loader = Loader(fileType, manager); - - // Unsuported loader - if (!loader) return false; - - loader.load(filePath, data => { - renderModel(fileType, data); - }); + if (loader) { + loader.load(filePath, data => { + renderModel(fileType, data); + }); + } + } }; export default ThreeLoader; diff --git a/src/renderer/component/threeViewer/internal/scene.js b/src/renderer/component/threeViewer/internal/scene.js index c6e79c731..f858c8832 100644 --- a/src/renderer/component/threeViewer/internal/scene.js +++ b/src/renderer/component/threeViewer/internal/scene.js @@ -1,4 +1,4 @@ -import * as THREE from './three.js'; +import * as THREE from './three'; const addGrid = (scene, { gridColor, centerLineColor, size }) => { const divisions = size / 2; @@ -31,25 +31,25 @@ const addLights = (scene, color, groundColor) => { }; const Scene = ({ backgroundColor, groundColor, showFog, showGrid, gridColor, centerLineColor }) => { - // Convert colors - backgroundColor = new THREE.Color(backgroundColor); - groundColor = new THREE.Color(groundColor); + // Convert color + const bgColor = new THREE.Color(backgroundColor); // New scene const scene = new THREE.Scene(); // Background color - scene.background = backgroundColor; + scene.background = bgColor; // Fog effect - scene.fog = showFog === true ? new THREE.Fog(backgroundColor, 1, 95) : null; - - showGrid && + scene.fog = showFog === true ? new THREE.Fog(bgColor, 1, 95) : null; + // Add grid + if (showGrid) { addGrid(scene, { size: 100, gridColor, centerLineColor, }); - + } // Add basic lights addLights(scene, '#FFFFFF', groundColor); + // Return new three scene return scene; }; diff --git a/src/renderer/scss/component/_progress-bar.scss b/src/renderer/scss/component/_progress-bar.scss deleted file mode 100644 index 4ee4b8dbe..000000000 --- a/src/renderer/scss/component/_progress-bar.scss +++ /dev/null @@ -1,16 +0,0 @@ -.progress-bar { - width: 75%; - height: 5px; - display: block; - background: rgba(255, 255, 255, 0.25); - border-radius: 3px; - overflow: hidden; -} - -.progress-bar__progress { - width: 20px; - height: 5px; - background: var(--color-primary); - border-radius: 3px; - transition: width 0.3s ease; -} -- 2.45.3 From 34ba2cf3ebad8383e045d606ff8b5c27921e53a7 Mon Sep 17 00:00:00 2001 From: btzr-io Date: Tue, 12 Jun 2018 17:18:00 -0600 Subject: [PATCH 5/8] fix object position --- src/renderer/component/threeViewer/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/component/threeViewer/index.jsx b/src/renderer/component/threeViewer/index.jsx index 8c74efa10..475630421 100644 --- a/src/renderer/component/threeViewer/index.jsx +++ b/src/renderer/component/threeViewer/index.jsx @@ -170,7 +170,7 @@ class ThreeViewer extends React.PureComponent { group.scale.set(scaleFactor, scaleFactor, scaleFactor); group.position.setY((meshY / 2) * scaleFactor); group.position.multiplyScalar(-1); - group.position.setY(meshY * scaleFactor); + group.position.setY((meshY * scaleFactor) / 2); } startLoader() { -- 2.45.3 From ac89a3d91cefbb876324f8045ce2773ab1d4a178 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Mon, 16 Jul 2018 10:38:46 -0400 Subject: [PATCH 6/8] re-add 3d-file viewer --- src/renderer/component/fileRender/view.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/component/fileRender/view.jsx b/src/renderer/component/fileRender/view.jsx index 5ce35c6b7..dba8ecbb2 100644 --- a/src/renderer/component/fileRender/view.jsx +++ b/src/renderer/component/fileRender/view.jsx @@ -2,6 +2,7 @@ import React from 'react'; import LoadingScreen from 'component/common/loading-screen'; import PdfViewer from 'component/viewers/pdfViewer'; +import ThreeViewer from 'component/threeViewer'; type Props = { mediaType: string, @@ -20,7 +21,7 @@ class FileRender extends React.PureComponent { // Supported mediaTypes const mediaTypes = { - // '3D-file': , + '3D-file': , // Add routes to viewer... }; -- 2.45.3 From 7db1782c5730a758a0e0f9287c311707b67a0bc3 Mon Sep 17 00:00:00 2001 From: btzr-io Date: Thu, 19 Jul 2018 00:45:32 -0600 Subject: [PATCH 7/8] more minor fixes --- src/main/index.js | 7 +++++ src/renderer/component/fileRender/view.jsx | 2 +- .../{ => viewers}/threeViewer/index.jsx | 31 +++++++------------ .../threeViewer/internal/detector.js | 0 .../threeViewer/internal/loader.js | 3 +- .../threeViewer/internal/renderer.js | 0 .../threeViewer/internal/scene.js | 0 .../threeViewer/internal/three.js | 0 8 files changed, 21 insertions(+), 22 deletions(-) rename src/renderer/component/{ => viewers}/threeViewer/index.jsx (93%) rename src/renderer/component/{ => viewers}/threeViewer/internal/detector.js (100%) rename src/renderer/component/{ => viewers}/threeViewer/internal/loader.js (88%) rename src/renderer/component/{ => viewers}/threeViewer/internal/renderer.js (100%) rename src/renderer/component/{ => viewers}/threeViewer/internal/scene.js (100%) rename src/renderer/component/{ => viewers}/threeViewer/internal/three.js (100%) diff --git a/src/main/index.js b/src/main/index.js index 807ca08e4..1c81d6217 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -53,6 +53,13 @@ app.setAsDefaultProtocolClient('lbry'); app.setName('LBRY'); app.setAppUserModelId('io.lbry.LBRY'); +if (isDev) { + // Enable WEBGL + app.commandLine.appendSwitch('ignore-gpu-blacklist'); + app.commandLine.appendSwitch('--disable-gpu-process-crash-limit'); + app.disableDomainBlockingFor3DAPIs(); +} + app.on('ready', async () => { const processList = await findProcess('name', 'lbrynet-daemon'); const isDaemonRunning = processList.length > 0; diff --git a/src/renderer/component/fileRender/view.jsx b/src/renderer/component/fileRender/view.jsx index dba8ecbb2..79aca834a 100644 --- a/src/renderer/component/fileRender/view.jsx +++ b/src/renderer/component/fileRender/view.jsx @@ -2,7 +2,7 @@ import React from 'react'; import LoadingScreen from 'component/common/loading-screen'; import PdfViewer from 'component/viewers/pdfViewer'; -import ThreeViewer from 'component/threeViewer'; +import ThreeViewer from 'component/viewers/threeViewer'; type Props = { mediaType: string, diff --git a/src/renderer/component/threeViewer/index.jsx b/src/renderer/component/viewers/threeViewer/index.jsx similarity index 93% rename from src/renderer/component/threeViewer/index.jsx rename to src/renderer/component/viewers/threeViewer/index.jsx index 475630421..63dc38622 100644 --- a/src/renderer/component/threeViewer/index.jsx +++ b/src/renderer/component/viewers/threeViewer/index.jsx @@ -69,7 +69,7 @@ class ThreeViewer extends React.PureComponent { } else { // No webgl support, handle Error... // TODO: Use a better error message - this.state({ error: "Sorry, your computer doesn't support WebGL." }); + this.setState({ error: "Sorry, your computer doesn't support WebGL." }); } } @@ -178,22 +178,24 @@ class ThreeViewer extends React.PureComponent { if (source) { ThreeLoader(source, this.renderModel.bind(this), { - onStart: this.handleStart(this), - onLoad: this.handleReady.bind(this), - onError: this.handleError.bind(this), - onProgress: this.handleProgress.bind(this), + onStart: this.handleStart, + onLoad: this.handleReady, + onError: this.handleError, }); } } - handleStart() { + handleStart = () => { this.setState({ isLoading: true }); - } + }; - handleReady() { - // Handle load ready + handleReady = () => { this.setState({ isReady: true, isLoading: false }); - } + }; + + handleError = () => { + this.setState({ error: "Sorry, looks like we can't load this file" }); + }; handleResize = () => { const { offsetWidth: width, offsetHeight: height } = this.viewer.current; @@ -203,15 +205,6 @@ class ThreeViewer extends React.PureComponent { this.renderer.setSize(width, height); }; - handleError() { - this.setState({ error: "Sorry, looks like we can't load this file" }); - } - - handleProgress() { - // const progress = (currentItem / totalItems) * 100; - // console.info(currentItem, totalItems, progress); - } - handleColorChange(color) { if (!this.mesh) return; const pickColor = this.materialColors[color] || this.materialColors.green; diff --git a/src/renderer/component/threeViewer/internal/detector.js b/src/renderer/component/viewers/threeViewer/internal/detector.js similarity index 100% rename from src/renderer/component/threeViewer/internal/detector.js rename to src/renderer/component/viewers/threeViewer/internal/detector.js diff --git a/src/renderer/component/threeViewer/internal/loader.js b/src/renderer/component/viewers/threeViewer/internal/loader.js similarity index 88% rename from src/renderer/component/threeViewer/internal/loader.js rename to src/renderer/component/viewers/threeViewer/internal/loader.js index 96bdf26b6..dc4ce7482 100644 --- a/src/renderer/component/threeViewer/internal/loader.js +++ b/src/renderer/component/viewers/threeViewer/internal/loader.js @@ -1,10 +1,9 @@ import { LoadingManager, STLLoader, OBJLoader } from './three'; -const Manager = ({ onLoad, onStart, onProgress, onError }) => { +const Manager = ({ onLoad, onStart, onError }) => { const manager = new LoadingManager(); manager.onLoad = onLoad; manager.onStart = onStart; - manager.onProgress = onProgress; manager.onError = onError; return manager; diff --git a/src/renderer/component/threeViewer/internal/renderer.js b/src/renderer/component/viewers/threeViewer/internal/renderer.js similarity index 100% rename from src/renderer/component/threeViewer/internal/renderer.js rename to src/renderer/component/viewers/threeViewer/internal/renderer.js diff --git a/src/renderer/component/threeViewer/internal/scene.js b/src/renderer/component/viewers/threeViewer/internal/scene.js similarity index 100% rename from src/renderer/component/threeViewer/internal/scene.js rename to src/renderer/component/viewers/threeViewer/internal/scene.js diff --git a/src/renderer/component/threeViewer/internal/three.js b/src/renderer/component/viewers/threeViewer/internal/three.js similarity index 100% rename from src/renderer/component/threeViewer/internal/three.js rename to src/renderer/component/viewers/threeViewer/internal/three.js -- 2.45.3 From 61dc4cb73c9d5b4326df75b5f95fb51706dfa6e5 Mon Sep 17 00:00:00 2001 From: Sean Yesmunt Date: Thu, 19 Jul 2018 09:55:33 -0400 Subject: [PATCH 8/8] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26713a24b..44c9f58f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Added + * Added 3D file viewer for OBJ & STL file types ([#1558](https://github.com/lbryio/lbry-desktop/pull/1558)) + + ### Changed -- 2.45.3