├── App.js
├── README.md
├── __tests__
└── App-test.js
├── _universe
├── app.json
├── entry.js
├── rn-cli.config.js
└── transformer.js
├── api
├── GooglePoly.js
└── registerForPushNotificationsAsync.js
├── app.json
├── assets
├── fonts
│ └── SpaceMono-Regular.ttf
├── images
│ ├── icon.png
│ ├── robot-dev.png
│ ├── robot-prod.png
│ └── splash.png
└── objects
│ └── TurkeyObject.json
├── components
├── .DS_Store
├── AppComponents
│ ├── GooglePolyAsset.js
│ ├── SearchableGooglePolyAssetList.js
│ └── index.js
└── __tests__
│ └── StyledText-test.js
├── constants
└── Colors.js
├── navigation
├── MainTabNavigator.js
└── RootNavigation.js
├── package.json
├── screens
└── HomeScreen.js
└── util
├── MTLLoader.js
└── OBJLoader.js
/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform, StatusBar, StyleSheet, View } from 'react-native';
3 | import { AppLoading, Asset, Font } from 'expo';
4 | import { Ionicons } from '@expo/vector-icons';
5 | import RootNavigation from './navigation/RootNavigation';
6 |
7 | export default class App extends React.Component {
8 | state = {
9 | isLoadingComplete: false,
10 | };
11 |
12 | render() {
13 | if (!this.state.isLoadingComplete && !this.props.skipLoadingScreen) {
14 | return (
15 |
20 | );
21 | } else {
22 | return (
23 |
24 | {Platform.OS === 'ios' && }
25 | {Platform.OS === 'android' && }
26 |
27 |
28 | );
29 | }
30 | }
31 |
32 | _loadResourcesAsync = async () => {
33 | return Promise.all([
34 | Asset.loadAsync([
35 | require('./assets/images/robot-dev.png'),
36 | require('./assets/images/robot-prod.png'),
37 | ]),
38 | Font.loadAsync({
39 | // This is the font that we are using for our tab bar
40 | ...Ionicons.font,
41 | // We include SpaceMono because we use it in HomeScreen.js. Feel free
42 | // to remove this if you are not using it in your app
43 | 'space-mono': require('./assets/fonts/SpaceMono-Regular.ttf'),
44 | }),
45 | ]);
46 | };
47 |
48 | _handleLoadingError = error => {
49 | // In this case, you might want to report the error to your error
50 | // reporting service, for example Sentry
51 | console.warn(error);
52 | };
53 |
54 | _handleFinishLoading = () => {
55 | this.setState({ isLoadingComplete: true });
56 | };
57 | }
58 |
59 | const styles = StyleSheet.create({
60 | container: {
61 | flex: 1,
62 | backgroundColor: '#fff',
63 | },
64 | statusBarUnderlay: {
65 | height: 24,
66 | backgroundColor: 'rgba(0,0,0,0.2)',
67 | },
68 | });
69 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PolySnap
2 | Augmented Reality application using Expo and Google Poly
3 |
--------------------------------------------------------------------------------
/__tests__/App-test.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import App from '../App';
4 | import renderer from 'react-test-renderer';
5 |
6 | it('renders the loading screen', async () => {
7 | const tree = renderer.create().toJSON();
8 | expect(tree).toMatchSnapshot();
9 | });
10 |
11 | it('renders the root without loading screen', async () => {
12 | const tree = renderer.create().toJSON();
13 | expect(tree).toMatchSnapshot();
14 | });
15 |
--------------------------------------------------------------------------------
/_universe/app.json:
--------------------------------------------------------------------------------
1 | {
2 | expo: {
3 | name: "My New Project",
4 | description: "No description",
5 | slug: "my-new-project",
6 | privacy: "unlisted",
7 | sdkVersion: "UNVERSIONED",
8 | version: "1.0.0",
9 | orientation: "portrait",
10 | primaryColor: "#cccccc",
11 | icon: "./assets/images/icon.png",
12 | splash: {
13 | image: "./assets/images/splash.png",
14 | resizeMode: "contain",
15 | backgroundColor: "#ffffff"
16 | },
17 | ios: {
18 | supportsTablet: true
19 | },
20 | packagerOpts: {
21 | config: "./_universe/rn-cli.config.js"
22 | },
23 | entryPoint: "_universe/entry.js"
24 | }
25 | }
--------------------------------------------------------------------------------
/_universe/entry.js:
--------------------------------------------------------------------------------
1 | import Expo from 'expo';
2 | import App from '../App.js';
3 |
4 | Expo.registerRootComponent(App);
5 |
--------------------------------------------------------------------------------
/_universe/rn-cli.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @noflow
3 | */
4 |
5 | const fs = require('fs');
6 | const path = require('path');
7 |
8 | let metroBundlerRequirePath;
9 | const rootPath = path.resolve('.');
10 | const nodeModulePath = path.join(rootPath, 'node_modules');
11 | const reactNativeModulePath = path.join(nodeModulePath, 'react-native');
12 | const stats = fs.lstatSync(reactNativeModulePath);
13 | if (stats.isSymbolicLink()) {
14 | metroBundlerRequirePath = 'react-native/node_modules/metro-bundler';
15 | } else {
16 | metroBundlerRequirePath = 'metro-bundler';
17 | }
18 |
19 | const blacklist = require(metroBundlerRequirePath + '/src/blacklist');
20 | const babelRegisterOnly = require(metroBundlerRequirePath + '/src/babelRegisterOnly');
21 |
22 | const registeredTransformModulePaths = new Set();
23 |
24 | module.exports = {
25 | getBlacklistRE() {
26 | return blacklist([/__internal__\/(.*)/]);
27 | },
28 | getProvidesModuleNodeModules() {
29 | return [];
30 | },
31 | getTransformModulePath() {
32 | const modulePath = path.join(__dirname, 'transformer.js');
33 | if (!registeredTransformModulePaths.has(modulePath)) {
34 | babelRegisterOnly([modulePath]);
35 | registeredTransformModulePaths.add(modulePath);
36 | }
37 | return modulePath;
38 | },
39 | extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
40 | };
41 |
42 | function getNodeModulesForDirectory(rootPath) {
43 | const nodeModulePath = path.join(rootPath, 'node_modules');
44 | const folders = fs.readdirSync(nodeModulePath);
45 | return folders.reduce((modules, folderName) => {
46 | const folderPath = path.join(nodeModulePath, folderName);
47 | if (folderName.startsWith('@')) {
48 | const scopedModuleFolders = fs.readdirSync(folderPath);
49 | const scopedModules = scopedModuleFolders.reduce((scopedModules, scopedFolderName) => {
50 | scopedModules[`${folderName}/${scopedFolderName}`] = maybeResolveSymlink(
51 | path.join(folderPath, scopedFolderName)
52 | );
53 | return scopedModules;
54 | }, {});
55 | return Object.assign({}, modules, scopedModules);
56 | }
57 | modules[folderName] = maybeResolveSymlink(folderPath);
58 | return modules;
59 | }, {});
60 | }
61 |
62 | function maybeResolveSymlink(maybeSymlinkPath) {
63 | if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) {
64 | const resolved = path.resolve(
65 | path.dirname(maybeSymlinkPath),
66 | fs.readlinkSync(maybeSymlinkPath)
67 | );
68 | return resolved;
69 | }
70 | return maybeSymlinkPath;
71 | }
72 |
--------------------------------------------------------------------------------
/_universe/transformer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2015-present, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | * Note: This is a fork of the fb-specific transform.js
10 | *
11 | */
12 | 'use strict';
13 |
14 | /**
15 | * [Expo] This transformer was based on React Native's transformer with the
16 | * following changes:
17 | * - Makes the packager use react-native-lab's copy of react-native
18 | * - Rewrites the paths of this module's dependencies so we load the
19 | * dependencies from react-native-lab's copy of react-native, to simulate
20 | * if we hadn't forked the transformer at all
21 | */
22 |
23 | const fs = require('fs');
24 | const path = require('path');
25 |
26 | let moduleBasePath = '';
27 | let isLinked = false;
28 | const rootPath = path.resolve('.');
29 | const nodeModulePath = path.join(rootPath, 'node_modules');
30 | const reactNativeModulePath = path.join(nodeModulePath, 'react-native');
31 | const stats = fs.lstatSync(reactNativeModulePath);
32 | if (stats.isSymbolicLink()) {
33 | isLinked = true;
34 | moduleBasePath = path.join(reactNativeModulePath, 'node_modules') + path.sep;
35 | }
36 |
37 | const babel = require(moduleBasePath + 'babel-core');
38 | const crypto = require('crypto');
39 | const externalHelpersPlugin = require(moduleBasePath + 'babel-plugin-external-helpers');
40 | const generate = require(moduleBasePath + 'babel-generator').default;
41 | const inlineRequiresPlugin = require(moduleBasePath + 'babel-preset-fbjs/plugins/inline-requires');
42 | const makeHMRConfig = require(moduleBasePath + 'babel-preset-react-native/configs/hmr');
43 | const resolvePlugins = require(moduleBasePath + 'babel-preset-react-native/lib/resolvePlugins');
44 |
45 | const { compactMapping } = require(moduleBasePath + 'metro-bundler/src/Bundler/source-map');
46 |
47 | const cacheKeyParts = [
48 | fs.readFileSync(__filename),
49 | require(moduleBasePath + 'babel-plugin-external-helpers/package.json').version,
50 | require(moduleBasePath + 'babel-preset-fbjs/package.json').version,
51 | require(moduleBasePath + 'babel-preset-react-native/package.json').version,
52 | ];
53 |
54 | const EXPO_REACT_NATIVE_PATH = isLinked
55 | ? path.join(process.env.EXPO_UNIVERSE_DIR, 'react-native-lab', 'react-native')
56 | : reactNativeModulePath;
57 | if (!fs.existsSync(EXPO_REACT_NATIVE_PATH)) {
58 | throw new Error(
59 | `Expo copy of React Native could not be found. Are you sure it exists at: ${EXPO_REACT_NATIVE_PATH}?`
60 | );
61 | }
62 | let EXPO_REACT_PATH = isLinked
63 | ? path.join(EXPO_REACT_NATIVE_PATH, 'node_modules/react')
64 | : path.join(nodeModulePath, 'react');
65 | if (!fs.existsSync(EXPO_REACT_PATH)) {
66 | throw new Error(
67 | `React Native's "react" peer could not be found. Are you sure it exists at: ${EXPO_REACT_PATH}?`
68 | );
69 | }
70 |
71 | /**
72 | * Return a memoized function that checks for the existence of a
73 | * project level .babelrc file, and if it doesn't exist, reads the
74 | * default RN babelrc file and uses that.
75 | */
76 | const getBabelRC = (function() {
77 | let babelRC = null;
78 |
79 | return function _getBabelRC(projectRoot) {
80 | if (babelRC !== null) {
81 | return babelRC;
82 | }
83 |
84 | babelRC = { plugins: [] };
85 |
86 | // Let's look for the .babelrc in the project root.
87 | // In the future let's look into adding a command line option to specify
88 | // this location.
89 | let projectBabelRCPath;
90 | if (projectRoot) {
91 | projectBabelRCPath = path.resolve(projectRoot, '.babelrc');
92 | }
93 |
94 | // If a .babelrc file doesn't exist in the project,
95 | // use the Babel config provided with react-native.
96 | if (!projectBabelRCPath || !fs.existsSync(projectBabelRCPath)) {
97 | babelRC = {
98 | presets: [require('babel-preset-react-native')],
99 | plugins: [],
100 | };
101 |
102 | // Require the babel-preset's listed in the default babel config
103 | // $FlowFixMe: dynamic require can't be avoided
104 | babelRC.presets = babelRC.presets.map(preset => require('babel-preset-' + preset));
105 | babelRC.plugins = resolvePlugins(babelRC.plugins);
106 | } else {
107 | // if we find a .babelrc file we tell babel to use it
108 | babelRC.extends = projectBabelRCPath;
109 | }
110 |
111 | return babelRC;
112 | };
113 | })();
114 |
115 | /**
116 | * Given a filename and options, build a Babel
117 | * config object with the appropriate plugins.
118 | */
119 | function buildBabelConfig(filename, options) {
120 | const babelRC = getBabelRC(options.projectRoot);
121 |
122 | const extraConfig = {
123 | // [Expo] We add the module resolver plugin (as a preset) to make sure
124 | // we're looking up peer deps (like react-native and react) in the
125 | // right place
126 | presets: [...(babelRC.presets || []), buildModuleResolverPreset()],
127 | // [Expo] When running in universe, we don't want to disable
128 | // babelRC lookup for dependencies
129 | babelrc: false,
130 | code: false,
131 | filename,
132 | };
133 |
134 | let config = Object.assign({}, babelRC, extraConfig);
135 |
136 | // Add extra plugins
137 | const extraPlugins = [externalHelpersPlugin];
138 |
139 | var inlineRequires = options.inlineRequires;
140 | var blacklist = typeof inlineRequires === 'object' ? inlineRequires.blacklist : null;
141 | if (inlineRequires && !(blacklist && filename in blacklist)) {
142 | extraPlugins.push(inlineRequiresPlugin);
143 | }
144 |
145 | config.plugins = extraPlugins.concat(config.plugins);
146 |
147 | if (options.dev && options.hot) {
148 | const hmrConfig = makeHMRConfig(options, filename);
149 | config = Object.assign({}, config, hmrConfig);
150 | }
151 |
152 | return Object.assign({}, babelRC, config);
153 | }
154 |
155 | function transform({ filename, options, src }) {
156 | options = options || { platform: '', projectRoot: '', inlineRequires: false };
157 |
158 | const OLD_BABEL_ENV = process.env.BABEL_ENV;
159 | process.env.BABEL_ENV = options.dev ? 'development' : 'production';
160 |
161 | try {
162 | const babelConfig = buildBabelConfig(filename, options);
163 | const { ast, ignored } = babel.transform(src, babelConfig);
164 |
165 | if (ignored) {
166 | return {
167 | ast: null,
168 | code: src,
169 | filename,
170 | map: null,
171 | };
172 | } else {
173 | const result = generate(
174 | ast,
175 | {
176 | comments: false,
177 | compact: false,
178 | filename,
179 | retainLines: !!options.retainLines,
180 | sourceFileName: filename,
181 | sourceMaps: true,
182 | },
183 | src
184 | );
185 |
186 | return {
187 | ast,
188 | code: result.code,
189 | filename,
190 | map: options.generateSourceMaps ? result.map : result.rawMappings.map(compactMapping),
191 | };
192 | }
193 | } catch (e) {
194 | console.error(e);
195 | throw e;
196 | } finally {
197 | process.env.BABEL_ENV = OLD_BABEL_ENV;
198 | }
199 | }
200 |
201 | /**
202 | * [Expo] Returns an Expo-internal Babel preset for aliasing react-native and
203 | * react imports
204 | */
205 | function buildModuleResolverPreset() {
206 | return {
207 | plugins: [
208 | [
209 | require('babel-plugin-module-resolver').default,
210 | {
211 | alias: {
212 | react: EXPO_REACT_PATH,
213 | 'react-native': EXPO_REACT_NATIVE_PATH,
214 | },
215 | },
216 | ],
217 | ],
218 | };
219 | }
220 |
221 | function getCacheKey() {
222 | var key = crypto.createHash('md5');
223 | cacheKeyParts.forEach(part => key.update(part));
224 | return key.digest('hex');
225 | }
226 |
227 | module.exports = {
228 | transform,
229 | getCacheKey,
230 | };
231 |
--------------------------------------------------------------------------------
/api/GooglePoly.js:
--------------------------------------------------------------------------------
1 |
2 | import ExpoTHREE from 'expo-three';
3 | import AssetUtils from 'expo-asset-utils';
4 | import * as THREE from 'three';
5 | require("./../util/OBJLoader");
6 | require("./../util/MTLLoader");
7 |
8 | export default class GooglePoly {
9 |
10 | constructor(apiKey) {
11 | this.apiKey = apiKey;
12 | this.currentResults = [];
13 | this.nextPageToken = "";
14 | this.keywords = "";
15 | }
16 |
17 | // Returns a query URL based on the given data...
18 | static getQueryURL(apiKey, keywords, nextPageToken) {
19 | var baseURL = "https://poly.googleapis.com/v1/assets?";
20 |
21 | var url = baseURL + "key=" + apiKey;
22 | url += "&pageSize=10";
23 | url += "&maxComplexity=MEDIUM";
24 | url += "&format=OBJ";
25 | if (keywords) { url += "&keywords=" + encodeURIComponent(keywords); }
26 | if (nextPageToken) { url += "&pageToken=" + nextPageToken; }
27 | return url;
28 | }
29 |
30 | // Sets current search parameters and resets member variables...
31 | setSearchParams = (keywords) => {
32 | this.currentResults = [];
33 | this.nextPageToken = "";
34 | this.keywords = keywords;
35 | }
36 |
37 | // Returns the results of the current query...
38 | getSearchResults() {
39 | var url = GooglePoly.getQueryURL(this.apiKey, this.keywords, this.nextPageToken);
40 |
41 | return fetch(url)
42 | .then(function(response) { return response.json(); })
43 | .then(function(data) {
44 | this.currentResults = this.currentResults.concat(data.assets);
45 | this.nextPageToken = data.nextPageToken;
46 |
47 | return Promise.resolve(data.assets);
48 | }.bind(this));
49 | }
50 |
51 | // Returns a Three.js object
52 | static getThreeModel(objectData, success, failure) {
53 | if (!success) { success = function() { }; }
54 | if (!failure) { failure = function() { }; }
55 | if (!objectData) { failure("objectData is null"); return; }
56 |
57 | // Search for a format...
58 | var format = objectData.formats.find(format => { return format.formatType == "OBJ"; });
59 | if (format === undefined) { failure("No format found"); return; }
60 |
61 | // Search for a resource...
62 | var obj = format.root;
63 | var mtl = format.resources.find(resource => { return resource.url.endsWith("mtl"); });
64 | var tex = format.resources.find(resource => { return resource.url.endsWith("png"); });
65 | var path = obj.url.slice(0, obj.url.indexOf(obj.relativePath));
66 |
67 | // Load the MTL...
68 | var loader = new THREE.MTLLoader();
69 | loader.setCrossOrigin(true);
70 | loader.setTexturePath(path);
71 | loader.load(mtl.url, function(materials) {
72 |
73 | // Load the OBJ...
74 | loader = new THREE.OBJLoader();
75 | loader.setMaterials(materials);
76 | loader.load(obj.url, async function(object) {
77 |
78 | // If there is a texture, apply it...
79 | if (tex !== undefined) {
80 | var texUri = await AssetUtils.uriAsync(tex.url);
81 | var texture = new THREE.MeshBasicMaterial({ map: await ExpoTHREE.loadAsync(texUri) });
82 | object.traverse((child) => {
83 | if (child instanceof THREE.Mesh) {
84 | child.material = texture;
85 | }
86 | });
87 | }
88 |
89 | // Return the object...
90 | success(object);
91 | });
92 | });
93 | }
94 | }
--------------------------------------------------------------------------------
/api/registerForPushNotificationsAsync.js:
--------------------------------------------------------------------------------
1 | import { Constants, Permissions, Notifications } from 'expo';
2 |
3 | // Example server, implemented in Rails: https://git.io/vKHKv
4 | const PUSH_ENDPOINT = 'https://expo-push-server.herokuapp.com/tokens';
5 |
6 | export default (async function registerForPushNotificationsAsync() {
7 | // Remote notifications do not work in simulators, only on device
8 | if (!Constants.isDevice) {
9 | return;
10 | }
11 |
12 | // Android remote notification permissions are granted during the app
13 | // install, so this will only ask on iOS
14 | let { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
15 |
16 | // Stop here if the user did not grant permissions
17 | if (status !== 'granted') {
18 | return;
19 | }
20 |
21 | // Get the token that uniquely identifies this device
22 | let token = await Notifications.getExpoPushTokenAsync();
23 |
24 | // POST the token to our backend so we can use it to send pushes from there
25 | return fetch(PUSH_ENDPOINT, {
26 | method: 'POST',
27 | headers: {
28 | Accept: 'application/json',
29 | 'Content-Type': 'application/json',
30 | },
31 | body: JSON.stringify({
32 | token: {
33 | value: token,
34 | },
35 | }),
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "poly-snap",
4 | "description": "A very interesting project.",
5 | "slug": "poly-snap",
6 | "privacy": "public",
7 | "sdkVersion": "25.0.0",
8 | "platforms": ["ios", "android"],
9 | "version": "1.0.0",
10 | "orientation": "portrait",
11 | "icon": "./assets/images/icon.png",
12 | "splash": {
13 | "image": "./assets/images/splash.png",
14 | "resizeMode": "contain",
15 | "backgroundColor": "#ffffff"
16 | },
17 | "ios": {
18 | "supportsTablet": true
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/assets/fonts/SpaceMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/fonts/SpaceMono-Regular.ttf
--------------------------------------------------------------------------------
/assets/images/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/images/icon.png
--------------------------------------------------------------------------------
/assets/images/robot-dev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/images/robot-dev.png
--------------------------------------------------------------------------------
/assets/images/robot-prod.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/images/robot-prod.png
--------------------------------------------------------------------------------
/assets/images/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/assets/images/splash.png
--------------------------------------------------------------------------------
/assets/objects/TurkeyObject.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"assets/6_2gGwLWWHN",
3 | "displayName":"Happy Turkey Day!",
4 | "authorName":"Zan Isgett",
5 | "description":"#thanksgiving #turkey #pilgrim #holiday",
6 | "createTime":"2017-11-06T21:08:08.975118Z",
7 | "updateTime":"2018-03-20T09:26:47.062381Z",
8 | "formats":[{"root":{"relativePath":"model.obj","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/2fjlcvDtM61/model.obj","contentType":"text/plain"},"resources":[{"relativePath":"materials.mtl","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/2fjlcvDtM61/materials.mtl","contentType":"text/plain"}],"formatComplexity":{"triangleCount":"2145"},"formatType":"OBJ"},{"root":{"relativePath":"model-triangulated.obj","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/a9hzk3RXgZ1/model-triangulated.obj","contentType":"text/plain"},"resources":[{"relativePath":"materials.mtl","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/a9hzk3RXgZ1/materials.mtl","contentType":"text/plain"}],"formatComplexity":{"triangleCount":"3724"},"formatType":"OBJ"},{"root":{"relativePath":"model.fbx","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/5yj8Kskb-QE/model.fbx","contentType":"application/octet-stream"},"formatComplexity":{},"formatType":"FBX"},{"root":{"relativePath":"model.gltf","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/5tDSnSpY5Nw/model.gltf","contentType":"model/gltf+json"},"resources":[{"relativePath":"model.bin","url":"https://poly.googleapis.com/downloads/6_2gGwLWWHN/5tDSnSpY5Nw/model.bin","contentType":"application/octet-stream"}],"formatComplexity":{"triangleCount":"3724"},"formatType":"GLTF"}],
9 | "thumbnail": {
10 | "relativePath":"aWLD8imIWmk.png",
11 | "url":"https://lh3.googleusercontent.com/WK4clNLrfVxwv2pljofKuuDKlpYqZkF6uHLZrjqmTFBMEFGm-lDYUzwBbltCMbyWmA",
12 | "contentType":"image/png"
13 | },
14 | "license":"CREATIVE_COMMONS_BY","visibility":"PUBLIC","isCurated":true,"presentationParams":{"orientingRotation":{"w":1},"colorSpace":"LINEAR"}
15 | }
--------------------------------------------------------------------------------
/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ProProgramming101/PolySnap/07769c15e17d9a9c68c6be51d029d48fe7fa2a93/components/.DS_Store
--------------------------------------------------------------------------------
/components/AppComponents/GooglePolyAsset.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native';
4 |
5 | export default class GooglePolyAsset extends React.Component {
6 |
7 | static defaultProps = {
8 | asset: { },
9 | onPress: function(asset) { },
10 | }
11 |
12 | render() {
13 | return (
14 | this.props.onPress(this.props.asset)}>
15 |
16 | {this.props.asset.displayName}
17 | {this.props.asset.authorName}
18 |
19 | );
20 | }
21 | }
22 |
23 | const styles = StyleSheet.create({
24 | container: { padding: 10, },
25 | thumbnail: { width: 150, height: 150, borderRadius: 10, },
26 | displayName: { fontWeight: "bold", textAlign: "center", width: 150, },
27 | authorName: { textAlign: "center", width: 150, },
28 | })
--------------------------------------------------------------------------------
/components/AppComponents/SearchableGooglePolyAssetList.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { StyleSheet, View, Text, TextInput, ScrollView, Button } from 'react-native';
4 | import { MaterialCommunityIcons as Icon } from 'react-native-vector-icons';
5 | import GooglePolyAsset from './GooglePolyAsset';
6 |
7 | export default class SearchableGooglePolyAssetList extends React.Component {
8 |
9 | static defaultProps = {
10 | googlePoly: { },
11 | onCancelPress: function() { },
12 | onAssetPress: function(asset) { },
13 | }
14 |
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | searchQuery: "",
19 | currentResults: this.props.googlePoly.currentResults,
20 | }
21 | }
22 |
23 | onSearchPress = () => {
24 | var keywords = this.state.searchQuery;
25 | this.props.googlePoly.setSearchParams(keywords);
26 |
27 | this.props.googlePoly.getSearchResults().then(function(assets) {
28 | this.setState({currentResults: this.props.googlePoly.currentResults });
29 | }.bind(this));
30 | }
31 |
32 | onLoadMorePress = () => {
33 | this.props.googlePoly.getSearchResults().then(function(assets) {
34 | this.setState({currentResults: this.props.googlePoly.currentResults });
35 | }.bind(this));
36 | }
37 |
38 | onSearchChangeText = (text) => {
39 | this.setState({searchQuery: text});
40 | }
41 |
42 | renderSearchInput() {
43 | return (
44 |
45 |
46 |
47 |
54 |
55 |
56 |
57 | );
58 | }
59 |
60 | renderCurrentResults() {
61 | if (this.state.currentResults.length == 0) {
62 | return (
63 |
64 | No results
65 |
66 | );
67 | }
68 |
69 | var results = [];
70 | for (var i = 0; i < this.state.currentResults.length; i += 2) {
71 | if (i == this.state.currentResults.length - 1) {
72 | results.push(
73 |
78 | );
79 | break;
80 | }
81 |
82 | results.push(
83 |
84 |
89 |
94 |
95 | );
96 | }
97 |
98 | return (
99 |
100 | {results}
101 |
102 | )
103 | }
104 |
105 | renderLoadMoreButton() {
106 | return (!this.props.googlePoly.nextPageToken)
107 | ?
108 | : ;
109 | }
110 |
111 | render() {
112 | return (
113 |
114 | {this.renderSearchInput()}
115 |
116 | {this.renderCurrentResults()}
117 | {this.renderLoadMoreButton()}
118 |
119 |
120 | );
121 | }
122 |
123 | }
124 |
125 | const styles = StyleSheet.create({
126 | searchContainer: {
127 | flex: 1,
128 | flexDirection: "row",
129 | alignItems: "center",
130 | borderWidth: 1,
131 | borderRadius: 10,
132 | borderColor: "#DDDDDD",
133 | },
134 |
135 | searchTextInput: {
136 | flex: 1,
137 | height:40,
138 | },
139 |
140 | noResultsText: {
141 | fontSize: 18,
142 | fontStyle: "italic",
143 | paddingTop: 50,
144 | }
145 | });
--------------------------------------------------------------------------------
/components/AppComponents/index.js:
--------------------------------------------------------------------------------
1 |
2 | import GooglePolyAsset from './GooglePolyAsset';
3 | import SearchableGooglePolyAssetList from './SearchableGooglePolyAssetList';
4 |
5 | export { GooglePolyAsset, SearchableGooglePolyAssetList };
--------------------------------------------------------------------------------
/components/__tests__/StyledText-test.js:
--------------------------------------------------------------------------------
1 | import 'react-native';
2 | import React from 'react';
3 | import { MonoText } from '../StyledText';
4 | import renderer from 'react-test-renderer';
5 |
6 | it('renders correctly', () => {
7 | const tree = renderer.create(Snapshot test!).toJSON();
8 |
9 | expect(tree).toMatchSnapshot();
10 | });
11 |
--------------------------------------------------------------------------------
/constants/Colors.js:
--------------------------------------------------------------------------------
1 | const tintColor = '#2f95dc';
2 |
3 | export default {
4 | tintColor,
5 | tabIconDefault: '#ccc',
6 | tabIconSelected: tintColor,
7 | tabBar: '#fefefe',
8 | errorBackground: 'red',
9 | errorText: '#fff',
10 | warningBackground: '#EAEB5E',
11 | warningText: '#666804',
12 | noticeBackground: tintColor,
13 | noticeText: '#fff',
14 | };
15 |
--------------------------------------------------------------------------------
/navigation/MainTabNavigator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Platform } from 'react-native';
3 | import { Ionicons } from '@expo/vector-icons';
4 | import { TabNavigator, StackNavigator, TabBarBottom } from 'react-navigation';
5 | import Colors from '../constants/Colors';
6 | import HomeScreen from '../screens/HomeScreen';
7 |
8 | export default StackNavigator({
9 | Home: { screen: HomeScreen, },
10 | }, { }
11 | );
12 |
--------------------------------------------------------------------------------
/navigation/RootNavigation.js:
--------------------------------------------------------------------------------
1 | import { Notifications } from 'expo';
2 | import React from 'react';
3 | import { StackNavigator } from 'react-navigation';
4 |
5 | import MainTabNavigator from './MainTabNavigator';
6 | import registerForPushNotificationsAsync from '../api/registerForPushNotificationsAsync';
7 |
8 | const RootStackNavigator = StackNavigator(
9 | {
10 | Main: {
11 | screen: MainTabNavigator,
12 | },
13 | },
14 | {
15 | navigationOptions: () => ({
16 | headerTitleStyle: {
17 | fontWeight: 'normal',
18 | },
19 | }),
20 | }
21 | );
22 |
23 | export default class RootNavigator extends React.Component {
24 | componentDidMount() {
25 | this._notificationSubscription = this._registerForPushNotifications();
26 | }
27 |
28 | componentWillUnmount() {
29 | this._notificationSubscription && this._notificationSubscription.remove();
30 | }
31 |
32 | render() {
33 | return ;
34 | }
35 |
36 | _registerForPushNotifications() {
37 | // Send our push token over to our backend so we can receive notifications
38 | // You can comment the following line out if you want to stop receiving
39 | // a notification every time you open the app. Check out the source
40 | // for this function in api/registerForPushNotificationsAsync.js
41 | registerForPushNotificationsAsync();
42 |
43 | // Watch for incoming notifications
44 | this._notificationSubscription = Notifications.addListener(this._handleNotification);
45 | }
46 |
47 | _handleNotification = ({ origin, data }) => {
48 | console.log(`Push notification ${origin} with data: ${JSON.stringify(data)}`);
49 | };
50 | }
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "private": true,
4 | "scripts": {
5 | "test": "node ./node_modules/jest/bin/jest.js --watchAll"
6 | },
7 | "jest": {
8 | "preset": "jest-expo"
9 | },
10 | "dependencies": {
11 | "@expo/samples": "2.1.1",
12 | "expo": "^25.0.0",
13 | "expo-asset-utils": "0.0.4-alpha.2",
14 | "expo-graphics": "0.0.3",
15 | "expo-three": "^2.0.7",
16 | "react": "16.2.0",
17 | "react-native": "https://github.com/expo/react-native/archive/sdk-25.0.0.tar.gz",
18 | "react-navigation": "^1.0.0-beta.27",
19 | "three": "^0.88.0"
20 | },
21 | "devDependencies": {
22 | "jest-expo": "^25.0.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/screens/HomeScreen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ScrollView, StyleSheet, Text, View, Image, Button, TextInput, Modal } from 'react-native';
3 | import Expo from 'expo';
4 | import ExpoTHREE, { THREE } from 'expo-three';
5 | import ExpoGraphics from 'expo-graphics';
6 | import { MaterialCommunityIcons as Icon } from 'react-native-vector-icons';
7 | import GooglePoly from './../api/GooglePoly';
8 | import ApiKeys from './../constants/ApiKeys';
9 | import TurkeyObject from './../assets/objects/TurkeyObject.json';
10 | import { SearchableGooglePolyAssetList } from './../components/AppComponents';
11 |
12 | console.disableYellowBox = true;
13 |
14 | export default class HomeScreen extends React.Component {
15 | static navigationOptions = {
16 | header: null,
17 | };
18 |
19 | constructor(props) {
20 | super(props);
21 |
22 | this.googlePoly = new GooglePoly(ApiKeys.GooglePoly);
23 | this.state = {
24 | searchModalVisible: false,
25 | currentAsset: TurkeyObject,
26 | }
27 | }
28 |
29 | onContextCreate = async ({gl, scale, width, height, arSession}) => {
30 | // Initialize renderer...
31 | this.renderer = ExpoTHREE.createRenderer({gl});
32 | this.renderer.setPixelRatio(scale);
33 | this.renderer.setSize(width, height);
34 |
35 | // Initialize scene...
36 | this.scene = new THREE.Scene();
37 | this.scene.background = ExpoTHREE.createARBackgroundTexture(arSession, this.renderer);
38 |
39 | // Initialize camera...
40 | this.camera = ExpoTHREE.createARCamera(arSession, width / scale, height / scale, 0.01, 1000);
41 |
42 | // Initialize lighting...
43 | var ambientLight = new THREE.AmbientLight(0xaaaaaa);
44 | this.scene.add(ambientLight);
45 | }
46 |
47 | onRender = (delta) => {
48 |
49 | // Rotate the object...
50 | if (this.threeModel) {
51 | this.threeModel.rotation.x += 2 * delta;
52 | this.threeModel.rotation.y += 1.5 * delta;
53 | }
54 |
55 | // Render...
56 | this.renderer.render(this.scene, this.camera);
57 | }
58 |
59 | onAddObjectPress = () => {
60 | // Remove the current object...
61 | this.onRemoveObjectPress();
62 |
63 | // Add the current object...
64 | GooglePoly.getThreeModel(this.state.currentAsset, function(object) {
65 | this.threeModel = object;
66 | ExpoTHREE.utils.scaleLongestSideToSize(object, 0.75);
67 | object.position.z = -3;
68 | this.scene.add(object);
69 | }.bind(this), function(error) {
70 | console.log(error);
71 | });
72 | }
73 |
74 | onRemoveObjectPress = () => {
75 | if (this.threeModel) {
76 | this.scene.remove(this.threeModel);
77 | }
78 | }
79 |
80 | onCancelPress = () => {
81 | this.setState({searchModalVisible: false});
82 | }
83 |
84 | onAssetPress = (asset) => {
85 | this.setState({currentAsset: asset});
86 | this.setState({searchModalVisible: false});
87 | }
88 |
89 | onSearchModalPress = () => {
90 | this.setState({searchModalVisible: true});
91 | }
92 |
93 | render() {
94 |
95 | return (
96 |
97 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
117 |
118 |
119 |
120 | );
121 | }
122 |
123 | }
124 |
125 | const styles = StyleSheet.create({
126 |
127 | });
128 |
--------------------------------------------------------------------------------
/util/MTLLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Loads a Wavefront .mtl file specifying materials
3 | *
4 | * @author angelxuanchang
5 | */
6 |
7 | THREE.MTLLoader = function ( manager ) {
8 |
9 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
10 |
11 | };
12 |
13 | THREE.MTLLoader.prototype = {
14 |
15 | constructor: THREE.MTLLoader,
16 |
17 | /**
18 | * Loads and parses a MTL asset from a URL.
19 | *
20 | * @param {String} url - URL to the MTL file.
21 | * @param {Function} [onLoad] - Callback invoked with the loaded object.
22 | * @param {Function} [onProgress] - Callback for download progress.
23 | * @param {Function} [onError] - Callback for download errors.
24 | *
25 | * @see setPath setTexturePath
26 | *
27 | * @note In order for relative texture references to resolve correctly
28 | * you must call setPath and/or setTexturePath explicitly prior to load.
29 | */
30 | load: function ( url, onLoad, onProgress, onError ) {
31 |
32 | var scope = this;
33 |
34 | var loader = new THREE.FileLoader( this.manager );
35 | loader.setPath( this.path );
36 | loader.load( url, function ( text ) {
37 |
38 | onLoad( scope.parse( text ) );
39 |
40 | }, onProgress, onError );
41 |
42 | },
43 |
44 | /**
45 | * Set base path for resolving references.
46 | * If set this path will be prepended to each loaded and found reference.
47 | *
48 | * @see setTexturePath
49 | * @param {String} path
50 | *
51 | * @example
52 | * mtlLoader.setPath( 'assets/obj/' );
53 | * mtlLoader.load( 'my.mtl', ... );
54 | */
55 | setPath: function ( path ) {
56 |
57 | this.path = path;
58 |
59 | },
60 |
61 | /**
62 | * Set base path for resolving texture references.
63 | * If set this path will be prepended found texture reference.
64 | * If not set and setPath is, it will be used as texture base path.
65 | *
66 | * @see setPath
67 | * @param {String} path
68 | *
69 | * @example
70 | * mtlLoader.setPath( 'assets/obj/' );
71 | * mtlLoader.setTexturePath( 'assets/textures/' );
72 | * mtlLoader.load( 'my.mtl', ... );
73 | */
74 | setTexturePath: function ( path ) {
75 |
76 | this.texturePath = path;
77 |
78 | },
79 |
80 | setBaseUrl: function ( path ) {
81 |
82 | console.warn( 'THREE.MTLLoader: .setBaseUrl() is deprecated. Use .setTexturePath( path ) for texture path or .setPath( path ) for general base path instead.' );
83 |
84 | this.setTexturePath( path );
85 |
86 | },
87 |
88 | setCrossOrigin: function ( value ) {
89 |
90 | this.crossOrigin = value;
91 |
92 | },
93 |
94 | setMaterialOptions: function ( value ) {
95 |
96 | this.materialOptions = value;
97 |
98 | },
99 |
100 | /**
101 | * Parses a MTL file.
102 | *
103 | * @param {String} text - Content of MTL file
104 | * @return {THREE.MTLLoader.MaterialCreator}
105 | *
106 | * @see setPath setTexturePath
107 | *
108 | * @note In order for relative texture references to resolve correctly
109 | * you must call setPath and/or setTexturePath explicitly prior to parse.
110 | */
111 | parse: function ( text ) {
112 |
113 | var lines = text.split( '\n' );
114 | var info = {};
115 | var delimiter_pattern = /\s+/;
116 | var materialsInfo = {};
117 |
118 | for ( var i = 0; i < lines.length; i ++ ) {
119 |
120 | var line = lines[ i ];
121 | line = line.trim();
122 |
123 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
124 |
125 | // Blank line or comment ignore
126 | continue;
127 |
128 | }
129 |
130 | var pos = line.indexOf( ' ' );
131 |
132 | var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
133 | key = key.toLowerCase();
134 |
135 | var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
136 | value = value.trim();
137 |
138 | if ( key === 'newmtl' ) {
139 |
140 | // New material
141 |
142 | info = { name: value };
143 | materialsInfo[ value ] = info;
144 |
145 | } else if ( info ) {
146 |
147 | if ( key === 'ka' || key === 'kd' || key === 'ks' ) {
148 |
149 | var ss = value.split( delimiter_pattern, 3 );
150 | info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
151 |
152 | } else {
153 |
154 | info[ key ] = value;
155 |
156 | }
157 |
158 | }
159 |
160 | }
161 |
162 | var materialCreator = new THREE.MTLLoader.MaterialCreator( this.texturePath || this.path, this.materialOptions );
163 | materialCreator.setCrossOrigin( this.crossOrigin );
164 | materialCreator.setManager( this.manager );
165 | materialCreator.setMaterials( materialsInfo );
166 | return materialCreator;
167 |
168 | }
169 |
170 | };
171 |
172 | /**
173 | * Create a new THREE-MTLLoader.MaterialCreator
174 | * @param baseUrl - Url relative to which textures are loaded
175 | * @param options - Set of options on how to construct the materials
176 | * side: Which side to apply the material
177 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
178 | * wrap: What type of wrapping to apply for textures
179 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
180 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
181 | * Default: false, assumed to be already normalized
182 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
183 | * Default: false
184 | * @constructor
185 | */
186 |
187 | THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {
188 |
189 | this.baseUrl = baseUrl || '';
190 | this.options = options;
191 | this.materialsInfo = {};
192 | this.materials = {};
193 | this.materialsArray = [];
194 | this.nameLookup = {};
195 |
196 | this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;
197 | this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;
198 |
199 | };
200 |
201 | THREE.MTLLoader.MaterialCreator.prototype = {
202 |
203 | constructor: THREE.MTLLoader.MaterialCreator,
204 |
205 | crossOrigin: 'Anonymous',
206 |
207 | setCrossOrigin: function ( value ) {
208 |
209 | this.crossOrigin = value;
210 |
211 | },
212 |
213 | setManager: function ( value ) {
214 |
215 | this.manager = value;
216 |
217 | },
218 |
219 | setMaterials: function ( materialsInfo ) {
220 |
221 | this.materialsInfo = this.convert( materialsInfo );
222 | this.materials = {};
223 | this.materialsArray = [];
224 | this.nameLookup = {};
225 |
226 | },
227 |
228 | convert: function ( materialsInfo ) {
229 |
230 | if ( ! this.options ) return materialsInfo;
231 |
232 | var converted = {};
233 |
234 | for ( var mn in materialsInfo ) {
235 |
236 | // Convert materials info into normalized form based on options
237 |
238 | var mat = materialsInfo[ mn ];
239 |
240 | var covmat = {};
241 |
242 | converted[ mn ] = covmat;
243 |
244 | for ( var prop in mat ) {
245 |
246 | var save = true;
247 | var value = mat[ prop ];
248 | var lprop = prop.toLowerCase();
249 |
250 | switch ( lprop ) {
251 |
252 | case 'kd':
253 | case 'ka':
254 | case 'ks':
255 |
256 | // Diffuse color (color under white light) using RGB values
257 |
258 | if ( this.options && this.options.normalizeRGB ) {
259 |
260 | value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
261 |
262 | }
263 |
264 | if ( this.options && this.options.ignoreZeroRGBs ) {
265 |
266 | if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
267 |
268 | // ignore
269 |
270 | save = false;
271 |
272 | }
273 |
274 | }
275 |
276 | break;
277 |
278 | default:
279 |
280 | break;
281 |
282 | }
283 |
284 | if ( save ) {
285 |
286 | covmat[ lprop ] = value;
287 |
288 | }
289 |
290 | }
291 |
292 | }
293 |
294 | return converted;
295 |
296 | },
297 |
298 | preload: function () {
299 |
300 | for ( var mn in this.materialsInfo ) {
301 |
302 | this.create( mn );
303 |
304 | }
305 |
306 | },
307 |
308 | getIndex: function ( materialName ) {
309 |
310 | return this.nameLookup[ materialName ];
311 |
312 | },
313 |
314 | getAsArray: function () {
315 |
316 | var index = 0;
317 |
318 | for ( var mn in this.materialsInfo ) {
319 |
320 | this.materialsArray[ index ] = this.create( mn );
321 | this.nameLookup[ mn ] = index;
322 | index ++;
323 |
324 | }
325 |
326 | return this.materialsArray;
327 |
328 | },
329 |
330 | create: function ( materialName ) {
331 |
332 | if ( this.materials[ materialName ] === undefined ) {
333 |
334 | this.createMaterial_( materialName );
335 |
336 | }
337 |
338 | return this.materials[ materialName ];
339 |
340 | },
341 |
342 | createMaterial_: function ( materialName ) {
343 |
344 | // Create material
345 |
346 | var scope = this;
347 | var mat = this.materialsInfo[ materialName ];
348 | var params = {
349 |
350 | name: materialName,
351 | side: this.side
352 |
353 | };
354 |
355 | function resolveURL( baseUrl, url ) {
356 |
357 | if ( typeof url !== 'string' || url === '' )
358 | return '';
359 |
360 | // Absolute URL
361 | if ( /^https?:\/\//i.test( url ) ) return url;
362 |
363 | return baseUrl + url;
364 |
365 | }
366 |
367 | function setMapForType( mapType, value ) {
368 |
369 | if ( params[ mapType ] ) return; // Keep the first encountered texture
370 |
371 | var texParams = scope.getTextureParams( value, params );
372 | var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
373 |
374 | map.repeat.copy( texParams.scale );
375 | map.offset.copy( texParams.offset );
376 |
377 | map.wrapS = scope.wrap;
378 | map.wrapT = scope.wrap;
379 |
380 | params[ mapType ] = map;
381 |
382 | }
383 |
384 | for ( var prop in mat ) {
385 |
386 | var value = mat[ prop ];
387 | var n;
388 |
389 | if ( value === '' ) continue;
390 |
391 | switch ( prop.toLowerCase() ) {
392 |
393 | // Ns is material specular exponent
394 |
395 | case 'kd':
396 |
397 | // Diffuse color (color under white light) using RGB values
398 |
399 | params.color = new THREE.Color().fromArray( value );
400 |
401 | break;
402 |
403 | case 'ks':
404 |
405 | // Specular color (color when light is reflected from shiny surface) using RGB values
406 | params.specular = new THREE.Color().fromArray( value );
407 |
408 | break;
409 |
410 | case 'map_kd':
411 |
412 | // Diffuse texture map
413 |
414 | setMapForType( "map", value );
415 |
416 | break;
417 |
418 | case 'map_ks':
419 |
420 | // Specular map
421 |
422 | setMapForType( "specularMap", value );
423 |
424 | break;
425 |
426 | case 'norm':
427 |
428 | setMapForType( "normalMap", value );
429 |
430 | break;
431 |
432 | case 'map_bump':
433 | case 'bump':
434 |
435 | // Bump texture map
436 |
437 | setMapForType( "bumpMap", value );
438 |
439 | break;
440 |
441 | case 'ns':
442 |
443 | // The specular exponent (defines the focus of the specular highlight)
444 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
445 |
446 | params.shininess = parseFloat( value );
447 |
448 | break;
449 |
450 | case 'd':
451 | n = parseFloat( value );
452 |
453 | if ( n < 1 ) {
454 |
455 | params.opacity = n;
456 | params.transparent = true;
457 |
458 | }
459 |
460 | break;
461 |
462 | case 'tr':
463 | n = parseFloat( value );
464 |
465 | if ( this.options && this.options.invertTrProperty ) n = 1 - n;
466 |
467 | if ( n < 1 ) {
468 |
469 | params.opacity = n;
470 | params.transparent = true;
471 |
472 | }
473 |
474 | break;
475 |
476 | default:
477 | break;
478 |
479 | }
480 |
481 | }
482 |
483 | this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
484 | return this.materials[ materialName ];
485 |
486 | },
487 |
488 | getTextureParams: function ( value, matParams ) {
489 |
490 | var texParams = {
491 |
492 | scale: new THREE.Vector2( 1, 1 ),
493 | offset: new THREE.Vector2( 0, 0 )
494 |
495 | };
496 |
497 | var items = value.split( /\s+/ );
498 | var pos;
499 |
500 | pos = items.indexOf( '-bm' );
501 |
502 | if ( pos >= 0 ) {
503 |
504 | matParams.bumpScale = parseFloat( items[ pos + 1 ] );
505 | items.splice( pos, 2 );
506 |
507 | }
508 |
509 | pos = items.indexOf( '-s' );
510 |
511 | if ( pos >= 0 ) {
512 |
513 | texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
514 | items.splice( pos, 4 ); // we expect 3 parameters here!
515 |
516 | }
517 |
518 | pos = items.indexOf( '-o' );
519 |
520 | if ( pos >= 0 ) {
521 |
522 | texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
523 | items.splice( pos, 4 ); // we expect 3 parameters here!
524 |
525 | }
526 |
527 | texParams.url = items.join( ' ' ).trim();
528 | return texParams;
529 |
530 | },
531 |
532 | loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
533 |
534 | var texture;
535 | var loader = THREE.Loader.Handlers.get( url );
536 | var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
537 |
538 | if ( loader === null ) {
539 |
540 | loader = new THREE.TextureLoader( manager );
541 |
542 | }
543 |
544 | if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
545 | texture = loader.load( url, onLoad, onProgress, onError );
546 |
547 | if ( mapping !== undefined ) texture.mapping = mapping;
548 |
549 | return texture;
550 |
551 | }
552 |
553 | };
--------------------------------------------------------------------------------
/util/OBJLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 |
5 | THREE.OBJLoader = ( function () {
6 |
7 | // o object_name | g group_name
8 | var object_pattern = /^[og]\s*(.+)?/;
9 | // mtllib file_reference
10 | var material_library_pattern = /^mtllib /;
11 | // usemtl material_name
12 | var material_use_pattern = /^usemtl /;
13 |
14 | function ParserState() {
15 |
16 | var state = {
17 | objects: [],
18 | object: {},
19 |
20 | vertices: [],
21 | normals: [],
22 | colors: [],
23 | uvs: [],
24 |
25 | materialLibraries: [],
26 |
27 | startObject: function ( name, fromDeclaration ) {
28 |
29 | // If the current object (initial from reset) is not from a g/o declaration in the parsed
30 | // file. We need to use it for the first parsed g/o to keep things in sync.
31 | if ( this.object && this.object.fromDeclaration === false ) {
32 |
33 | this.object.name = name;
34 | this.object.fromDeclaration = ( fromDeclaration !== false );
35 | return;
36 |
37 | }
38 |
39 | var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );
40 |
41 | if ( this.object && typeof this.object._finalize === 'function' ) {
42 |
43 | this.object._finalize( true );
44 |
45 | }
46 |
47 | this.object = {
48 | name: name || '',
49 | fromDeclaration: ( fromDeclaration !== false ),
50 |
51 | geometry: {
52 | vertices: [],
53 | normals: [],
54 | colors: [],
55 | uvs: []
56 | },
57 | materials: [],
58 | smooth: true,
59 |
60 | startMaterial: function ( name, libraries ) {
61 |
62 | var previous = this._finalize( false );
63 |
64 | // New usemtl declaration overwrites an inherited material, except if faces were declared
65 | // after the material, then it must be preserved for proper MultiMaterial continuation.
66 | if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {
67 |
68 | this.materials.splice( previous.index, 1 );
69 |
70 | }
71 |
72 | var material = {
73 | index: this.materials.length,
74 | name: name || '',
75 | mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
76 | smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
77 | groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
78 | groupEnd: - 1,
79 | groupCount: - 1,
80 | inherited: false,
81 |
82 | clone: function ( index ) {
83 |
84 | var cloned = {
85 | index: ( typeof index === 'number' ? index : this.index ),
86 | name: this.name,
87 | mtllib: this.mtllib,
88 | smooth: this.smooth,
89 | groupStart: 0,
90 | groupEnd: - 1,
91 | groupCount: - 1,
92 | inherited: false
93 | };
94 | cloned.clone = this.clone.bind( cloned );
95 | return cloned;
96 |
97 | }
98 | };
99 |
100 | this.materials.push( material );
101 |
102 | return material;
103 |
104 | },
105 |
106 | currentMaterial: function () {
107 |
108 | if ( this.materials.length > 0 ) {
109 |
110 | return this.materials[ this.materials.length - 1 ];
111 |
112 | }
113 |
114 | return undefined;
115 |
116 | },
117 |
118 | _finalize: function ( end ) {
119 |
120 | var lastMultiMaterial = this.currentMaterial();
121 | if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {
122 |
123 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
124 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
125 | lastMultiMaterial.inherited = false;
126 |
127 | }
128 |
129 | // Ignore objects tail materials if no face declarations followed them before a new o/g started.
130 | if ( end && this.materials.length > 1 ) {
131 |
132 | for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) {
133 |
134 | if ( this.materials[ mi ].groupCount <= 0 ) {
135 |
136 | this.materials.splice( mi, 1 );
137 |
138 | }
139 |
140 | }
141 |
142 | }
143 |
144 | // Guarantee at least one empty material, this makes the creation later more straight forward.
145 | if ( end && this.materials.length === 0 ) {
146 |
147 | this.materials.push( {
148 | name: '',
149 | smooth: this.smooth
150 | } );
151 |
152 | }
153 |
154 | return lastMultiMaterial;
155 |
156 | }
157 | };
158 |
159 | // Inherit previous objects material.
160 | // Spec tells us that a declared material must be set to all objects until a new material is declared.
161 | // If a usemtl declaration is encountered while this new object is being parsed, it will
162 | // overwrite the inherited material. Exception being that there was already face declarations
163 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation.
164 |
165 | if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {
166 |
167 | var declared = previousMaterial.clone( 0 );
168 | declared.inherited = true;
169 | this.object.materials.push( declared );
170 |
171 | }
172 |
173 | this.objects.push( this.object );
174 |
175 | },
176 |
177 | finalize: function () {
178 |
179 | if ( this.object && typeof this.object._finalize === 'function' ) {
180 |
181 | this.object._finalize( true );
182 |
183 | }
184 |
185 | },
186 |
187 | parseVertexIndex: function ( value, len ) {
188 |
189 | var index = parseInt( value, 10 );
190 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
191 |
192 | },
193 |
194 | parseNormalIndex: function ( value, len ) {
195 |
196 | var index = parseInt( value, 10 );
197 | return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;
198 |
199 | },
200 |
201 | parseUVIndex: function ( value, len ) {
202 |
203 | var index = parseInt( value, 10 );
204 | return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;
205 |
206 | },
207 |
208 | addVertex: function ( a, b, c ) {
209 |
210 | var src = this.vertices;
211 | var dst = this.object.geometry.vertices;
212 |
213 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
214 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
215 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
216 |
217 | },
218 |
219 | addVertexPoint: function ( a ) {
220 |
221 | var src = this.vertices;
222 | var dst = this.object.geometry.vertices;
223 |
224 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
225 |
226 | },
227 |
228 | addVertexLine: function ( a ) {
229 |
230 | var src = this.vertices;
231 | var dst = this.object.geometry.vertices;
232 |
233 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
234 |
235 | },
236 |
237 | addNormal: function ( a, b, c ) {
238 |
239 | var src = this.normals;
240 | var dst = this.object.geometry.normals;
241 |
242 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
243 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
244 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
245 |
246 | },
247 |
248 | addColor: function ( a, b, c ) {
249 |
250 | var src = this.colors;
251 | var dst = this.object.geometry.colors;
252 |
253 | dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
254 | dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
255 | dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );
256 |
257 | },
258 |
259 | addUV: function ( a, b, c ) {
260 |
261 | var src = this.uvs;
262 | var dst = this.object.geometry.uvs;
263 |
264 | dst.push( src[ a + 0 ], src[ a + 1 ] );
265 | dst.push( src[ b + 0 ], src[ b + 1 ] );
266 | dst.push( src[ c + 0 ], src[ c + 1 ] );
267 |
268 | },
269 |
270 | addUVLine: function ( a ) {
271 |
272 | var src = this.uvs;
273 | var dst = this.object.geometry.uvs;
274 |
275 | dst.push( src[ a + 0 ], src[ a + 1 ] );
276 |
277 | },
278 |
279 | addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {
280 |
281 | var vLen = this.vertices.length;
282 |
283 | var ia = this.parseVertexIndex( a, vLen );
284 | var ib = this.parseVertexIndex( b, vLen );
285 | var ic = this.parseVertexIndex( c, vLen );
286 |
287 | this.addVertex( ia, ib, ic );
288 |
289 | if ( ua !== undefined && ua !== '' ) {
290 |
291 | var uvLen = this.uvs.length;
292 | ia = this.parseUVIndex( ua, uvLen );
293 | ib = this.parseUVIndex( ub, uvLen );
294 | ic = this.parseUVIndex( uc, uvLen );
295 | this.addUV( ia, ib, ic );
296 |
297 | }
298 |
299 | if ( na !== undefined && na !== '' ) {
300 |
301 | // Normals are many times the same. If so, skip function call and parseInt.
302 | var nLen = this.normals.length;
303 | ia = this.parseNormalIndex( na, nLen );
304 |
305 | ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
306 | ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );
307 |
308 | this.addNormal( ia, ib, ic );
309 |
310 | }
311 |
312 | if ( this.colors.length > 0 ) {
313 |
314 | this.addColor( ia, ib, ic );
315 |
316 | }
317 |
318 | },
319 |
320 | addPointGeometry: function ( vertices ) {
321 |
322 | this.object.geometry.type = 'Points';
323 |
324 | var vLen = this.vertices.length;
325 |
326 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
327 |
328 | this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) );
329 |
330 | }
331 |
332 | },
333 |
334 | addLineGeometry: function ( vertices, uvs ) {
335 |
336 | this.object.geometry.type = 'Line';
337 |
338 | var vLen = this.vertices.length;
339 | var uvLen = this.uvs.length;
340 |
341 | for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {
342 |
343 | this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );
344 |
345 | }
346 |
347 | for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {
348 |
349 | this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );
350 |
351 | }
352 |
353 | }
354 |
355 | };
356 |
357 | state.startObject( '', false );
358 |
359 | return state;
360 |
361 | }
362 |
363 | //
364 |
365 | function OBJLoader( manager ) {
366 |
367 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
368 |
369 | this.materials = null;
370 |
371 | }
372 |
373 | OBJLoader.prototype = {
374 |
375 | constructor: OBJLoader,
376 |
377 | load: function ( url, onLoad, onProgress, onError ) {
378 |
379 | var scope = this;
380 |
381 | var loader = new THREE.FileLoader( scope.manager );
382 | loader.setPath( this.path );
383 | loader.load( url, function ( text ) {
384 |
385 | onLoad( scope.parse( text ) );
386 |
387 | }, onProgress, onError );
388 |
389 | },
390 |
391 | setPath: function ( value ) {
392 |
393 | this.path = value;
394 |
395 | },
396 |
397 | setMaterials: function ( materials ) {
398 |
399 | this.materials = materials;
400 |
401 | return this;
402 |
403 | },
404 |
405 | parse: function ( text ) {
406 |
407 | console.time( 'OBJLoader' );
408 |
409 | var state = new ParserState();
410 |
411 | if ( text.indexOf( '\r\n' ) !== - 1 ) {
412 |
413 | // This is faster than String.split with regex that splits on both
414 | text = text.replace( /\r\n/g, '\n' );
415 |
416 | }
417 |
418 | if ( text.indexOf( '\\\n' ) !== - 1 ) {
419 |
420 | // join lines separated by a line continuation character (\)
421 | text = text.replace( /\\\n/g, '' );
422 |
423 | }
424 |
425 | var lines = text.split( '\n' );
426 | var line = '', lineFirstChar = '';
427 | var lineLength = 0;
428 | var result = [];
429 |
430 | // Faster to just trim left side of the line. Use if available.
431 | var trimLeft = ( typeof ''.trimLeft === 'function' );
432 |
433 | for ( var i = 0, l = lines.length; i < l; i ++ ) {
434 |
435 | line = lines[ i ];
436 |
437 | line = trimLeft ? line.trimLeft() : line.trim();
438 |
439 | lineLength = line.length;
440 |
441 | if ( lineLength === 0 ) continue;
442 |
443 | lineFirstChar = line.charAt( 0 );
444 |
445 | // @todo invoke passed in handler if any
446 | if ( lineFirstChar === '#' ) continue;
447 |
448 | if ( lineFirstChar === 'v' ) {
449 |
450 | var data = line.split( /\s+/ );
451 |
452 | switch ( data[ 0 ] ) {
453 |
454 | case 'v':
455 | state.vertices.push(
456 | parseFloat( data[ 1 ] ),
457 | parseFloat( data[ 2 ] ),
458 | parseFloat( data[ 3 ] )
459 | );
460 | if ( data.length === 8 ) {
461 |
462 | state.colors.push(
463 | parseFloat( data[ 4 ] ),
464 | parseFloat( data[ 5 ] ),
465 | parseFloat( data[ 6 ] )
466 |
467 | );
468 |
469 | }
470 | break;
471 | case 'vn':
472 | state.normals.push(
473 | parseFloat( data[ 1 ] ),
474 | parseFloat( data[ 2 ] ),
475 | parseFloat( data[ 3 ] )
476 | );
477 | break;
478 | case 'vt':
479 | state.uvs.push(
480 | parseFloat( data[ 1 ] ),
481 | parseFloat( data[ 2 ] )
482 | );
483 | break;
484 |
485 | }
486 |
487 | } else if ( lineFirstChar === 'f' ) {
488 |
489 | var lineData = line.substr( 1 ).trim();
490 | var vertexData = lineData.split( /\s+/ );
491 | var faceVertices = [];
492 |
493 | // Parse the face vertex data into an easy to work with format
494 |
495 | for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {
496 |
497 | var vertex = vertexData[ j ];
498 |
499 | if ( vertex.length > 0 ) {
500 |
501 | var vertexParts = vertex.split( '/' );
502 | faceVertices.push( vertexParts );
503 |
504 | }
505 |
506 | }
507 |
508 | // Draw an edge between the first vertex and all subsequent vertices to form an n-gon
509 |
510 | var v1 = faceVertices[ 0 ];
511 |
512 | for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {
513 |
514 | var v2 = faceVertices[ j ];
515 | var v3 = faceVertices[ j + 1 ];
516 |
517 | state.addFace(
518 | v1[ 0 ], v2[ 0 ], v3[ 0 ],
519 | v1[ 1 ], v2[ 1 ], v3[ 1 ],
520 | v1[ 2 ], v2[ 2 ], v3[ 2 ]
521 | );
522 |
523 | }
524 |
525 | } else if ( lineFirstChar === 'l' ) {
526 |
527 | var lineParts = line.substring( 1 ).trim().split( " " );
528 | var lineVertices = [], lineUVs = [];
529 |
530 | if ( line.indexOf( "/" ) === - 1 ) {
531 |
532 | lineVertices = lineParts;
533 |
534 | } else {
535 |
536 | for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {
537 |
538 | var parts = lineParts[ li ].split( "/" );
539 |
540 | if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] );
541 | if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] );
542 |
543 | }
544 |
545 | }
546 | state.addLineGeometry( lineVertices, lineUVs );
547 |
548 | } else if ( lineFirstChar === 'p' ) {
549 |
550 | var lineData = line.substr( 1 ).trim();
551 | var pointData = lineData.split( " " );
552 |
553 | state.addPointGeometry( pointData );
554 |
555 | } else if ( ( result = object_pattern.exec( line ) ) !== null ) {
556 |
557 | // o object_name
558 | // or
559 | // g group_name
560 |
561 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
562 | // var name = result[ 0 ].substr( 1 ).trim();
563 | var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 );
564 |
565 | state.startObject( name );
566 |
567 | } else if ( material_use_pattern.test( line ) ) {
568 |
569 | // material
570 |
571 | state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );
572 |
573 | } else if ( material_library_pattern.test( line ) ) {
574 |
575 | // mtl file
576 |
577 | state.materialLibraries.push( line.substring( 7 ).trim() );
578 |
579 | } else if ( lineFirstChar === 's' ) {
580 |
581 | result = line.split( ' ' );
582 |
583 | // smooth shading
584 |
585 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
586 | // but does not define a usemtl for each face set.
587 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
588 | // This requires some care to not create extra material on each smooth value for "normal" obj files.
589 | // where explicit usemtl defines geometry groups.
590 | // Example asset: examples/models/obj/cerberus/Cerberus.obj
591 |
592 | /*
593 | * http://paulbourke.net/dataformats/obj/
594 | * or
595 | * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
596 | *
597 | * From chapter "Grouping" Syntax explanation "s group_number":
598 | * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
599 | * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
600 | * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
601 | * than 0."
602 | */
603 | if ( result.length > 1 ) {
604 |
605 | var value = result[ 1 ].trim().toLowerCase();
606 | state.object.smooth = ( value !== '0' && value !== 'off' );
607 |
608 | } else {
609 |
610 | // ZBrush can produce "s" lines #11707
611 | state.object.smooth = true;
612 |
613 | }
614 | var material = state.object.currentMaterial();
615 | if ( material ) material.smooth = state.object.smooth;
616 |
617 | } else {
618 |
619 | // Handle null terminated files without exception
620 | if ( line === '\0' ) continue;
621 |
622 | throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );
623 |
624 | }
625 |
626 | }
627 |
628 | state.finalize();
629 |
630 | var container = new THREE.Group();
631 | container.materialLibraries = [].concat( state.materialLibraries );
632 |
633 | for ( var i = 0, l = state.objects.length; i < l; i ++ ) {
634 |
635 | var object = state.objects[ i ];
636 | var geometry = object.geometry;
637 | var materials = object.materials;
638 | var isLine = ( geometry.type === 'Line' );
639 | var isPoints = ( geometry.type === 'Points' );
640 | var hasVertexColors = false;
641 |
642 | // Skip o/g line declarations that did not follow with any faces
643 | if ( geometry.vertices.length === 0 ) continue;
644 |
645 | var buffergeometry = new THREE.BufferGeometry();
646 |
647 | buffergeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) );
648 |
649 | if ( geometry.normals.length > 0 ) {
650 |
651 | buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) );
652 |
653 | } else {
654 |
655 | buffergeometry.computeVertexNormals();
656 |
657 | }
658 |
659 | if ( geometry.colors.length > 0 ) {
660 |
661 | hasVertexColors = true;
662 | buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) );
663 |
664 | }
665 |
666 | if ( geometry.uvs.length > 0 ) {
667 |
668 | buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) );
669 |
670 | }
671 |
672 | // Create materials
673 |
674 | var createdMaterials = [];
675 |
676 | for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
677 |
678 | var sourceMaterial = materials[ mi ];
679 | var material = undefined;
680 |
681 | if ( this.materials !== null ) {
682 |
683 | material = this.materials.create( sourceMaterial.name );
684 |
685 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
686 | if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {
687 |
688 | var materialLine = new THREE.LineBasicMaterial();
689 | materialLine.copy( material );
690 | materialLine.lights = false; // TOFIX
691 | material = materialLine;
692 |
693 | } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) {
694 |
695 | var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } );
696 | materialLine.copy( material );
697 | material = materialPoints;
698 |
699 | }
700 |
701 | }
702 |
703 | if ( ! material ) {
704 |
705 | if ( isLine ) {
706 |
707 | material = new THREE.LineBasicMaterial();
708 |
709 | } else if ( isPoints ) {
710 |
711 | material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } );
712 |
713 | } else {
714 |
715 | material = new THREE.MeshPhongMaterial();
716 |
717 | }
718 |
719 | material.name = sourceMaterial.name;
720 |
721 | }
722 |
723 | material.flatShading = sourceMaterial.smooth ? false : true;
724 | material.vertexColors = hasVertexColors ? THREE.VertexColors : THREE.NoColors;
725 |
726 | createdMaterials.push( material );
727 |
728 | }
729 |
730 | // Create mesh
731 |
732 | var mesh;
733 |
734 | if ( createdMaterials.length > 1 ) {
735 |
736 | for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {
737 |
738 | var sourceMaterial = materials[ mi ];
739 | buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );
740 |
741 | }
742 |
743 | if ( isLine ) {
744 |
745 | mesh = new THREE.LineSegments( buffergeometry, createdMaterials );
746 |
747 | } else if ( isPoints ) {
748 |
749 | mesh = new THREE.Points( buffergeometry, createdMaterials );
750 |
751 | } else {
752 |
753 | mesh = new THREE.Mesh( buffergeometry, createdMaterials );
754 |
755 | }
756 |
757 | } else {
758 |
759 | if ( isLine ) {
760 |
761 | mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] );
762 |
763 | } else if ( isPoints ) {
764 |
765 | mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] );
766 |
767 | } else {
768 |
769 | mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] );
770 |
771 | }
772 |
773 | }
774 |
775 | mesh.name = object.name;
776 |
777 | container.add( mesh );
778 |
779 | }
780 |
781 | console.timeEnd( 'OBJLoader' );
782 |
783 | return container;
784 |
785 | }
786 |
787 | };
788 |
789 | return OBJLoader;
790 |
791 | } )();
--------------------------------------------------------------------------------