├── pages
├── cube
│ ├── index.wxss
│ ├── index.json
│ ├── pikachu.png
│ ├── index.wxml
│ └── index.js
├── glb
│ ├── index.wxss
│ ├── index.json
│ ├── index.wxml
│ └── index.js
├── gltf
│ ├── index.wxss
│ ├── index.json
│ ├── index.wxml
│ ├── index.js
│ └── loadgLTF.js
├── ngltf
│ ├── index.wxss
│ ├── index.json
│ ├── index.wxml
│ ├── index.js
│ └── render.js
├── trackcontrol
│ ├── index.wxss
│ ├── index.json
│ ├── pikachu.png
│ ├── index.wxml
│ └── index.js
├── index
│ ├── index.json
│ ├── index.js
│ ├── index.wxss
│ └── index.wxml
├── obj
│ ├── index.wxss
│ ├── index.json
│ ├── index.wxml
│ ├── index.js
│ └── loadObj.js
├── primitives
│ ├── index.wxss
│ ├── index.json
│ ├── index.wxml
│ ├── index.js
│ └── primitives.js
├── raycaster
│ ├── index.wxss
│ ├── index.json
│ ├── index.wxml
│ └── index.js
├── animation
│ ├── index.wxss
│ ├── index.json
│ ├── index.wxml
│ ├── index.js
│ └── animate.js
└── logs
│ ├── logs.json
│ ├── logs.wxss
│ ├── logs.wxml
│ └── logs.js
├── app.js
├── demo
├── qrcode.jpg
├── Screenshot_2019-10-22-11-01-18-748_com.tencent.mm.png
├── Screenshot_2019-10-22-11-03-37-095_com.tencent.mm.png
├── Screenshot_2019-10-22-11-03-47-236_com.tencent.mm.png
├── Screenshot_2019-10-22-11-04-02-587_com.tencent.mm.png
├── Screenshot_2019-10-22-11-04-32-720_com.tencent.mm.png
├── Screenshot_2019-10-24-10-43-43-123_com.tencent.mm.png
├── Screenshot_2019-10-28-19-13-24-007_com.tencent.mm.png
├── Screenshot_2019-10-28-20-01-20-469_com.tencent.mm.png
└── Screenshot_2019-10-30-00-14-41-017_com.tencent.mm.png
├── sitemap.json
├── app.wxss
├── utils
├── util.js
└── ResourceTracker.js
├── app.json
├── LICENSE
├── project.config.json
├── README.md
└── jsm
├── loaders
├── EquirectangularToCubeGenerator.js
├── DDSLoader.js
├── MTLLoader.js
├── STLLoader.js
├── RGBELoader.js
└── OBJLoader.js
├── pmrem
├── PMREMCubeUVPacker.js
└── PMREMGenerator.js
├── utils
└── SkeletonUtils.js
└── controls
└── TrackballControls.js
/pages/cube/index.wxss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/glb/index.wxss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/gltf/index.wxss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/ngltf/index.wxss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/trackcontrol/index.wxss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pages/index/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {}
3 | }
--------------------------------------------------------------------------------
/pages/obj/index.wxss:
--------------------------------------------------------------------------------
1 |
2 | #c {
3 | width: 100%;
4 | height: 100%;
5 | }
--------------------------------------------------------------------------------
/pages/primitives/index.wxss:
--------------------------------------------------------------------------------
1 |
2 | #c {
3 | width: 100%;
4 | height: 100%;
5 | }
--------------------------------------------------------------------------------
/pages/raycaster/index.wxss:
--------------------------------------------------------------------------------
1 |
2 | #c {
3 | width: 100%;
4 | height: 100%;
5 | }
--------------------------------------------------------------------------------
/pages/animation/index.wxss:
--------------------------------------------------------------------------------
1 |
2 | #c {
3 | width: 100%;
4 | height: 100%;
5 | }
--------------------------------------------------------------------------------
/pages/cube/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/pages/glb/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/pages/gltf/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/pages/ngltf/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/pages/obj/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | App({
2 | onLaunch: function () {
3 | // wx.THREE = THREE
4 | }
5 | })
6 |
--------------------------------------------------------------------------------
/pages/animation/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/pages/primitives/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/pages/raycaster/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/pages/trackcontrol/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "usingComponents": {},
3 | "disableScroll": true
4 | }
--------------------------------------------------------------------------------
/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "查看启动日志",
3 | "usingComponents": {}
4 | }
--------------------------------------------------------------------------------
/demo/qrcode.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/qrcode.jpg
--------------------------------------------------------------------------------
/pages/cube/pikachu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/pages/cube/pikachu.png
--------------------------------------------------------------------------------
/pages/primitives/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pages/trackcontrol/pikachu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/pages/trackcontrol/pikachu.png
--------------------------------------------------------------------------------
/pages/glb/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | .log-list {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 40rpx;
5 | }
6 | .log-item {
7 | margin: 10rpx;
8 | }
9 |
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-22-11-01-18-748_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-22-11-01-18-748_com.tencent.mm.png
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-22-11-03-37-095_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-22-11-03-37-095_com.tencent.mm.png
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-22-11-03-47-236_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-22-11-03-47-236_com.tencent.mm.png
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-22-11-04-02-587_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-22-11-04-02-587_com.tencent.mm.png
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-22-11-04-32-720_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-22-11-04-32-720_com.tencent.mm.png
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-24-10-43-43-123_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-24-10-43-43-123_com.tencent.mm.png
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-28-19-13-24-007_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-28-19-13-24-007_com.tencent.mm.png
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-28-20-01-20-469_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-28-20-01-20-469_com.tencent.mm.png
--------------------------------------------------------------------------------
/demo/Screenshot_2019-10-30-00-14-41-017_com.tencent.mm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yannliao/threejs-example-for-miniprogram/HEAD/demo/Screenshot_2019-10-30-00-14-41-017_com.tencent.mm.png
--------------------------------------------------------------------------------
/sitemap.json:
--------------------------------------------------------------------------------
1 | {
2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
3 | "rules": [{
4 | "action": "allow",
5 | "page": "*"
6 | }]
7 | }
--------------------------------------------------------------------------------
/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/pages/index/index.js:
--------------------------------------------------------------------------------
1 | Page({
2 | data: {},
3 | onLoad: function () {
4 |
5 | },
6 | tap: function(e) {
7 | let path = e.target.dataset.id
8 | wx.navigateTo({
9 | url: `/pages/${path}/index`
10 | })
11 | }
12 | })
13 |
--------------------------------------------------------------------------------
/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | page {
2 | width: 100%;
3 | height: 100%;
4 | }
5 | /* .list-container {
6 | display: flex;
7 | flex-direction: column;
8 | justify-content: flex-start;
9 | align-items: center;
10 | } */
11 | button {margin: 10px;}
--------------------------------------------------------------------------------
/pages/obj/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pages/animation/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pages/raycaster/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pages/gltf/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pages/ngltf/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | page {
3 | width: 100%;
4 | height: 100%;
5 | margin: 0;
6 | padding: 0;
7 | }
8 |
9 | .container {
10 | height: 100%;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: space-between;
15 | padding: 200rpx 0;
16 | box-sizing: border-box;
17 | }
18 |
--------------------------------------------------------------------------------
/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | //logs.js
2 | const util = require('../../utils/util.js')
3 |
4 | Page({
5 | data: {
6 | logs: []
7 | },
8 | onLoad: function () {
9 | this.setData({
10 | logs: (wx.getStorageSync('logs') || []).map(log => {
11 | return util.formatTime(new Date(log))
12 | })
13 | })
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/pages/cube/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pages/trackcontrol/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/pages/primitives/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 | import renderPrimitives from './primitives'
3 |
4 | Page({
5 | data: {},
6 | onLoad: function () {
7 | wx.createSelectorQuery()
8 | .select('#c')
9 | .node()
10 | .exec((res) => {
11 | const canvas = new THREE.global.registerCanvas(res[0].node)
12 | renderPrimitives(canvas, THREE)
13 | })
14 | },
15 | onUnload: function () {
16 | THREE.global.clearCanvas()
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/utils/util.js:
--------------------------------------------------------------------------------
1 | const formatTime = date => {
2 | const year = date.getFullYear()
3 | const month = date.getMonth() + 1
4 | const day = date.getDate()
5 | const hour = date.getHours()
6 | const minute = date.getMinutes()
7 | const second = date.getSeconds()
8 |
9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
10 | }
11 |
12 | const formatNumber = n => {
13 | n = n.toString()
14 | return n[1] ? n : '0' + n
15 | }
16 |
17 | module.exports = {
18 | formatTime: formatTime
19 | }
20 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages": [
3 | "pages/index/index",
4 | "pages/primitives/index",
5 | "pages/cube/index",
6 | "pages/glb/index",
7 | "pages/gltf/index",
8 | "pages/ngltf/index",
9 | "pages/obj/index",
10 | "pages/animation/index",
11 | "pages/trackcontrol/index",
12 | "pages/raycaster/index",
13 | "pages/logs/logs"
14 | ],
15 | "window": {
16 | "backgroundTextStyle": "light",
17 | "navigationBarBackgroundColor": "#fff",
18 | "navigationBarTitleText": "WeChat",
19 | "navigationBarTextStyle": "black"
20 | },
21 | "sitemapLocation": "sitemap.json"
22 | }
--------------------------------------------------------------------------------
/pages/obj/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 | import loadObj from './loadObj'
3 |
4 | Page({
5 | data: {},
6 | onLoad: function () {
7 | wx.createSelectorQuery()
8 | .select('#c')
9 | .node()
10 | .exec((res) => {
11 | const canvas = new THREE.global.registerCanvas(res[0].node)
12 | loadObj(canvas, THREE)
13 | })
14 | },
15 | onUnload: function () {
16 | THREE.global.clearCanvas()
17 | },
18 | touchStart(e) {
19 | console.log('canvas', e)
20 | THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
21 | },
22 | touchMove(e) {
23 | console.log('canvas', e)
24 | THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e)
25 | },
26 | touchEnd(e) {
27 | console.log('canvas', e)
28 | THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
29 | },
30 | touchCancel(e) {
31 | // console.log('canvas', e)
32 | },
33 | longTap(e) {
34 | // console.log('canvas', e)
35 | },
36 | tap(e) {
37 | // console.log('canvas', e)
38 | },
39 | })
40 |
--------------------------------------------------------------------------------
/pages/animation/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 | import animate from './animate'
3 |
4 | Page({
5 | data: {},
6 | onLoad: function () {
7 | wx.createSelectorQuery()
8 | .select('#c')
9 | .node()
10 | .exec((res) => {
11 | const canvas = new THREE.global.registerCanvas(res[0].node)
12 | animate(canvas, THREE)
13 | })
14 | },
15 | onUnload: function () {
16 | THREE.global.clearCanvas()
17 | },
18 | touchStart(e) {
19 | console.log('canvas', e)
20 | THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
21 | },
22 | touchMove(e) {
23 | console.log('canvas', e)
24 | THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e)
25 | },
26 | touchEnd(e) {
27 | console.log('canvas', e)
28 | THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
29 | },
30 | touchCancel(e) {
31 | // console.log('canvas', e)
32 | },
33 | longTap(e) {
34 | // console.log('canvas', e)
35 | },
36 | tap(e) {
37 | // console.log('canvas', e)
38 | },
39 | })
40 |
--------------------------------------------------------------------------------
/pages/ngltf/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 | import render from './render'
3 |
4 | Page({
5 | data: {},
6 | onLoad: function () {
7 | wx.createSelectorQuery()
8 | .select('#c')
9 | .node()
10 | .exec((res) => {
11 | const canvas = new THREE.global.registerCanvas(res[0].node)
12 | render(canvas, THREE)
13 | })
14 | },
15 | onUnload: function () {
16 | //注意清理global中的canvas对象
17 | THREE.global.clearCanvas()
18 | },
19 | touchStart(e) {
20 | console.log('canvas', e)
21 | THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
22 | },
23 | touchMove(e) {
24 | console.log('canvas', e)
25 | THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e)
26 | },
27 | touchEnd(e) {
28 | console.log('canvas', e)
29 | THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
30 | },
31 | touchCancel(e) {
32 | // console.log('canvas', e)
33 | },
34 | longTap(e) {
35 | // console.log('canvas', e)
36 | },
37 | tap(e) {
38 | // console.log('canvas', e)
39 | },
40 | })
41 |
--------------------------------------------------------------------------------
/pages/gltf/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 | import loadgLTF from './loadgLTF'
3 |
4 | Page({
5 | data: {},
6 | onLoad: function () {
7 | wx.createSelectorQuery()
8 | .select('#c')
9 | .node()
10 | .exec((res) => {
11 | const canvas = new THREE.global.registerCanvas(res[0].node)
12 | loadgLTF(canvas, THREE)
13 | })
14 | },
15 | onUnload: function () {
16 | //注意清理global中的canvas对象
17 | THREE.global.clearCanvas()
18 | },
19 | touchStart(e) {
20 | console.log('canvas', e)
21 | THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
22 | },
23 | touchMove(e) {
24 | console.log('canvas', e)
25 | THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e)
26 | },
27 | touchEnd(e) {
28 | console.log('canvas', e)
29 | THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
30 | },
31 | touchCancel(e) {
32 | // console.log('canvas', e)
33 | },
34 | longTap(e) {
35 | // console.log('canvas', e)
36 | },
37 | tap(e) {
38 | // console.log('canvas', e)
39 | },
40 | })
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Yann Liao
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
11 |
12 |
15 |
18 |
21 |
24 |
27 |
30 |
--------------------------------------------------------------------------------
/project.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "description": "项目配置文件",
3 | "packOptions": {
4 | "ignore": [
5 | {
6 | "value": "demo",
7 | "type": "folder"
8 | }
9 | ]
10 | },
11 | "setting": {
12 | "urlCheck": false,
13 | "es6": true,
14 | "postcss": true,
15 | "minified": true,
16 | "newFeature": true,
17 | "coverView": true,
18 | "nodeModules": true,
19 | "autoAudits": false,
20 | "uglifyFileName": true,
21 | "checkInvalidKey": true,
22 | "checkSiteMap": true,
23 | "uploadWithSourceMap": true,
24 | "babelSetting": {
25 | "ignore": [],
26 | "disablePlugins": [],
27 | "outputPath": ""
28 | }
29 | },
30 | "compileType": "miniprogram",
31 | "libVersion": "2.8.3",
32 | "appid": "wx6d94fd5dfb164f75",
33 | "projectname": "threejs-example",
34 | "debugOptions": {
35 | "hidedInDevtools": []
36 | },
37 | "isGameTourist": false,
38 | "simulatorType": "wechat",
39 | "simulatorPluginLibVersion": {},
40 | "condition": {
41 | "search": {
42 | "current": -1,
43 | "list": []
44 | },
45 | "conversation": {
46 | "current": -1,
47 | "list": []
48 | },
49 | "game": {
50 | "currentL": -1,
51 | "list": []
52 | },
53 | "miniprogram": {
54 | "current": -1,
55 | "list": []
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/pages/glb/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 | import gLTF from '../../jsm/loaders/GLTFLoader'
3 | let window = THREE.global
4 | let GLTFLoader = gLTF(THREE)
5 |
6 | Page({
7 | data: {},
8 | onLoad: function () {
9 | var that = this
10 | wx.createSelectorQuery()
11 | .select('#c')
12 | .node()
13 | .exec((res) => {
14 | const canvas = new THREE.global.registerCanvas(res[0].node)
15 | that.render(canvas, THREE)
16 | })
17 | },
18 | onUnload: function () {
19 | //注意清理global中的canvas对象
20 | THREE.global.clearCanvas()
21 | },
22 | render(canvas) {
23 | const renderer = new THREE.WebGLRenderer({ antialias: true });
24 | renderer.setPixelRatio(window.devicePixelRatio);
25 | // renderer.setSize(canvas.width, canvas.height);
26 | renderer.gammaOutput = true;
27 |
28 | const fov = 45;
29 | const aspect = 2; // the canvas default
30 | const near = 0.1;
31 | const far = 100;
32 | const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
33 | camera.position.set(0, 10, 20);
34 |
35 |
36 | const scene = new THREE.Scene();
37 | scene.background = new THREE.Color('black');
38 |
39 |
40 | {
41 | const skyColor = 0xB1E1FF; // light blue
42 | const groundColor = 0xB97A20; // brownish orange
43 | const intensity = 1;
44 | const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
45 | scene.add(light);
46 | }
47 |
48 | {
49 | const color = 0xFFFFFF;
50 | const intensity = 1;
51 | const light = new THREE.DirectionalLight(color, intensity);
52 | light.position.set(5, 10, 2);
53 | scene.add(light);
54 | scene.add(light.target);
55 | }
56 |
57 |
58 |
59 | {
60 | const gltfLoader = new GLTFLoader();
61 | gltfLoader.load('https://cdn.liubaiwenhua.com/201812/ctree/resource/model/scene.glb', (gltf) => {
62 | const root = gltf.scene;
63 | scene.add(root);
64 | });
65 | }
66 |
67 |
68 |
69 | function render() {
70 |
71 | camera.aspect = canvas.clientWidth / canvas.clientHeight;
72 | camera.updateProjectionMatrix();
73 |
74 | renderer.render(scene, camera);
75 | canvas.requestAnimationFrame(render);
76 | }
77 |
78 | canvas.requestAnimationFrame(render);
79 | }
80 | })
81 |
--------------------------------------------------------------------------------
/utils/ResourceTracker.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../libs/three.weapp.js'
2 | export default class ResourceTracker {
3 | constructor() {
4 | this.resources = new Set();
5 | }
6 | track(resource) {
7 | if (!resource) {
8 | return resource;
9 | }
10 |
11 | // handle children and when material is an array of materials or
12 | // uniform is array of textures
13 | if (Array.isArray(resource)) {
14 | resource.forEach(resource => this.track(resource));
15 | return resource;
16 | }
17 |
18 | if (resource.dispose || resource instanceof THREE.Object3D) {
19 | this.resources.add(resource);
20 | }
21 | if (resource instanceof THREE.Object3D) {
22 | this.track(resource.geometry);
23 | this.track(resource.material);
24 | this.track(resource.children);
25 | } else if (resource instanceof THREE.Material) {
26 | // We have to check if there are any textures on the material
27 | for (const value of Object.values(resource)) {
28 | if (value instanceof THREE.Texture) {
29 | this.track(value);
30 | }
31 | }
32 | // We also have to check if any uniforms reference textures or arrays of textures
33 | if (resource.uniforms) {
34 | for (const value of Object.values(resource.uniforms)) {
35 | if (value) {
36 | const uniformValue = value.value;
37 | if (uniformValue instanceof THREE.Texture ||
38 | Array.isArray(uniformValue)) {
39 | this.track(uniformValue);
40 | }
41 | }
42 | }
43 | }
44 | }
45 | return resource;
46 | }
47 | untrack(resource) {
48 | this.resources.delete(resource);
49 | }
50 | dispose() {
51 | for (const resource of this.resources) {
52 | if (resource instanceof THREE.Object3D) {
53 | if (resource.parent) {
54 | resource.parent.remove(resource);
55 | }
56 | }
57 | if (resource.dispose) {
58 | resource.dispose();
59 | }
60 | }
61 | this.resources.clear();
62 | }
63 | }
--------------------------------------------------------------------------------
/pages/ngltf/render.js:
--------------------------------------------------------------------------------
1 | import gLTF from '../../jsm/loaders/GLTFLoader'
2 | import { OrbitControls } from '../../jsm/controls/OrbitControls'
3 |
4 | import getRGBLoader from '../../jsm/loaders/RGBELoader.js';
5 | import getGenerator from '../../jsm/loaders/EquirectangularToCubeGenerator.js';
6 | import getPMREMGenerator from '../../jsm/pmrem/PMREMGenerator.js';
7 | import getPMREMCubeUVPacker from '../../jsm/pmrem/PMREMCubeUVPacker.js';
8 |
9 |
10 | export default function (canvas, THREE) {
11 | let GLTFLoader = gLTF(THREE);
12 | let { RGBELoader } = getRGBLoader(THREE)
13 | let { EquirectangularToCubeGenerator } = getGenerator(THREE)
14 | let { PMREMGenerator } = getPMREMGenerator(THREE)
15 | let { PMREMCubeUVPacker } = getPMREMCubeUVPacker(THREE)
16 | let window = THREE.global
17 |
18 | var controls;
19 | var camera, scene, renderer;
20 |
21 | init();
22 | animate();
23 |
24 | function init() {
25 |
26 | renderer = new THREE.WebGLRenderer({ antialias: true });
27 | renderer.setPixelRatio(window.devicePixelRatio);
28 | // renderer.setSize(canvas.width, canvas.height);
29 | renderer.gammaOutput = true;
30 |
31 |
32 | camera = new THREE.PerspectiveCamera(45, canvas.width / canvas.height, 0.25, 20);
33 | camera.position.set(- 1.8, 0.9, 2.7);
34 |
35 | scene = new THREE.Scene();
36 |
37 | new RGBELoader()
38 | .setDataType(THREE.UnsignedByteType)
39 | .setPath('https://threejs.org/examples/textures/equirectangular/')
40 | .load('pedestrian_overpass_2k.hdr', function (texture) {
41 |
42 | var cubeGenerator = new EquirectangularToCubeGenerator(texture, { resolution: 1024 });
43 | cubeGenerator.update(renderer);
44 |
45 | var pmremGenerator = new PMREMGenerator(cubeGenerator.renderTarget.texture);
46 | pmremGenerator.update(renderer);
47 |
48 | var pmremCubeUVPacker = new PMREMCubeUVPacker(pmremGenerator.cubeLods);
49 | pmremCubeUVPacker.update(renderer);
50 |
51 | var envMap = pmremCubeUVPacker.CubeUVRenderTarget.texture;
52 |
53 | // model
54 |
55 | var loader = new GLTFLoader().setPath('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/');
56 | loader.load('DamagedHelmet.gltf', function (gltf) {
57 |
58 | gltf.scene.traverse(function (child) {
59 |
60 | if (child.isMesh) {
61 |
62 | child.material.envMap = envMap;
63 |
64 | }
65 |
66 | });
67 |
68 | scene.add(gltf.scene);
69 |
70 | });
71 |
72 | pmremGenerator.dispose();
73 | pmremCubeUVPacker.dispose();
74 |
75 | scene.background = cubeGenerator.renderTarget;
76 |
77 | });
78 |
79 |
80 |
81 | controls = new OrbitControls(camera, renderer.domElement);
82 | controls.target.set(0, - 0.2, - 0.2);
83 | controls.update();
84 | }
85 |
86 | //
87 |
88 | function animate() {
89 |
90 | canvas.requestAnimationFrame(animate);
91 |
92 | renderer.render(scene, camera);
93 |
94 | }
95 |
96 |
97 | }
--------------------------------------------------------------------------------
/pages/cube/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 | import { OrbitControls } from '../../jsm/controls/OrbitControls'
3 |
4 | Page({
5 | data: {
6 | canvasId: null
7 | },
8 | onLoad: function () {
9 | wx.createSelectorQuery()
10 | .select('#c')
11 | .node()
12 | .exec((res) => {
13 | let canvasId = res[0].node._canvasId
14 | const canvas = THREE.global.registerCanvas(canvasId, res[0].node)
15 |
16 | this.setData({ canvasId })
17 |
18 | const camera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 1, 1000);
19 | camera.position.z = 500;
20 | const scene = new THREE.Scene();
21 | scene.background = new THREE.Color(0xAAAAAA);
22 | const renderer = new THREE.WebGLRenderer({ antialias: true });
23 |
24 | const controls = new OrbitControls(camera, renderer.domElement);
25 | // controls.enableDamping = true;
26 | // controls.dampingFactor = 0.25;
27 | // controls.enableZoom = false;
28 | camera.position.set(200, 200, 500);
29 | controls.update();
30 | const geometry = new THREE.BoxBufferGeometry(200, 200, 200);
31 |
32 | const texture = new THREE.TextureLoader().load('./pikachu.png');
33 | const material = new THREE.MeshBasicMaterial({ map: texture });
34 |
35 | // const material = new THREE.MeshBasicMaterial({ color: 0x44aa88 });
36 | const mesh = new THREE.Mesh(geometry, material);
37 | scene.add(mesh);
38 |
39 | // renderer.setPixelRatio(wx.getSystemInfoSync().pixelRatio);
40 | // renderer.setSize(canvas.width, canvas.height);
41 |
42 | function onWindowResize() {
43 | camera.aspect = window.innerWidth / window.innerHeight;
44 | camera.updateProjectionMatrix();
45 | renderer.setSize(canvas.width, canvas.height);
46 | }
47 | function render() {
48 | canvas.requestAnimationFrame(render);
49 | // mesh.rotation.x += 0.005;
50 | // mesh.rotation.y += 0.01;
51 | controls.update();
52 | renderer.render(scene, camera);
53 | }
54 |
55 | render()
56 |
57 | })
58 | },
59 | onUnload: function () {
60 | THREE.global.unregisterCanvas(this.data.canvasId)
61 | },
62 | touchStart(e) {
63 | console.log('canvas', e)
64 | THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
65 | },
66 | touchMove(e) {
67 | console.log('canvas', e)
68 | THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e)
69 | },
70 | touchEnd(e) {
71 | console.log('canvas', e)
72 | THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
73 | },
74 | touchCancel(e) {
75 | // console.log('canvas', e)
76 | },
77 | longTap(e) {
78 | // console.log('canvas', e)
79 | },
80 | tap(e) {
81 | // console.log('canvas', e)
82 | },
83 | documentTouchStart(e) {
84 | // console.log('document',e)
85 | },
86 | documentTouchMove(e) {
87 | // console.log('document',e)
88 | },
89 | documentTouchEnd(e) {
90 | // console.log('document',e)
91 | },
92 | })
93 |
--------------------------------------------------------------------------------
/pages/obj/loadObj.js:
--------------------------------------------------------------------------------
1 |
2 | import getDDSLoader from '../../jsm/loaders/DDSLoader.js';
3 | import getMTLLoader from '../../jsm/loaders/MTLLoader.js';
4 | import getOBJLoader from '../../jsm/loaders/OBJLoader.js';
5 | import { OrbitControls } from '../../jsm/controls/OrbitControls';
6 |
7 | export default function (canvas, THREE) {
8 | let { DDSLoader } = getDDSLoader(THREE);
9 | let { MTLLoader } = getMTLLoader(THREE);
10 | let OBJLoader = getOBJLoader(THREE);
11 | let window = THREE.global;
12 |
13 | let camera, scene, renderer, controls;
14 |
15 | let object;
16 |
17 | init();
18 | animate();
19 |
20 |
21 | function init() {
22 | //renderer
23 | {
24 | renderer = new THREE.WebGLRenderer({ canvas });
25 | renderer.setPixelRatio(window.devicePixelRatio);
26 | renderer.setSize(window.innerWidth, window.innerHeight);
27 | }
28 | //camera
29 | {
30 | camera = new THREE.PerspectiveCamera(45, canvas.clientWidth / canvas.clientHeight, 1, 2000);
31 | camera.position.z = 250;
32 | }
33 | //controls
34 | {
35 | controls = new OrbitControls(camera, canvas);
36 | controls.target.set(0, 5, 0);
37 | controls.update();
38 | }
39 |
40 | // scene & light
41 | {
42 | scene = new THREE.Scene();
43 |
44 | let ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
45 | scene.add(ambientLight);
46 |
47 | let pointLight = new THREE.PointLight(0xffffff, 0.8);
48 | camera.add(pointLight);
49 | scene.add(camera);
50 | }
51 |
52 | // manager
53 |
54 | function loadModel() {
55 |
56 | object.traverse(function (child) {
57 |
58 | if (child.isMesh) child.material.map = texture;
59 |
60 | });
61 |
62 | object.position.y = - 95;
63 | scene.add(object);
64 |
65 | }
66 |
67 | let manager = new THREE.LoadingManager(loadModel);
68 |
69 | manager.onProgress = function (item, loaded, total) {
70 |
71 | console.log(item, loaded, total);
72 |
73 | };
74 |
75 | // texture
76 |
77 | let textureLoader = new THREE.TextureLoader(manager);
78 |
79 | let texture = textureLoader.load('https://threejs.org/examples/textures/uv_grid_opengl.jpg');
80 |
81 | // model
82 |
83 | function onProgress(xhr) {
84 |
85 | if (xhr.lengthComputable) {
86 |
87 | let percentComplete = xhr.loaded / xhr.total * 100;
88 | console.log('model ' + Math.round(percentComplete, 2) + '% downloaded');
89 |
90 | }
91 |
92 | }
93 |
94 | function onError() { }
95 |
96 | let loader = new OBJLoader(manager);
97 |
98 | loader.load('https://threejs.org/examples/models/obj/male02/male02.obj', function (obj) {
99 |
100 | object = obj;
101 |
102 | }, onProgress, onError);
103 |
104 | }
105 |
106 | function animate() {
107 |
108 | canvas.requestAnimationFrame(animate);
109 | render();
110 |
111 | }
112 |
113 | function render() {
114 |
115 | camera.lookAt(scene.position);
116 | controls.update();
117 | renderer.render(scene, camera);
118 |
119 | }
120 |
121 |
122 | }
--------------------------------------------------------------------------------
/pages/raycaster/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 |
3 | let window = THREE.global;
4 | let { document } = window;
5 |
6 | Page({
7 | data: {
8 | },
9 | onLoad: function () {
10 | this.theta = 0;
11 | this.mouse = new THREE.Vector2();
12 | this.radius = 100;
13 |
14 | wx.createSelectorQuery()
15 | .select('#c')
16 | .node()
17 | .exec((res) => {
18 | const canvas = THREE.global.registerCanvas(res[0].node)
19 | this.canvas = canvas;
20 |
21 | this.init(canvas)
22 | this.animate(canvas);
23 | })
24 |
25 | },
26 |
27 | render() {
28 | let { scene, mouse, renderer, radius, raycaster } = this;
29 |
30 | this.theta += 0.1;
31 |
32 | this.camera.position.x = radius * Math.sin(THREE.Math.degToRad(this.theta));
33 | this.camera.position.y = radius * Math.sin(THREE.Math.degToRad(this.theta));
34 | this.camera.position.z = radius * Math.cos(THREE.Math.degToRad(this.theta));
35 | this.camera.lookAt(scene.position);
36 |
37 | this.camera.updateMatrixWorld();
38 |
39 | // find intersections
40 |
41 | raycaster.setFromCamera(mouse, this.camera);
42 |
43 | var intersects = raycaster.intersectObjects(scene.children);
44 |
45 | if (intersects.length > 0) {
46 |
47 | if (this.INTERSECTED != intersects[0].object) {
48 |
49 | if (this.INTERSECTED) this.INTERSECTED.material.emissive.setHex(this.INTERSECTED.currentHex);
50 |
51 | this.INTERSECTED = intersects[0].object;
52 | this.INTERSECTED.currentHex = this.INTERSECTED.material.emissive.getHex();
53 | this.INTERSECTED.material.emissive.setHex(0xff0000);
54 |
55 | }
56 |
57 | } else {
58 |
59 | if (this.INTERSECTED) this.INTERSECTED.material.emissive.setHex(this.INTERSECTED.currentHex);
60 |
61 | this.INTERSECTED = null;
62 |
63 | }
64 |
65 | renderer.render(scene, this.camera);
66 |
67 | },
68 | animate() {
69 | let canvas = this.canvas;
70 | canvas.requestAnimationFrame(this.animate);
71 |
72 | this.render();
73 |
74 | },
75 | init(canvas) {
76 |
77 | this.camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 10000);
78 |
79 | this.scene = new THREE.Scene();
80 | this.scene.background = new THREE.Color(0xf0f0f0);
81 |
82 | var light = new THREE.DirectionalLight(0xffffff, 1);
83 | light.position.set(1, 1, 1).normalize();
84 | this.scene.add(light);
85 |
86 | var geometry = new THREE.BoxBufferGeometry(20, 20, 20);
87 |
88 | for (var i = 0; i < 2000; i++) {
89 |
90 | var object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
91 |
92 | object.position.x = Math.random() * 800 - 400;
93 | object.position.y = Math.random() * 800 - 400;
94 | object.position.z = Math.random() * 800 - 400;
95 |
96 | object.rotation.x = Math.random() * 2 * Math.PI;
97 | object.rotation.y = Math.random() * 2 * Math.PI;
98 | object.rotation.z = Math.random() * 2 * Math.PI;
99 |
100 | object.scale.x = Math.random() + 0.5;
101 | object.scale.y = Math.random() + 0.5;
102 | object.scale.z = Math.random() + 0.5;
103 |
104 | this.scene.add(object);
105 |
106 | }
107 |
108 | this.raycaster = new THREE.Raycaster();
109 |
110 | this.renderer = new THREE.WebGLRenderer({ canvas });
111 | // this.renderer.setPixelRatio(window.devicePixelRatio);
112 | this.renderer.setSize(window.innerWidth, window.innerHeight);
113 |
114 |
115 | //
116 | window.addEventListener('resize', onWindowResize, false);
117 |
118 | function onWindowResize() {
119 |
120 | this.camera.aspect = window.innerWidth / window.innerHeight;
121 | this.camera.updateProjectionMatrix();
122 |
123 | this.renderer.setSize(window.innerWidth, window.innerHeight);
124 |
125 | }
126 |
127 |
128 |
129 | },
130 | onUnload() {
131 | THREE.global.clearCanvas()
132 | },
133 | touchStart(e) {
134 | // console.log('canvas', e)
135 | // THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
136 | },
137 | touchMove(event) {
138 | console.log('canvas', event)
139 |
140 | let touch = event.touches[0];
141 | this.mouse.x = (touch.x / window.innerWidth) * 2 - 1;
142 | this.mouse.y = - (touch.y / window.innerHeight) * 2 + 1;
143 |
144 | },
145 | touchEnd(e) {
146 | // console.log('canvas', e)
147 | // THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
148 | },
149 | touchCancel(e) {
150 | // console.log('canvas', e)
151 | },
152 | longTap(e) {
153 | // console.log('canvas', e)
154 | },
155 | tap(e) {
156 |
157 | // console.log('canvas', e)
158 | },
159 | })
160 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # three.js example
2 |
3 | three.js example in wechat miniprogram
4 |
5 | ----
6 |
7 | three.js 在小程序里的使用示例,其中 [three.js](https://github.com/yannliao/three.js) 使用的是小程序移植版,详情请到 [https://github.com/yannliao/three.js](https://github.com/yannliao/three.js)。
8 |
9 | ## 注意
10 | 1. 由于示例中有较多的模型是使用官网链接,加载比较慢。如果可以请查找替换资源路径,并替换。小程序只可以跟指定的域名与进行网络通信。使用前请[配置信任域名](https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html)
11 |
12 | 2. 内存泄漏。为了防止内存泄漏,请在页面unload时,用 THREE.global.unregisterCanvas 或 THREE.global.clearCanvas 清除THREE.global中的canvas引用。同时利用canvas.cancelAnimationFrame 清除动画, 并且释放 Geometries Materials 等 Three.js 对象, 参考[How to dispose of objects](https://threejs.org/docs/index.html#manual/en/introduction/How-to-dispose-of-objects), [Three.js Cleanup](https://threejsfundamentals.org/threejs/lessons/threejs-cleanup.html)
13 |
14 | ## 交流
15 | 大家可以加入QQ群进行交流,群号: 858741591
16 |
17 |
18 |
19 | ## 示例&兼容性
20 |
21 |
22 |
23 |
24 | | 名称 |
25 | 性能 |
26 | 机型 |
27 |
28 |
29 |
30 |
31 | | 基本 |
32 |  |
33 |
34 | xiaomi MIX2 Android 8.0.0 Wechat Version 7.0.5
35 | vivo X21A Android 9 Wechat Version 7.0.5
36 | iphone 8Plus ios13.1.2 Wechat Version 7.0.8
37 | |
38 |
39 |
40 | | 动画 |
41 |
42 |
43 | |
44 |
45 | xiaomi MIX2 Android 8.0.0 Wechat Version 7.0.5
46 | vivo X21A Android 9 Wechat Version 7.0.5
47 | iphone 8Plus ios13.1.2 Wechat Version 7.0.8
48 | |
49 |
50 |
51 | | OrbitControl 立方 |
52 |
53 |
54 | |
55 |
56 | xiaomi MIX2 Android 8.0.0 Wechat Version 7.0.5
57 | vivo X21A Android 9 Wechat Version 7.0.5
58 | iphone 8Plus ios13.1.2 Wechat Version 7.0.8
59 | |
60 |
61 |
62 | | gLTF 文件加载 |
63 |
64 |
65 | |
66 |
67 | xiaomi MIX2 Android 8.0.0 Wechat Version 7.0.5
68 | vivo X21A Android 9 Wechat Version 7.0.5
69 | iphone 8Plus ios13.1.2 Wechat Version 7.0.8
70 | |
71 |
72 |
73 | | glb 文件 |
74 |
75 |
76 | |
77 |
78 | xiaomi MIX2 Android 8.0.0 Wechat Version 7.0.5
79 | vivo X21A Android 9 Wechat Version 7.0.5
80 | iphone 8Plus ios13.1.2 Wechat Version 7.0.8
81 | |
82 |
83 |
84 | | gLTF 文件加贴图 |
85 |
86 |
87 |
88 | |
89 |
90 | xiaomi MIX2 Android 8.0.0 Wechat Version 7.0.5
91 | vivo X21A Android 9 Wechat Version 7.0.5
92 | iphone 8Plus ios13.1.2 Wechat Version 7.0.8
93 | |
94 |
95 |
96 | | obj 文件 (WEBGL_compressed_texture_s3tc 扩展不支持) |
97 |
98 |
99 | |
100 |
101 | xiaomi MIX2 Android 8.0.0 Wechat Version 7.0.5
102 | vivo X21A Android 9 Wechat Version 7.0.5
103 | iphone 8Plus ios13.1.2 Wechat Version 7.0.8
104 | |
105 |
106 |
107 | | raycaster 选中立方体 |
108 |
109 |
110 | |
111 |
112 | xiaomi MIX2 Android 8.0.0 Wechat Version 7.0.5
113 | iphone 8Plus ios13.1.2 Wechat Version 7.0.8
114 | |
115 |
116 |
117 |
118 |
--------------------------------------------------------------------------------
/pages/trackcontrol/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from '../../libs/three.weapp.js'
2 | import getControl from '../../jsm/controls/TrackballControls.js'
3 | let { TrackballControls } = getControl(THREE);
4 | let window = THREE.global;
5 | import ResourceTracker from '../../utils/ResourceTracker';
6 |
7 | const resMgr = new ResourceTracker();
8 | const track = resMgr.track.bind(resMgr);
9 |
10 | Page({
11 | data: {
12 | canvas: null
13 | },
14 | onLoad: function () {
15 | let that = this;
16 | wx.createSelectorQuery()
17 | .select('#c')
18 | .node()
19 | .exec((res) => {
20 | const canvas = THREE.global.registerCanvas(res[0].node);
21 | that.setData({
22 | canvas: canvas
23 | })
24 | const params = {
25 | orthographicCamera: false
26 | };
27 |
28 | const frustumSize = 400;
29 |
30 |
31 | const aspect = window.innerWidth / window.innerHeight;
32 |
33 | const perspectiveCamera = new THREE.PerspectiveCamera(60, aspect, 1, 1000);
34 | perspectiveCamera.position.z = 500;
35 |
36 | const orthographicCamera = new THREE.OrthographicCamera(frustumSize * aspect / - 2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / - 2, 1, 1000);
37 | orthographicCamera.position.z = 500;
38 |
39 | // world
40 |
41 | const scene = new THREE.Scene();
42 | scene.background = new THREE.Color(0xcccccc);
43 | scene.fog = new THREE.FogExp2(0xcccccc, 0.002);
44 | track(scene);
45 |
46 | const geometry = new THREE.CylinderBufferGeometry(0, 10, 30, 4, 1);
47 | const material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });
48 |
49 | for (let i = 0; i < 500; i++) {
50 |
51 | let mesh = new THREE.Mesh(geometry, material);
52 | mesh.position.x = (Math.random() - 0.5) * 1000;
53 | mesh.position.y = (Math.random() - 0.5) * 1000;
54 | mesh.position.z = (Math.random() - 0.5) * 1000;
55 | mesh.updateMatrix();
56 | mesh.matrixAutoUpdate = false;
57 | scene.add(mesh);
58 |
59 | }
60 |
61 | // lights
62 |
63 | {
64 | let dlight = new THREE.DirectionalLight(0xffffff);
65 | dlight.position.set(1, 1, 1);
66 | scene.add(dlight);
67 |
68 | let Dlight = new THREE.DirectionalLight(0x002288);
69 | Dlight.position.set(- 1, - 1, - 1);
70 | scene.add(Dlight);
71 |
72 | let Alight = new THREE.AmbientLight(0x222222);
73 | scene.add(Alight);
74 | }
75 | // renderer
76 |
77 | const renderer = new THREE.WebGLRenderer({ antialias: true });
78 | renderer.setPixelRatio(window.devicePixelRatio);
79 | renderer.setSize(window.innerWidth, window.innerHeight);
80 |
81 |
82 | const controls = new TrackballControls(perspectiveCamera, renderer.domElement);
83 |
84 | controls.rotateSpeed = 1.0;
85 | controls.zoomSpeed = 1.2;
86 | controls.panSpeed = 0.8;
87 |
88 | controls.staticMoving = true;
89 | controls.dynamicDampingFactor = 0.3;
90 |
91 | controls.keys = [65, 83, 68];
92 |
93 | controls.addEventListener('change', render);
94 |
95 |
96 |
97 | window.addEventListener('resize', onWindowResize, false);
98 |
99 | function onWindowResize() {
100 |
101 | var aspect = window.innerWidth / window.innerHeight;
102 |
103 | perspectiveCamera.aspect = aspect;
104 | perspectiveCamera.updateProjectionMatrix();
105 |
106 | orthographicCamera.left = - frustumSize * aspect / 2;
107 | orthographicCamera.right = frustumSize * aspect / 2;
108 | orthographicCamera.top = frustumSize / 2;
109 | orthographicCamera.bottom = - frustumSize / 2;
110 | orthographicCamera.updateProjectionMatrix();
111 |
112 | renderer.setSize(window.innerWidth, window.innerHeight);
113 |
114 | // controls.handleResize();
115 |
116 | render();
117 |
118 | }
119 |
120 | function animate() {
121 |
122 | that.animateId = canvas.requestAnimationFrame(animate);
123 |
124 | controls.update();
125 |
126 | }
127 |
128 | function render() {
129 |
130 | let camera = (params.orthographicCamera) ? orthographicCamera : perspectiveCamera;
131 |
132 | renderer.render(scene, camera);
133 |
134 | }
135 |
136 | render();
137 | animate();
138 | })
139 | },
140 | onUnload: function () {
141 | let that = this;
142 | that.data.canvas.cancelAnimationFrame(that.animateId);
143 | that.animateId = null;
144 | that.setData({
145 | canvas: null
146 | });
147 | THREE.global.clearCanvas();
148 | resMgr.dispose();
149 | },
150 | touchStart(e) {
151 | console.log('canvas', e)
152 | THREE.global.touchEventHandlerFactory('canvas', 'touchstart')(e)
153 | },
154 | touchMove(e) {
155 | console.log('canvas', e)
156 | THREE.global.touchEventHandlerFactory('canvas', 'touchmove')(e)
157 | },
158 | touchEnd(e) {
159 | console.log('canvas', e)
160 | THREE.global.touchEventHandlerFactory('canvas', 'touchend')(e)
161 | },
162 | touchCancel(e) {
163 | // console.log('canvas', e)
164 | },
165 | longTap(e) {
166 | // console.log('canvas', e)
167 | },
168 | tap(e) {
169 | // console.log('canvas', e)
170 | },
171 | documentTouchStart(e) {
172 | // console.log('document',e)
173 | },
174 | documentTouchMove(e) {
175 | // console.log('document',e)
176 | },
177 | documentTouchEnd(e) {
178 | // console.log('document',e)
179 | },
180 | })
181 |
--------------------------------------------------------------------------------
/jsm/loaders/EquirectangularToCubeGenerator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Richard M. / https://github.com/richardmonette
3 | * @author WestLangley / http://github.com/WestLangley
4 | */
5 | export default function (THREE) {
6 | let {
7 | BackSide,
8 | BoxBufferGeometry,
9 | CubeCamera,
10 | Mesh,
11 | NoBlending,
12 | PerspectiveCamera,
13 | Scene,
14 | ShaderMaterial,
15 | UniformsUtils,
16 | WebGLRenderTargetCube
17 | } = THREE;
18 |
19 | var CubemapGenerator = function ( renderer ) {
20 |
21 | this.renderer = renderer;
22 |
23 | };
24 |
25 | CubemapGenerator.prototype.fromEquirectangular = function ( texture, options ) {
26 |
27 | options = options || {};
28 |
29 | var scene = new Scene();
30 |
31 | var shader = {
32 |
33 | uniforms: {
34 | tEquirect: { value: null },
35 | },
36 |
37 | vertexShader:
38 |
39 | `
40 | varying vec3 vWorldDirection;
41 |
42 | //include
43 | vec3 transformDirection( in vec3 dir, in mat4 matrix ) {
44 |
45 | return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );
46 |
47 | }
48 |
49 | void main() {
50 |
51 | vWorldDirection = transformDirection( position, modelMatrix );
52 |
53 | #include
54 | #include
55 |
56 | }
57 | `,
58 |
59 | fragmentShader:
60 |
61 | `
62 | uniform sampler2D tEquirect;
63 |
64 | varying vec3 vWorldDirection;
65 |
66 | //include
67 | #define RECIPROCAL_PI 0.31830988618
68 | #define RECIPROCAL_PI2 0.15915494
69 |
70 | void main() {
71 |
72 | vec3 direction = normalize( vWorldDirection );
73 |
74 | vec2 sampleUV;
75 |
76 | sampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;
77 |
78 | sampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;
79 |
80 | gl_FragColor = texture2D( tEquirect, sampleUV );
81 |
82 | }
83 | `
84 | };
85 |
86 | var material = new ShaderMaterial( {
87 |
88 | type: 'CubemapFromEquirect',
89 |
90 | uniforms: UniformsUtils.clone( shader.uniforms ),
91 | vertexShader: shader.vertexShader,
92 | fragmentShader: shader.fragmentShader,
93 | side: BackSide,
94 | blending: NoBlending
95 |
96 | } );
97 |
98 | material.uniforms.tEquirect.value = texture;
99 |
100 | var mesh = new Mesh( new BoxBufferGeometry( 5, 5, 5 ), material );
101 |
102 | scene.add( mesh );
103 |
104 | var resolution = options.resolution || 512;
105 |
106 | var params = {
107 | type: texture.type,
108 | format: texture.format,
109 | encoding: texture.encoding,
110 | generateMipmaps: ( options.generateMipmaps !== undefined ) ? options.generateMipmaps : texture.generateMipmaps,
111 | minFilter: ( options.minFilter !== undefined ) ? options.minFilter : texture.minFilter,
112 | magFilter: ( options.magFilter !== undefined ) ? options.magFilter : texture.magFilter
113 | };
114 |
115 | var camera = new CubeCamera( 1, 10, resolution, params );
116 |
117 | camera.update( this.renderer, scene );
118 |
119 | mesh.geometry.dispose();
120 | mesh.material.dispose();
121 |
122 | return camera.renderTarget;
123 |
124 | };
125 |
126 | //
127 |
128 | var EquirectangularToCubeGenerator = ( function () {
129 |
130 | var camera = new PerspectiveCamera( 90, 1, 0.1, 10 );
131 | var scene = new Scene();
132 | var boxMesh = new Mesh( new BoxBufferGeometry( 1, 1, 1 ), getShader() );
133 | boxMesh.material.side = BackSide;
134 | scene.add( boxMesh );
135 |
136 | var EquirectangularToCubeGenerator = function ( sourceTexture, options ) {
137 |
138 | options = options || {};
139 |
140 | this.sourceTexture = sourceTexture;
141 | this.resolution = options.resolution || 512;
142 |
143 | this.views = [
144 | { t: [ 1, 0, 0 ], u: [ 0, - 1, 0 ] },
145 | { t: [ - 1, 0, 0 ], u: [ 0, - 1, 0 ] },
146 | { t: [ 0, 1, 0 ], u: [ 0, 0, 1 ] },
147 | { t: [ 0, - 1, 0 ], u: [ 0, 0, - 1 ] },
148 | { t: [ 0, 0, 1 ], u: [ 0, - 1, 0 ] },
149 | { t: [ 0, 0, - 1 ], u: [ 0, - 1, 0 ] },
150 | ];
151 |
152 | var params = {
153 | format: options.format || this.sourceTexture.format,
154 | magFilter: this.sourceTexture.magFilter,
155 | minFilter: this.sourceTexture.minFilter,
156 | type: options.type || this.sourceTexture.type,
157 | generateMipmaps: this.sourceTexture.generateMipmaps,
158 | anisotropy: this.sourceTexture.anisotropy,
159 | encoding: this.sourceTexture.encoding
160 | };
161 |
162 | this.renderTarget = new WebGLRenderTargetCube( this.resolution, this.resolution, params );
163 |
164 | };
165 |
166 | EquirectangularToCubeGenerator.prototype = {
167 |
168 | constructor: EquirectangularToCubeGenerator,
169 |
170 | update: function ( renderer ) {
171 |
172 | var currentRenderTarget = renderer.getRenderTarget();
173 |
174 | boxMesh.material.uniforms.equirectangularMap.value = this.sourceTexture;
175 |
176 | for ( var i = 0; i < 6; i ++ ) {
177 |
178 | var v = this.views[ i ];
179 |
180 | camera.position.set( 0, 0, 0 );
181 | camera.up.set( v.u[ 0 ], v.u[ 1 ], v.u[ 2 ] );
182 | camera.lookAt( v.t[ 0 ], v.t[ 1 ], v.t[ 2 ] );
183 |
184 | renderer.setRenderTarget( this.renderTarget, i );
185 | renderer.clear();
186 | renderer.render( scene, camera );
187 |
188 | }
189 |
190 | renderer.setRenderTarget( currentRenderTarget );
191 |
192 | return this.renderTarget.texture;
193 |
194 | },
195 |
196 | dispose: function () {
197 |
198 | this.renderTarget.dispose();
199 |
200 | }
201 |
202 | };
203 |
204 | function getShader() {
205 |
206 | var shaderMaterial = new ShaderMaterial( {
207 |
208 | uniforms: {
209 | "equirectangularMap": { value: null },
210 | },
211 |
212 | vertexShader:
213 | "varying vec3 localPosition;\n\
214 | \n\
215 | void main() {\n\
216 | localPosition = position;\n\
217 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\
218 | }",
219 |
220 | fragmentShader:
221 | "#include \n\
222 | varying vec3 localPosition;\n\
223 | uniform sampler2D equirectangularMap;\n\
224 | \n\
225 | vec2 EquirectangularSampleUV(vec3 v) {\n\
226 | vec2 uv = vec2(atan(v.z, v.x), asin(v.y));\n\
227 | uv *= vec2(0.1591, 0.3183); // inverse atan\n\
228 | uv += 0.5;\n\
229 | return uv;\n\
230 | }\n\
231 | \n\
232 | void main() {\n\
233 | vec2 uv = EquirectangularSampleUV(normalize(localPosition));\n\
234 | gl_FragColor = texture2D(equirectangularMap, uv);\n\
235 | }",
236 |
237 | blending: NoBlending
238 |
239 | } );
240 |
241 | shaderMaterial.type = 'EquirectangularToCubeGenerator';
242 |
243 | return shaderMaterial;
244 |
245 | }
246 |
247 | return EquirectangularToCubeGenerator;
248 |
249 | } )();
250 |
251 | return { CubemapGenerator, EquirectangularToCubeGenerator };
252 | }
253 |
--------------------------------------------------------------------------------
/jsm/loaders/DDSLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 | export default function (THREE) {
5 | let {
6 | CompressedTextureLoader,
7 | RGBAFormat,
8 | RGBA_S3TC_DXT3_Format,
9 | RGBA_S3TC_DXT5_Format,
10 | RGB_ETC1_Format,
11 | RGB_S3TC_DXT1_Format
12 | } = THREE;
13 |
14 | var DDSLoader = function (manager) {
15 |
16 | CompressedTextureLoader.call(this, manager);
17 |
18 | };
19 |
20 | DDSLoader.prototype = Object.assign(Object.create(CompressedTextureLoader.prototype), {
21 |
22 | constructor: DDSLoader,
23 |
24 | parse: function (buffer, loadMipmaps) {
25 |
26 | var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 };
27 |
28 | // Adapted from @toji's DDS utils
29 | // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
30 |
31 | // All values and structures referenced from:
32 | // http://msdn.microsoft.com/en-us/library/bb943991.aspx/
33 |
34 | var DDS_MAGIC = 0x20534444;
35 |
36 | var DDSD_CAPS = 0x1,
37 | DDSD_HEIGHT = 0x2,
38 | DDSD_WIDTH = 0x4,
39 | DDSD_PITCH = 0x8,
40 | DDSD_PIXELFORMAT = 0x1000,
41 | DDSD_MIPMAPCOUNT = 0x20000,
42 | DDSD_LINEARSIZE = 0x80000,
43 | DDSD_DEPTH = 0x800000;
44 |
45 | var DDSCAPS_COMPLEX = 0x8,
46 | DDSCAPS_MIPMAP = 0x400000,
47 | DDSCAPS_TEXTURE = 0x1000;
48 |
49 | var DDSCAPS2_CUBEMAP = 0x200,
50 | DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
51 | DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
52 | DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
53 | DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
54 | DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
55 | DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
56 | DDSCAPS2_VOLUME = 0x200000;
57 |
58 | var DDPF_ALPHAPIXELS = 0x1,
59 | DDPF_ALPHA = 0x2,
60 | DDPF_FOURCC = 0x4,
61 | DDPF_RGB = 0x40,
62 | DDPF_YUV = 0x200,
63 | DDPF_LUMINANCE = 0x20000;
64 |
65 | function fourCCToInt32(value) {
66 |
67 | return value.charCodeAt(0) +
68 | (value.charCodeAt(1) << 8) +
69 | (value.charCodeAt(2) << 16) +
70 | (value.charCodeAt(3) << 24);
71 |
72 | }
73 |
74 | function int32ToFourCC(value) {
75 |
76 | return String.fromCharCode(
77 | value & 0xff,
78 | (value >> 8) & 0xff,
79 | (value >> 16) & 0xff,
80 | (value >> 24) & 0xff
81 | );
82 |
83 | }
84 |
85 | function loadARGBMip(buffer, dataOffset, width, height) {
86 |
87 | var dataLength = width * height * 4;
88 | var srcBuffer = new Uint8Array(buffer, dataOffset, dataLength);
89 | var byteArray = new Uint8Array(dataLength);
90 | var dst = 0;
91 | var src = 0;
92 | for (var y = 0; y < height; y++) {
93 |
94 | for (var x = 0; x < width; x++) {
95 |
96 | var b = srcBuffer[src]; src++;
97 | var g = srcBuffer[src]; src++;
98 | var r = srcBuffer[src]; src++;
99 | var a = srcBuffer[src]; src++;
100 | byteArray[dst] = r; dst++; //r
101 | byteArray[dst] = g; dst++; //g
102 | byteArray[dst] = b; dst++; //b
103 | byteArray[dst] = a; dst++; //a
104 |
105 | }
106 |
107 | }
108 | return byteArray;
109 |
110 | }
111 |
112 | var FOURCC_DXT1 = fourCCToInt32("DXT1");
113 | var FOURCC_DXT3 = fourCCToInt32("DXT3");
114 | var FOURCC_DXT5 = fourCCToInt32("DXT5");
115 | var FOURCC_ETC1 = fourCCToInt32("ETC1");
116 |
117 | var headerLengthInt = 31; // The header length in 32 bit ints
118 |
119 | // Offsets into the header array
120 |
121 | var off_magic = 0;
122 |
123 | var off_size = 1;
124 | var off_flags = 2;
125 | var off_height = 3;
126 | var off_width = 4;
127 |
128 | var off_mipmapCount = 7;
129 |
130 | var off_pfFlags = 20;
131 | var off_pfFourCC = 21;
132 | var off_RGBBitCount = 22;
133 | var off_RBitMask = 23;
134 | var off_GBitMask = 24;
135 | var off_BBitMask = 25;
136 | var off_ABitMask = 26;
137 |
138 | var off_caps = 27;
139 | var off_caps2 = 28;
140 | var off_caps3 = 29;
141 | var off_caps4 = 30;
142 |
143 | // Parse header
144 |
145 | var header = new Int32Array(buffer, 0, headerLengthInt);
146 |
147 | if (header[off_magic] !== DDS_MAGIC) {
148 |
149 | console.error('THREE.DDSLoader.parse: Invalid magic number in DDS header.');
150 | return dds;
151 |
152 | }
153 |
154 | if (!header[off_pfFlags] & DDPF_FOURCC) {
155 |
156 | console.error('THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.');
157 | return dds;
158 |
159 | }
160 |
161 | var blockBytes;
162 |
163 | var fourCC = header[off_pfFourCC];
164 |
165 | var isRGBAUncompressed = false;
166 |
167 | switch (fourCC) {
168 |
169 | case FOURCC_DXT1:
170 |
171 | blockBytes = 8;
172 | dds.format = RGB_S3TC_DXT1_Format;
173 | break;
174 |
175 | case FOURCC_DXT3:
176 |
177 | blockBytes = 16;
178 | dds.format = RGBA_S3TC_DXT3_Format;
179 | break;
180 |
181 | case FOURCC_DXT5:
182 |
183 | blockBytes = 16;
184 | dds.format = RGBA_S3TC_DXT5_Format;
185 | break;
186 |
187 | case FOURCC_ETC1:
188 |
189 | blockBytes = 8;
190 | dds.format = RGB_ETC1_Format;
191 | break;
192 |
193 | default:
194 |
195 | if (header[off_RGBBitCount] === 32
196 | && header[off_RBitMask] & 0xff0000
197 | && header[off_GBitMask] & 0xff00
198 | && header[off_BBitMask] & 0xff
199 | && header[off_ABitMask] & 0xff000000) {
200 |
201 | isRGBAUncompressed = true;
202 | blockBytes = 64;
203 | dds.format = RGBAFormat;
204 |
205 | } else {
206 |
207 | console.error('THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC(fourCC));
208 | return dds;
209 |
210 | }
211 |
212 | }
213 |
214 | dds.mipmapCount = 1;
215 |
216 | if (header[off_flags] & DDSD_MIPMAPCOUNT && loadMipmaps !== false) {
217 |
218 | dds.mipmapCount = Math.max(1, header[off_mipmapCount]);
219 |
220 | }
221 |
222 | var caps2 = header[off_caps2];
223 | dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false;
224 | if (dds.isCubemap && (
225 | !(caps2 & DDSCAPS2_CUBEMAP_POSITIVEX) ||
226 | !(caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX) ||
227 | !(caps2 & DDSCAPS2_CUBEMAP_POSITIVEY) ||
228 | !(caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY) ||
229 | !(caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ) ||
230 | !(caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ)
231 | )) {
232 |
233 | console.error('THREE.DDSLoader.parse: Incomplete cubemap faces');
234 | return dds;
235 |
236 | }
237 |
238 | dds.width = header[off_width];
239 | dds.height = header[off_height];
240 |
241 | var dataOffset = header[off_size] + 4;
242 |
243 | // Extract mipmaps buffers
244 |
245 | var faces = dds.isCubemap ? 6 : 1;
246 |
247 | for (var face = 0; face < faces; face++) {
248 |
249 | var width = dds.width;
250 | var height = dds.height;
251 |
252 | for (var i = 0; i < dds.mipmapCount; i++) {
253 |
254 | if (isRGBAUncompressed) {
255 |
256 | var byteArray = loadARGBMip(buffer, dataOffset, width, height);
257 | var dataLength = byteArray.length;
258 |
259 | } else {
260 |
261 | var dataLength = Math.max(4, width) / 4 * Math.max(4, height) / 4 * blockBytes;
262 | var byteArray = new Uint8Array(buffer, dataOffset, dataLength);
263 |
264 | }
265 |
266 | var mipmap = { "data": byteArray, "width": width, "height": height };
267 | dds.mipmaps.push(mipmap);
268 |
269 | dataOffset += dataLength;
270 |
271 | width = Math.max(width >> 1, 1);
272 | height = Math.max(height >> 1, 1);
273 |
274 | }
275 |
276 | }
277 |
278 | return dds;
279 |
280 | }
281 |
282 | });
283 |
284 | return { DDSLoader };
285 | }
--------------------------------------------------------------------------------
/jsm/pmrem/PMREMCubeUVPacker.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Prashant Sharma / spidersharma03
3 | * @author Ben Houston / bhouston, https://clara.io
4 | *
5 | * This class takes the cube lods(corresponding to different roughness values), and creates a single cubeUV
6 | * Texture. The format for a given roughness set of faces is simply::
7 | * +X+Y+Z
8 | * -X-Y-Z
9 | * For every roughness a mip map chain is also saved, which is essential to remove the texture artifacts due to
10 | * minification.
11 | * Right now for every face a PlaneMesh is drawn, which leads to a lot of geometry draw calls, but can be replaced
12 | * later by drawing a single buffer and by sending the appropriate faceIndex via vertex attributes.
13 | * The arrangement of the faces is fixed, as assuming this arrangement, the sampling function has been written.
14 | */
15 | export default function (THREE) {
16 | let {
17 | BackSide,
18 | CubeUVReflectionMapping,
19 | LinearFilter,
20 | LinearToneMapping,
21 | Mesh,
22 | NoBlending,
23 | OrthographicCamera,
24 | PlaneBufferGeometry,
25 | RGBEEncoding,
26 | RGBM16Encoding,
27 | Scene,
28 | ShaderMaterial,
29 | Vector2,
30 | Vector3,
31 | WebGLRenderTarget
32 | } = THREE;
33 |
34 | var PMREMCubeUVPacker = ( function () {
35 |
36 | var camera = new OrthographicCamera();
37 | var scene = new Scene();
38 | var shader = getShader();
39 |
40 | var PMREMCubeUVPacker = function ( cubeTextureLods ) {
41 |
42 | this.cubeLods = cubeTextureLods;
43 | var size = cubeTextureLods[ 0 ].width * 4;
44 |
45 | var sourceTexture = cubeTextureLods[ 0 ].texture;
46 | var params = {
47 | format: sourceTexture.format,
48 | magFilter: sourceTexture.magFilter,
49 | minFilter: sourceTexture.minFilter,
50 | type: sourceTexture.type,
51 | generateMipmaps: sourceTexture.generateMipmaps,
52 | anisotropy: sourceTexture.anisotropy,
53 | encoding: ( sourceTexture.encoding === RGBEEncoding ) ? RGBM16Encoding : sourceTexture.encoding
54 | };
55 |
56 | if ( params.encoding === RGBM16Encoding ) {
57 |
58 | params.magFilter = LinearFilter;
59 | params.minFilter = LinearFilter;
60 |
61 | }
62 |
63 | this.CubeUVRenderTarget = new WebGLRenderTarget( size, size, params );
64 | this.CubeUVRenderTarget.texture.name = "PMREMCubeUVPacker.cubeUv";
65 | this.CubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping;
66 |
67 | this.objects = [];
68 |
69 | var geometry = new PlaneBufferGeometry( 1, 1 );
70 |
71 | var faceOffsets = [];
72 | faceOffsets.push( new Vector2( 0, 0 ) );
73 | faceOffsets.push( new Vector2( 1, 0 ) );
74 | faceOffsets.push( new Vector2( 2, 0 ) );
75 | faceOffsets.push( new Vector2( 0, 1 ) );
76 | faceOffsets.push( new Vector2( 1, 1 ) );
77 | faceOffsets.push( new Vector2( 2, 1 ) );
78 |
79 | var textureResolution = size;
80 | size = cubeTextureLods[ 0 ].width;
81 |
82 | var offset2 = 0;
83 | var c = 4.0;
84 | this.numLods = Math.log( cubeTextureLods[ 0 ].width ) / Math.log( 2 ) - 2; // IE11 doesn't support Math.log2
85 | for ( var i = 0; i < this.numLods; i ++ ) {
86 |
87 | var offset1 = ( textureResolution - textureResolution / c ) * 0.5;
88 | if ( size > 16 ) c *= 2;
89 | var nMips = size > 16 ? 6 : 1;
90 | var mipOffsetX = 0;
91 | var mipOffsetY = 0;
92 | var mipSize = size;
93 |
94 | for ( var j = 0; j < nMips; j ++ ) {
95 |
96 | // Mip Maps
97 | for ( var k = 0; k < 6; k ++ ) {
98 |
99 | // 6 Cube Faces
100 | var material = shader.clone();
101 | material.uniforms[ 'envMap' ].value = this.cubeLods[ i ].texture;
102 | material.envMap = this.cubeLods[ i ].texture;
103 | material.uniforms[ 'faceIndex' ].value = k;
104 | material.uniforms[ 'mapSize' ].value = mipSize;
105 |
106 | var planeMesh = new Mesh( geometry, material );
107 | planeMesh.position.x = faceOffsets[ k ].x * mipSize - offset1 + mipOffsetX;
108 | planeMesh.position.y = faceOffsets[ k ].y * mipSize - offset1 + offset2 + mipOffsetY;
109 | planeMesh.material.side = BackSide;
110 | planeMesh.scale.setScalar( mipSize );
111 | this.objects.push( planeMesh );
112 |
113 | }
114 | mipOffsetY += 1.75 * mipSize;
115 | mipOffsetX += 1.25 * mipSize;
116 | mipSize /= 2;
117 |
118 | }
119 | offset2 += 2 * size;
120 | if ( size > 16 ) size /= 2;
121 |
122 | }
123 |
124 | };
125 |
126 | PMREMCubeUVPacker.prototype = {
127 |
128 | constructor: PMREMCubeUVPacker,
129 |
130 | update: function ( renderer ) {
131 |
132 | var size = this.cubeLods[ 0 ].width * 4;
133 | // top and bottom are swapped for some reason?
134 | camera.left = - size * 0.5;
135 | camera.right = size * 0.5;
136 | camera.top = - size * 0.5;
137 | camera.bottom = size * 0.5;
138 | camera.near = 0;
139 | camera.far = 1;
140 | camera.updateProjectionMatrix();
141 |
142 | for ( var i = 0; i < this.objects.length; i ++ ) {
143 |
144 | scene.add( this.objects[ i ] );
145 |
146 | }
147 |
148 | var gammaInput = renderer.gammaInput;
149 | var gammaOutput = renderer.gammaOutput;
150 | var toneMapping = renderer.toneMapping;
151 | var toneMappingExposure = renderer.toneMappingExposure;
152 | var currentRenderTarget = renderer.getRenderTarget();
153 |
154 | renderer.gammaInput = false;
155 | renderer.gammaOutput = false;
156 | renderer.toneMapping = LinearToneMapping;
157 | renderer.toneMappingExposure = 1.0;
158 | renderer.setRenderTarget( this.CubeUVRenderTarget );
159 | renderer.render( scene, camera );
160 |
161 | renderer.setRenderTarget( currentRenderTarget );
162 | renderer.toneMapping = toneMapping;
163 | renderer.toneMappingExposure = toneMappingExposure;
164 | renderer.gammaInput = gammaInput;
165 | renderer.gammaOutput = gammaOutput;
166 |
167 | for ( var i = 0; i < this.objects.length; i ++ ) {
168 |
169 | scene.remove( this.objects[ i ] );
170 |
171 | }
172 |
173 | },
174 |
175 | dispose: function () {
176 |
177 | for ( var i = 0, l = this.objects.length; i < l; i ++ ) {
178 |
179 | this.objects[ i ].material.dispose();
180 |
181 | }
182 |
183 | this.objects[ 0 ].geometry.dispose();
184 |
185 | }
186 |
187 | };
188 |
189 | function getShader() {
190 |
191 | var shaderMaterial = new ShaderMaterial( {
192 |
193 | uniforms: {
194 | "faceIndex": { value: 0 },
195 | "mapSize": { value: 0 },
196 | "envMap": { value: null },
197 | "testColor": { value: new Vector3( 1, 1, 1 ) }
198 | },
199 |
200 | vertexShader:
201 | "precision highp float;\
202 | varying vec2 vUv;\
203 | void main() {\
204 | vUv = uv;\
205 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\
206 | }",
207 |
208 | fragmentShader:
209 | "precision highp float;\
210 | varying vec2 vUv;\
211 | uniform samplerCube envMap;\
212 | uniform float mapSize;\
213 | uniform vec3 testColor;\
214 | uniform int faceIndex;\
215 | \
216 | void main() {\
217 | vec3 sampleDirection;\
218 | vec2 uv = vUv;\
219 | uv = uv * 2.0 - 1.0;\
220 | uv.y *= -1.0;\
221 | if(faceIndex == 0) {\
222 | sampleDirection = normalize(vec3(1.0, uv.y, -uv.x));\
223 | } else if(faceIndex == 1) {\
224 | sampleDirection = normalize(vec3(uv.x, 1.0, uv.y));\
225 | } else if(faceIndex == 2) {\
226 | sampleDirection = normalize(vec3(uv.x, uv.y, 1.0));\
227 | } else if(faceIndex == 3) {\
228 | sampleDirection = normalize(vec3(-1.0, uv.y, uv.x));\
229 | } else if(faceIndex == 4) {\
230 | sampleDirection = normalize(vec3(uv.x, -1.0, -uv.y));\
231 | } else {\
232 | sampleDirection = normalize(vec3(-uv.x, uv.y, -1.0));\
233 | }\
234 | vec4 color = envMapTexelToLinear( textureCube( envMap, sampleDirection ) );\
235 | gl_FragColor = linearToOutputTexel( color );\
236 | }",
237 |
238 | blending: NoBlending
239 |
240 | } );
241 |
242 | shaderMaterial.type = 'PMREMCubeUVPacker';
243 |
244 | return shaderMaterial;
245 |
246 | }
247 |
248 |
249 | return PMREMCubeUVPacker;
250 |
251 | } )();
252 |
253 | return { PMREMCubeUVPacker };
254 | }
--------------------------------------------------------------------------------
/jsm/pmrem/PMREMGenerator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Prashant Sharma / spidersharma03
3 | * @author Ben Houston / bhouston, https://clara.io
4 | *
5 | * To avoid cube map seams, I create an extra pixel around each face. This way when the cube map is
6 | * sampled by an application later(with a little care by sampling the centre of the texel), the extra 1 border
7 | * of pixels makes sure that there is no seams artifacts present. This works perfectly for cubeUV format as
8 | * well where the 6 faces can be arranged in any manner whatsoever.
9 | * Code in the beginning of fragment shader's main function does this job for a given resolution.
10 | * Run Scene_PMREM_Test.html in the examples directory to see the sampling from the cube lods generated
11 | * by this class.
12 | */
13 | export default function (THREE) {
14 | let {
15 | DoubleSide,
16 | GammaEncoding,
17 | LinearEncoding,
18 | LinearFilter,
19 | LinearToneMapping,
20 | Mesh,
21 | NearestFilter,
22 | NoBlending,
23 | OrthographicCamera,
24 | PlaneBufferGeometry,
25 | Scene,
26 | ShaderMaterial,
27 | WebGLRenderTargetCube,
28 | sRGBEncoding
29 | } = THREE;
30 |
31 | var PMREMGenerator = ( function () {
32 |
33 | var shader = getShader();
34 | var camera = new OrthographicCamera( - 1, 1, 1, - 1, 0.0, 1000 );
35 | var scene = new Scene();
36 | var planeMesh = new Mesh( new PlaneBufferGeometry( 2, 2, 0 ), shader );
37 | planeMesh.material.side = DoubleSide;
38 | scene.add( planeMesh );
39 | scene.add( camera );
40 |
41 | var PMREMGenerator = function ( sourceTexture, samplesPerLevel, resolution ) {
42 |
43 | this.sourceTexture = sourceTexture;
44 | this.resolution = ( resolution !== undefined ) ? resolution : 256; // NODE: 256 is currently hard coded in the glsl code for performance reasons
45 | this.samplesPerLevel = ( samplesPerLevel !== undefined ) ? samplesPerLevel : 32;
46 |
47 | var monotonicEncoding = ( this.sourceTexture.encoding === LinearEncoding ) ||
48 | ( this.sourceTexture.encoding === GammaEncoding ) || ( this.sourceTexture.encoding === sRGBEncoding );
49 |
50 | this.sourceTexture.minFilter = ( monotonicEncoding ) ? LinearFilter : NearestFilter;
51 | this.sourceTexture.magFilter = ( monotonicEncoding ) ? LinearFilter : NearestFilter;
52 | this.sourceTexture.generateMipmaps = this.sourceTexture.generateMipmaps && monotonicEncoding;
53 |
54 | this.cubeLods = [];
55 |
56 | var size = this.resolution;
57 | var params = {
58 | format: this.sourceTexture.format,
59 | magFilter: this.sourceTexture.magFilter,
60 | minFilter: this.sourceTexture.minFilter,
61 | type: this.sourceTexture.type,
62 | generateMipmaps: this.sourceTexture.generateMipmaps,
63 | anisotropy: this.sourceTexture.anisotropy,
64 | encoding: this.sourceTexture.encoding
65 | };
66 |
67 | // how many LODs fit in the given CubeUV Texture.
68 | this.numLods = Math.log( size ) / Math.log( 2 ) - 2; // IE11 doesn't support Math.log2
69 |
70 | for ( var i = 0; i < this.numLods; i ++ ) {
71 |
72 | var renderTarget = new WebGLRenderTargetCube( size, size, params );
73 | renderTarget.texture.name = "PMREMGenerator.cube" + i;
74 | this.cubeLods.push( renderTarget );
75 | size = Math.max( 16, size / 2 );
76 |
77 | }
78 |
79 | };
80 |
81 | PMREMGenerator.prototype = {
82 |
83 | constructor: PMREMGenerator,
84 |
85 | /*
86 | * Prashant Sharma / spidersharma03: More thought and work is needed here.
87 | * Right now it's a kind of a hack to use the previously convolved map to convolve the current one.
88 | * I tried to use the original map to convolve all the lods, but for many textures(specially the high frequency)
89 | * even a high number of samples(1024) dosen't lead to satisfactory results.
90 | * By using the previous convolved maps, a lower number of samples are generally sufficient(right now 32, which
91 | * gives okay results unless we see the reflection very carefully, or zoom in too much), however the math
92 | * goes wrong as the distribution function tries to sample a larger area than what it should be. So I simply scaled
93 | * the roughness by 0.9(totally empirical) to try to visually match the original result.
94 | * The condition "if(i <5)" is also an attemt to make the result match the original result.
95 | * This method requires the most amount of thinking I guess. Here is a paper which we could try to implement in future::
96 | * https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html
97 | */
98 | update: function ( renderer ) {
99 |
100 | // Texture should only be flipped for CubeTexture, not for
101 | // a Texture created via WebGLRenderTargetCube.
102 | var tFlip = ( this.sourceTexture.isCubeTexture ) ? - 1 : 1;
103 |
104 | shader.defines[ 'SAMPLES_PER_LEVEL' ] = this.samplesPerLevel;
105 | shader.uniforms[ 'faceIndex' ].value = 0;
106 | shader.uniforms[ 'envMap' ].value = this.sourceTexture;
107 | shader.envMap = this.sourceTexture;
108 | shader.needsUpdate = true;
109 |
110 | var gammaInput = renderer.gammaInput;
111 | var gammaOutput = renderer.gammaOutput;
112 | var toneMapping = renderer.toneMapping;
113 | var toneMappingExposure = renderer.toneMappingExposure;
114 | var currentRenderTarget = renderer.getRenderTarget();
115 |
116 | renderer.toneMapping = LinearToneMapping;
117 | renderer.toneMappingExposure = 1.0;
118 | renderer.gammaInput = false;
119 | renderer.gammaOutput = false;
120 |
121 | for ( var i = 0; i < this.numLods; i ++ ) {
122 |
123 | var r = i / ( this.numLods - 1 );
124 | shader.uniforms[ 'roughness' ].value = r * 0.9; // see comment above, pragmatic choice
125 | // Only apply the tFlip for the first LOD
126 | shader.uniforms[ 'tFlip' ].value = ( i == 0 ) ? tFlip : 1;
127 | var size = this.cubeLods[ i ].width;
128 | shader.uniforms[ 'mapSize' ].value = size;
129 | this.renderToCubeMapTarget( renderer, this.cubeLods[ i ] );
130 |
131 | if ( i < 5 ) shader.uniforms[ 'envMap' ].value = this.cubeLods[ i ].texture;
132 |
133 | }
134 |
135 | renderer.setRenderTarget( currentRenderTarget );
136 | renderer.toneMapping = toneMapping;
137 | renderer.toneMappingExposure = toneMappingExposure;
138 | renderer.gammaInput = gammaInput;
139 | renderer.gammaOutput = gammaOutput;
140 |
141 | },
142 |
143 | renderToCubeMapTarget: function ( renderer, renderTarget ) {
144 |
145 | for ( var i = 0; i < 6; i ++ ) {
146 |
147 | this.renderToCubeMapTargetFace( renderer, renderTarget, i );
148 |
149 | }
150 |
151 | },
152 |
153 | renderToCubeMapTargetFace: function ( renderer, renderTarget, faceIndex ) {
154 |
155 | shader.uniforms[ 'faceIndex' ].value = faceIndex;
156 | renderer.setRenderTarget( renderTarget, faceIndex );
157 | renderer.clear();
158 | renderer.render( scene, camera );
159 |
160 | },
161 |
162 | dispose: function () {
163 |
164 | for ( var i = 0, l = this.cubeLods.length; i < l; i ++ ) {
165 |
166 | this.cubeLods[ i ].dispose();
167 |
168 | }
169 |
170 | },
171 |
172 | };
173 |
174 | function getShader() {
175 |
176 | var shaderMaterial = new ShaderMaterial( {
177 |
178 | defines: {
179 | "SAMPLES_PER_LEVEL": 20,
180 | },
181 |
182 | uniforms: {
183 | "faceIndex": { value: 0 },
184 | "roughness": { value: 0.5 },
185 | "mapSize": { value: 0.5 },
186 | "envMap": { value: null },
187 | "tFlip": { value: - 1 },
188 | },
189 |
190 | vertexShader:
191 | "varying vec2 vUv;\n\
192 | void main() {\n\
193 | vUv = uv;\n\
194 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\
195 | }",
196 |
197 | fragmentShader:
198 | "#include \n\
199 | varying vec2 vUv;\n\
200 | uniform int faceIndex;\n\
201 | uniform float roughness;\n\
202 | uniform samplerCube envMap;\n\
203 | uniform float mapSize;\n\
204 | uniform float tFlip;\n\
205 | \n\
206 | float GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\
207 | float a = ggxRoughness + 0.0001;\n\
208 | a *= a;\n\
209 | return ( 2.0 / a - 2.0 );\n\
210 | }\n\
211 | vec3 ImportanceSamplePhong(vec2 uv, mat3 vecSpace, float specPow) {\n\
212 | float phi = uv.y * 2.0 * PI;\n\
213 | float cosTheta = pow(1.0 - uv.x, 1.0 / (specPow + 1.0));\n\
214 | float sinTheta = sqrt(1.0 - cosTheta * cosTheta);\n\
215 | vec3 sampleDir = vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);\n\
216 | return vecSpace * sampleDir;\n\
217 | }\n\
218 | vec3 ImportanceSampleGGX( vec2 uv, mat3 vecSpace, float Roughness )\n\
219 | {\n\
220 | float a = Roughness * Roughness;\n\
221 | float Phi = 2.0 * PI * uv.x;\n\
222 | float CosTheta = sqrt( (1.0 - uv.y) / ( 1.0 + (a*a - 1.0) * uv.y ) );\n\
223 | float SinTheta = sqrt( 1.0 - CosTheta * CosTheta );\n\
224 | return vecSpace * vec3(SinTheta * cos( Phi ), SinTheta * sin( Phi ), CosTheta);\n\
225 | }\n\
226 | mat3 matrixFromVector(vec3 n) {\n\
227 | float a = 1.0 / (1.0 + n.z);\n\
228 | float b = -n.x * n.y * a;\n\
229 | vec3 b1 = vec3(1.0 - n.x * n.x * a, b, -n.x);\n\
230 | vec3 b2 = vec3(b, 1.0 - n.y * n.y * a, -n.y);\n\
231 | return mat3(b1, b2, n);\n\
232 | }\n\
233 | \n\
234 | vec4 testColorMap(float Roughness) {\n\
235 | vec4 color;\n\
236 | if(faceIndex == 0)\n\
237 | color = vec4(1.0,0.0,0.0,1.0);\n\
238 | else if(faceIndex == 1)\n\
239 | color = vec4(0.0,1.0,0.0,1.0);\n\
240 | else if(faceIndex == 2)\n\
241 | color = vec4(0.0,0.0,1.0,1.0);\n\
242 | else if(faceIndex == 3)\n\
243 | color = vec4(1.0,1.0,0.0,1.0);\n\
244 | else if(faceIndex == 4)\n\
245 | color = vec4(0.0,1.0,1.0,1.0);\n\
246 | else\n\
247 | color = vec4(1.0,0.0,1.0,1.0);\n\
248 | color *= ( 1.0 - Roughness );\n\
249 | return color;\n\
250 | }\n\
251 | void main() {\n\
252 | vec3 sampleDirection;\n\
253 | vec2 uv = vUv*2.0 - 1.0;\n\
254 | float offset = -1.0/mapSize;\n\
255 | const float a = -1.0;\n\
256 | const float b = 1.0;\n\
257 | float c = -1.0 + offset;\n\
258 | float d = 1.0 - offset;\n\
259 | float bminusa = b - a;\n\
260 | uv.x = (uv.x - a)/bminusa * d - (uv.x - b)/bminusa * c;\n\
261 | uv.y = (uv.y - a)/bminusa * d - (uv.y - b)/bminusa * c;\n\
262 | if (faceIndex==0) {\n\
263 | sampleDirection = vec3(1.0, -uv.y, -uv.x);\n\
264 | } else if (faceIndex==1) {\n\
265 | sampleDirection = vec3(-1.0, -uv.y, uv.x);\n\
266 | } else if (faceIndex==2) {\n\
267 | sampleDirection = vec3(uv.x, 1.0, uv.y);\n\
268 | } else if (faceIndex==3) {\n\
269 | sampleDirection = vec3(uv.x, -1.0, -uv.y);\n\
270 | } else if (faceIndex==4) {\n\
271 | sampleDirection = vec3(uv.x, -uv.y, 1.0);\n\
272 | } else {\n\
273 | sampleDirection = vec3(-uv.x, -uv.y, -1.0);\n\
274 | }\n\
275 | vec3 correctedDirection = vec3( tFlip * sampleDirection.x, sampleDirection.yz );\n\
276 | mat3 vecSpace = matrixFromVector( normalize( correctedDirection ) );\n\
277 | vec3 rgbColor = vec3(0.0);\n\
278 | const int NumSamples = SAMPLES_PER_LEVEL;\n\
279 | vec3 vect;\n\
280 | float weight = 0.0;\n\
281 | for( int i = 0; i < NumSamples; i ++ ) {\n\
282 | float sini = sin(float(i));\n\
283 | float cosi = cos(float(i));\n\
284 | float r = rand(vec2(sini, cosi));\n\
285 | vect = ImportanceSampleGGX(vec2(float(i) / float(NumSamples), r), vecSpace, roughness);\n\
286 | float dotProd = dot(vect, normalize(sampleDirection));\n\
287 | weight += dotProd;\n\
288 | vec3 color = envMapTexelToLinear(textureCube(envMap, vect)).rgb;\n\
289 | rgbColor.rgb += color;\n\
290 | }\n\
291 | rgbColor /= float(NumSamples);\n\
292 | //rgbColor = testColorMap( roughness ).rgb;\n\
293 | gl_FragColor = linearToOutputTexel( vec4( rgbColor, 1.0 ) );\n\
294 | }",
295 |
296 | blending: NoBlending
297 |
298 | } );
299 |
300 | shaderMaterial.type = 'PMREMGenerator';
301 |
302 | return shaderMaterial;
303 |
304 | }
305 |
306 | return PMREMGenerator;
307 |
308 | } )();
309 |
310 | return { PMREMGenerator };
311 | }
--------------------------------------------------------------------------------
/pages/animation/animate.js:
--------------------------------------------------------------------------------
1 | import getSkeletonUtils from '../../jsm/utils/SkeletonUtils.js';
2 |
3 | import getGLTFLoader from '../../jsm/loaders/GLTFLoader'
4 | import { OrbitControls } from '../../jsm/controls/OrbitControls'
5 |
6 |
7 | export default function (canvas, THREE) {
8 | let GLTFLoader = getGLTFLoader(THREE);
9 | let { SkeletonUtils } = getSkeletonUtils(THREE);
10 | let window = THREE.global;
11 |
12 |
13 | //////////////////////////////
14 | // Global objects
15 | //////////////////////////////
16 | var worldScene = null; // THREE.Scene where it all will be rendered
17 | var renderer = null;
18 | var camera = null;
19 | var clock = null;
20 | var mixers = []; // All the THREE.AnimationMixer objects for all the animations in the scene
21 | //////////////////////////////
22 |
23 |
24 | //////////////////////////////
25 | // Information about our 3D models and units
26 | //////////////////////////////
27 |
28 | // The names of the 3D models to load. One-per file.
29 | // A model may have multiple SkinnedMesh objects as well as several rigs (armatures). Units will define which
30 | // meshes, armatures and animations to use. We will load the whole scene for each object and clone it for each unit.
31 | // Models are from https://www.mixamo.com/
32 | var MODELS = [
33 | { name: "Soldier" },
34 | { name: "Parrot" },
35 | // { name: "RiflePunch" },
36 | ];
37 |
38 | // Here we define instances of the models that we want to place in the scene, their position, scale and the animations
39 | // that must be played.
40 | var UNITS = [
41 | {
42 | modelName: "Soldier", // Will use the 3D model from file models/gltf/Soldier.glb
43 | meshName: "vanguard_Mesh", // Name of the main mesh to animate
44 | position: { x: 0, y: 2, z: 0 }, // Where to put the unit in the scene
45 | scale: 1, // Scaling of the unit. 1.0 means: use original size, 0.1 means "10 times smaller", etc.
46 | animationName: "Idle" // Name of animation to run
47 | },
48 | {
49 | modelName: "Soldier",
50 | meshName: "vanguard_Mesh",
51 | position: { x: 1, y: -2, z: 0 },
52 | scale: 2,
53 | animationName: "Walk"
54 | },
55 | {
56 | modelName: "Soldier",
57 | meshName: "vanguard_Mesh",
58 | position: { x: 1, y: 2, z: 0 },
59 | scale: 1,
60 | animationName: "Run"
61 | },
62 | {
63 | modelName: "Parrot",
64 | meshName: "mesh_0",
65 | position: { x: - 2, y: 0, z: 0 },
66 | rotation: { x: 0, y: Math.PI, z: 0 },
67 | scale: 0.01,
68 | animationName: "parrot_A_"
69 | },
70 | {
71 | modelName: "Parrot",
72 | meshName: "mesh_0",
73 | position: { x: - 1, y: 3, z: 0 },
74 | rotation: { x: 0, y: Math.PI / 2, z: 0 },
75 | scale: 0.02,
76 | animationName: null
77 | },
78 | ];
79 |
80 | //////////////////////////////
81 | // The main setup happens here
82 | //////////////////////////////
83 | var numLoadedModels = 0;
84 | initScene();
85 | initRenderer();
86 | loadModels();
87 | animate();
88 | //////////////////////////////
89 |
90 |
91 | //////////////////////////////
92 | // Function implementations
93 | //////////////////////////////
94 | /**
95 | * Function that starts loading process for the next model in the queue. The loading process is
96 | * asynchronous: it happens "in the background". Therefore we don't load all the models at once. We load one,
97 | * wait until it is done, then load the next one. When all models are loaded, we call loadUnits().
98 | */
99 | function loadModels() {
100 |
101 | for (var i = 0; i < MODELS.length; ++i) {
102 |
103 | var m = MODELS[i];
104 |
105 | loadGltfModel(m, function () {
106 |
107 | ++numLoadedModels;
108 |
109 | if (numLoadedModels === MODELS.length) {
110 |
111 | console.log("All models loaded, time to instantiate units...");
112 | instantiateUnits();
113 |
114 | }
115 |
116 | });
117 |
118 | }
119 |
120 | }
121 |
122 | /**
123 | * Look at UNITS configuration, clone necessary 3D model scenes, place the armatures and meshes in the scene and
124 | * launch necessary animations
125 | */
126 | function instantiateUnits() {
127 |
128 | var numSuccess = 0;
129 |
130 | for (var i = 0; i < UNITS.length; ++i) {
131 |
132 | var u = UNITS[i];
133 | var model = getModelByName(u.modelName);
134 |
135 | if (model) {
136 |
137 | var clonedScene = SkeletonUtils.clone(model.scene);
138 |
139 | if (clonedScene) {
140 |
141 | // THREE.Scene is cloned properly, let's find one mesh and launch animation for it
142 | var clonedMesh = clonedScene.getObjectByName(u.meshName);
143 |
144 | if (clonedMesh) {
145 |
146 | var mixer = startAnimation(clonedMesh, model.animations, u.animationName);
147 |
148 | // Save the animation mixer in the list, will need it in the animation loop
149 | mixers.push(mixer);
150 | numSuccess++;
151 |
152 | }
153 |
154 | // Different models can have different configurations of armatures and meshes. Therefore,
155 | // We can't set position, scale or rotation to individual mesh objects. Instead we set
156 | // it to the whole cloned scene and then add the whole scene to the game world
157 | // Note: this may have weird effects if you have lights or other items in the GLTF file's scene!
158 | worldScene.add(clonedScene);
159 |
160 | if (u.position) {
161 |
162 | clonedScene.position.set(u.position.x, u.position.y, u.position.z);
163 |
164 | }
165 |
166 | if (u.scale) {
167 |
168 | clonedScene.scale.set(u.scale, u.scale, u.scale);
169 |
170 | }
171 |
172 | if (u.rotation) {
173 |
174 | clonedScene.rotation.x = u.rotation.x;
175 | clonedScene.rotation.y = u.rotation.y;
176 | clonedScene.rotation.z = u.rotation.z;
177 |
178 | }
179 |
180 | }
181 |
182 | } else {
183 |
184 | console.error("Can not find model", u.modelName);
185 |
186 | }
187 |
188 | }
189 |
190 | console.log(`Successfully instantiated ${numSuccess} units`);
191 |
192 | }
193 |
194 | /**
195 | * Start animation for a specific mesh object. Find the animation by name in the 3D model's animation array
196 | * @param skinnedMesh {THREE.SkinnedMesh} The mesh to animate
197 | * @param animations {Array} Array containing all the animations for this model
198 | * @param animationName {string} Name of the animation to launch
199 | * @return {THREE.AnimationMixer} Mixer to be used in the render loop
200 | */
201 | function startAnimation(skinnedMesh, animations, animationName) {
202 |
203 | var mixer = new THREE.AnimationMixer(skinnedMesh);
204 | var clip = THREE.AnimationClip.findByName(animations, animationName);
205 |
206 | if (clip) {
207 |
208 | var action = mixer.clipAction(clip);
209 | action.play();
210 |
211 | }
212 |
213 | return mixer;
214 |
215 | }
216 |
217 | /**
218 | * Find a model object by name
219 | * @param name
220 | * @returns {object|null}
221 | */
222 | function getModelByName(name) {
223 |
224 | for (var i = 0; i < MODELS.length; ++i) {
225 |
226 | if (MODELS[i].name === name) {
227 |
228 | return MODELS[i];
229 |
230 | }
231 |
232 | }
233 |
234 | return null;
235 |
236 | }
237 |
238 | /**
239 | * Load a 3D model from a GLTF file. Use the GLTFLoader.
240 | * @param model {object} Model config, one item from the MODELS array. It will be updated inside the function!
241 | * @param onLoaded {function} A callback function that will be called when the model is loaded
242 | */
243 | function loadGltfModel(model, onLoaded) {
244 |
245 | var loader = new GLTFLoader();
246 | var modelName = "models/gltf/" + model.name + ".glb";
247 |
248 | loader.load("https://threejs.org/examples/" + modelName, function (gltf) {
249 |
250 | var scene = gltf.scene;
251 |
252 | model.animations = gltf.animations;
253 | model.scene = scene;
254 |
255 | // Enable Shadows
256 |
257 | gltf.scene.traverse(function (object) {
258 |
259 | if (object.isMesh) {
260 |
261 | object.castShadow = true;
262 |
263 | }
264 |
265 | });
266 |
267 | console.log("Done loading model", model.name);
268 |
269 | onLoaded();
270 |
271 | });
272 |
273 | }
274 |
275 | /**
276 | * Render loop. Renders the next frame of all animations
277 | */
278 | function animate() {
279 |
280 | canvas.requestAnimationFrame(animate);
281 |
282 | // Get the time elapsed since the last frame
283 |
284 | var mixerUpdateDelta = clock.getDelta();
285 |
286 | // Update all the animation frames
287 |
288 | for (var i = 0; i < mixers.length; ++i) {
289 |
290 | mixers[i].update(mixerUpdateDelta);
291 |
292 | }
293 |
294 | renderer.render(worldScene, camera);
295 |
296 | }
297 |
298 | //////////////////////////////
299 | // General Three.JS stuff
300 | //////////////////////////////
301 | // This part is not anyhow related to the cloning of models, it's just setting up the scene.
302 |
303 | /**
304 | * Initialize ThreeJS scene renderer
305 | */
306 | function initRenderer() {
307 |
308 | renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
309 | renderer.setPixelRatio(window.devicePixelRatio);
310 | renderer.setSize(window.innerWidth, window.innerHeight);
311 | renderer.gammaOutput = true;
312 | renderer.gammaFactor = 2.2;
313 | renderer.shadowMap.enabled = true;
314 | renderer.shadowMap.type = THREE.PCFSoftShadowMap;
315 |
316 | }
317 |
318 | /**
319 | * Initialize ThreeJS THREE.Scene
320 | */
321 | function initScene() {
322 |
323 | camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
324 | camera.position.set(3, 6, - 10);
325 | camera.lookAt(0, 1, 0);
326 |
327 | clock = new THREE.Clock();
328 |
329 | worldScene = new THREE.Scene();
330 | worldScene.background = new THREE.Color(0xa0a0a0);
331 | worldScene.fog = new THREE.Fog(0xa0a0a0, 10, 22);
332 |
333 | var hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
334 | hemiLight.position.set(0, 20, 0);
335 | worldScene.add(hemiLight);
336 |
337 | var dirLight = new THREE.DirectionalLight(0xffffff);
338 | dirLight.position.set(- 3, 10, - 10);
339 | dirLight.castShadow = true;
340 | dirLight.shadow.camera.top = 10;
341 | dirLight.shadow.camera.bottom = - 10;
342 | dirLight.shadow.camera.left = - 10;
343 | dirLight.shadow.camera.right = 10;
344 | dirLight.shadow.camera.near = 0.1;
345 | dirLight.shadow.camera.far = 40;
346 | worldScene.add(dirLight);
347 |
348 | // ground
349 | var groundMesh = new THREE.Mesh(
350 | new THREE.PlaneBufferGeometry(40, 40),
351 | new THREE.MeshPhongMaterial({
352 | color: 0x999999,
353 | depthWrite: false
354 | })
355 | );
356 |
357 | groundMesh.rotation.x = - Math.PI / 2;
358 | groundMesh.receiveShadow = true;
359 | worldScene.add(groundMesh);
360 | window.addEventListener('resize', onWindowResize, false);
361 |
362 | }
363 |
364 | /**
365 | * A callback that will be called whenever the browser window is resized.
366 | */
367 | function onWindowResize() {
368 |
369 | camera.aspect = window.innerWidth / window.innerHeight;
370 | camera.updateProjectionMatrix();
371 | renderer.setSize(window.innerWidth, window.innerHeight);
372 |
373 | }
374 | }
--------------------------------------------------------------------------------
/jsm/loaders/MTLLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Loads a Wavefront .mtl file specifying materials
3 | *
4 | * @author angelxuanchang
5 | */
6 | export default function (THREE) {
7 | let {
8 | Color,
9 | DefaultLoadingManager,
10 | FileLoader,
11 | FrontSide,
12 | Loader,
13 | LoaderUtils,
14 | MeshPhongMaterial,
15 | RepeatWrapping,
16 | TextureLoader,
17 | Vector2
18 | } = THREE;
19 |
20 | var MTLLoader = function (manager) {
21 |
22 | Loader.call(this, manager);
23 |
24 | };
25 |
26 | MTLLoader.prototype = Object.assign(Object.create(Loader.prototype), {
27 |
28 | constructor: MTLLoader,
29 |
30 | /**
31 | * Loads and parses a MTL asset from a URL.
32 | *
33 | * @param {String} url - URL to the MTL file.
34 | * @param {Function} [onLoad] - Callback invoked with the loaded object.
35 | * @param {Function} [onProgress] - Callback for download progress.
36 | * @param {Function} [onError] - Callback for download errors.
37 | *
38 | * @see setPath setResourcePath
39 | *
40 | * @note In order for relative texture references to resolve correctly
41 | * you must call setResourcePath() explicitly prior to load.
42 | */
43 | load: function (url, onLoad, onProgress, onError) {
44 |
45 | var scope = this;
46 |
47 | var path = (this.path === '') ? LoaderUtils.extractUrlBase(url) : this.path;
48 |
49 | var loader = new FileLoader(this.manager);
50 | loader.setPath(this.path);
51 | loader.load(url, function (text) {
52 |
53 | onLoad(scope.parse(text, path));
54 |
55 | }, onProgress, onError);
56 |
57 | },
58 |
59 | setMaterialOptions: function (value) {
60 |
61 | this.materialOptions = value;
62 | return this;
63 |
64 | },
65 |
66 | /**
67 | * Parses a MTL file.
68 | *
69 | * @param {String} text - Content of MTL file
70 | * @return {MTLLoader.MaterialCreator}
71 | *
72 | * @see setPath setResourcePath
73 | *
74 | * @note In order for relative texture references to resolve correctly
75 | * you must call setResourcePath() explicitly prior to parse.
76 | */
77 | parse: function (text, path) {
78 |
79 | var lines = text.split('\n');
80 | var info = {};
81 | var delimiter_pattern = /\s+/;
82 | var materialsInfo = {};
83 |
84 | for (var i = 0; i < lines.length; i++) {
85 |
86 | var line = lines[i];
87 | line = line.trim();
88 |
89 | if (line.length === 0 || line.charAt(0) === '#') {
90 |
91 | // Blank line or comment ignore
92 | continue;
93 |
94 | }
95 |
96 | var pos = line.indexOf(' ');
97 |
98 | var key = (pos >= 0) ? line.substring(0, pos) : line;
99 | key = key.toLowerCase();
100 |
101 | var value = (pos >= 0) ? line.substring(pos + 1) : '';
102 | value = value.trim();
103 |
104 | if (key === 'newmtl') {
105 |
106 | // New material
107 |
108 | info = { name: value };
109 | materialsInfo[value] = info;
110 |
111 | } else {
112 |
113 | if (key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke') {
114 |
115 | var ss = value.split(delimiter_pattern, 3);
116 | info[key] = [parseFloat(ss[0]), parseFloat(ss[1]), parseFloat(ss[2])];
117 |
118 | } else {
119 |
120 | info[key] = value;
121 |
122 | }
123 |
124 | }
125 |
126 | }
127 |
128 | var materialCreator = new MTLLoader.MaterialCreator(this.resourcePath || path, this.materialOptions);
129 | materialCreator.setCrossOrigin(this.crossOrigin);
130 | materialCreator.setManager(this.manager);
131 | materialCreator.setMaterials(materialsInfo);
132 | return materialCreator;
133 |
134 | }
135 |
136 | });
137 |
138 | /**
139 | * Create a new THREE-MTLLoader.MaterialCreator
140 | * @param baseUrl - Url relative to which textures are loaded
141 | * @param options - Set of options on how to construct the materials
142 | * side: Which side to apply the material
143 | * FrontSide (default), THREE.BackSide, THREE.DoubleSide
144 | * wrap: What type of wrapping to apply for textures
145 | * RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
146 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
147 | * Default: false, assumed to be already normalized
148 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
149 | * Default: false
150 | * @constructor
151 | */
152 |
153 | MTLLoader.MaterialCreator = function (baseUrl, options) {
154 |
155 | this.baseUrl = baseUrl || '';
156 | this.options = options;
157 | this.materialsInfo = {};
158 | this.materials = {};
159 | this.materialsArray = [];
160 | this.nameLookup = {};
161 |
162 | this.side = (this.options && this.options.side) ? this.options.side : FrontSide;
163 | this.wrap = (this.options && this.options.wrap) ? this.options.wrap : RepeatWrapping;
164 |
165 | };
166 |
167 | MTLLoader.MaterialCreator.prototype = {
168 |
169 | constructor: MTLLoader.MaterialCreator,
170 |
171 | crossOrigin: 'anonymous',
172 |
173 | setCrossOrigin: function (value) {
174 |
175 | this.crossOrigin = value;
176 | return this;
177 |
178 | },
179 |
180 | setManager: function (value) {
181 |
182 | this.manager = value;
183 |
184 | },
185 |
186 | setMaterials: function (materialsInfo) {
187 |
188 | this.materialsInfo = this.convert(materialsInfo);
189 | this.materials = {};
190 | this.materialsArray = [];
191 | this.nameLookup = {};
192 |
193 | },
194 |
195 | convert: function (materialsInfo) {
196 |
197 | if (!this.options) return materialsInfo;
198 |
199 | var converted = {};
200 |
201 | for (var mn in materialsInfo) {
202 |
203 | // Convert materials info into normalized form based on options
204 |
205 | var mat = materialsInfo[mn];
206 |
207 | var covmat = {};
208 |
209 | converted[mn] = covmat;
210 |
211 | for (var prop in mat) {
212 |
213 | var save = true;
214 | var value = mat[prop];
215 | var lprop = prop.toLowerCase();
216 |
217 | switch (lprop) {
218 |
219 | case 'kd':
220 | case 'ka':
221 | case 'ks':
222 |
223 | // Diffuse color (color under white light) using RGB values
224 |
225 | if (this.options && this.options.normalizeRGB) {
226 |
227 | value = [value[0] / 255, value[1] / 255, value[2] / 255];
228 |
229 | }
230 |
231 | if (this.options && this.options.ignoreZeroRGBs) {
232 |
233 | if (value[0] === 0 && value[1] === 0 && value[2] === 0) {
234 |
235 | // ignore
236 |
237 | save = false;
238 |
239 | }
240 |
241 | }
242 |
243 | break;
244 |
245 | default:
246 |
247 | break;
248 |
249 | }
250 |
251 | if (save) {
252 |
253 | covmat[lprop] = value;
254 |
255 | }
256 |
257 | }
258 |
259 | }
260 |
261 | return converted;
262 |
263 | },
264 |
265 | preload: function () {
266 |
267 | for (var mn in this.materialsInfo) {
268 |
269 | this.create(mn);
270 |
271 | }
272 |
273 | },
274 |
275 | getIndex: function (materialName) {
276 |
277 | return this.nameLookup[materialName];
278 |
279 | },
280 |
281 | getAsArray: function () {
282 |
283 | var index = 0;
284 |
285 | for (var mn in this.materialsInfo) {
286 |
287 | this.materialsArray[index] = this.create(mn);
288 | this.nameLookup[mn] = index;
289 | index++;
290 |
291 | }
292 |
293 | return this.materialsArray;
294 |
295 | },
296 |
297 | create: function (materialName) {
298 |
299 | if (this.materials[materialName] === undefined) {
300 |
301 | this.createMaterial_(materialName);
302 |
303 | }
304 |
305 | return this.materials[materialName];
306 |
307 | },
308 |
309 | createMaterial_: function (materialName) {
310 |
311 | // Create material
312 |
313 | var scope = this;
314 | var mat = this.materialsInfo[materialName];
315 | var params = {
316 |
317 | name: materialName,
318 | side: this.side
319 |
320 | };
321 |
322 | function resolveURL(baseUrl, url) {
323 |
324 | if (typeof url !== 'string' || url === '')
325 | return '';
326 |
327 | // Absolute URL
328 | if (/^https?:\/\//i.test(url)) return url;
329 |
330 | return baseUrl + url;
331 |
332 | }
333 |
334 | function setMapForType(mapType, value) {
335 |
336 | if (params[mapType]) return; // Keep the first encountered texture
337 |
338 | var texParams = scope.getTextureParams(value, params);
339 | var map = scope.loadTexture(resolveURL(scope.baseUrl, texParams.url));
340 |
341 | map.repeat.copy(texParams.scale);
342 | map.offset.copy(texParams.offset);
343 |
344 | map.wrapS = scope.wrap;
345 | map.wrapT = scope.wrap;
346 |
347 | params[mapType] = map;
348 |
349 | }
350 |
351 | for (var prop in mat) {
352 |
353 | var value = mat[prop];
354 | var n;
355 |
356 | if (value === '') continue;
357 |
358 | switch (prop.toLowerCase()) {
359 |
360 | // Ns is material specular exponent
361 |
362 | case 'kd':
363 |
364 | // Diffuse color (color under white light) using RGB values
365 |
366 | params.color = new Color().fromArray(value);
367 |
368 | break;
369 |
370 | case 'ks':
371 |
372 | // Specular color (color when light is reflected from shiny surface) using RGB values
373 | params.specular = new Color().fromArray(value);
374 |
375 | break;
376 |
377 | case 'ke':
378 |
379 | // Emissive using RGB values
380 | params.emissive = new Color().fromArray(value);
381 |
382 | break;
383 |
384 | case 'map_kd':
385 |
386 | // Diffuse texture map
387 |
388 | setMapForType("map", value);
389 |
390 | break;
391 |
392 | case 'map_ks':
393 |
394 | // Specular map
395 |
396 | setMapForType("specularMap", value);
397 |
398 | break;
399 |
400 | case 'map_ke':
401 |
402 | // Emissive map
403 |
404 | setMapForType("emissiveMap", value);
405 |
406 | break;
407 |
408 | case 'norm':
409 |
410 | setMapForType("normalMap", value);
411 |
412 | break;
413 |
414 | case 'map_bump':
415 | case 'bump':
416 |
417 | // Bump texture map
418 |
419 | setMapForType("bumpMap", value);
420 |
421 | break;
422 |
423 | case 'map_d':
424 |
425 | // Alpha map
426 |
427 | setMapForType("alphaMap", value);
428 | params.transparent = true;
429 |
430 | break;
431 |
432 | case 'ns':
433 |
434 | // The specular exponent (defines the focus of the specular highlight)
435 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
436 |
437 | params.shininess = parseFloat(value);
438 |
439 | break;
440 |
441 | case 'd':
442 | n = parseFloat(value);
443 |
444 | if (n < 1) {
445 |
446 | params.opacity = n;
447 | params.transparent = true;
448 |
449 | }
450 |
451 | break;
452 |
453 | case 'tr':
454 | n = parseFloat(value);
455 |
456 | if (this.options && this.options.invertTrProperty) n = 1 - n;
457 |
458 | if (n > 0) {
459 |
460 | params.opacity = 1 - n;
461 | params.transparent = true;
462 |
463 | }
464 |
465 | break;
466 |
467 | default:
468 | break;
469 |
470 | }
471 |
472 | }
473 |
474 | this.materials[materialName] = new MeshPhongMaterial(params);
475 | return this.materials[materialName];
476 |
477 | },
478 |
479 | getTextureParams: function (value, matParams) {
480 |
481 | var texParams = {
482 |
483 | scale: new Vector2(1, 1),
484 | offset: new Vector2(0, 0)
485 |
486 | };
487 |
488 | var items = value.split(/\s+/);
489 | var pos;
490 |
491 | pos = items.indexOf('-bm');
492 |
493 | if (pos >= 0) {
494 |
495 | matParams.bumpScale = parseFloat(items[pos + 1]);
496 | items.splice(pos, 2);
497 |
498 | }
499 |
500 | pos = items.indexOf('-s');
501 |
502 | if (pos >= 0) {
503 |
504 | texParams.scale.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
505 | items.splice(pos, 4); // we expect 3 parameters here!
506 |
507 | }
508 |
509 | pos = items.indexOf('-o');
510 |
511 | if (pos >= 0) {
512 |
513 | texParams.offset.set(parseFloat(items[pos + 1]), parseFloat(items[pos + 2]));
514 | items.splice(pos, 4); // we expect 3 parameters here!
515 |
516 | }
517 |
518 | texParams.url = items.join(' ').trim();
519 | return texParams;
520 |
521 | },
522 |
523 | loadTexture: function (url, mapping, onLoad, onProgress, onError) {
524 |
525 | var texture;
526 | var manager = (this.manager !== undefined) ? this.manager : DefaultLoadingManager;
527 | var loader = manager.getHandler(url);
528 |
529 | if (loader === null) {
530 |
531 | loader = new TextureLoader(manager);
532 |
533 | }
534 |
535 | if (loader.setCrossOrigin) loader.setCrossOrigin(this.crossOrigin);
536 | texture = loader.load(url, onLoad, onProgress, onError);
537 |
538 | if (mapping !== undefined) texture.mapping = mapping;
539 |
540 | return texture;
541 |
542 | }
543 |
544 | };
545 |
546 | return { MTLLoader };
547 | }
--------------------------------------------------------------------------------
/pages/gltf/loadgLTF.js:
--------------------------------------------------------------------------------
1 | import gLTF from '../../jsm/loaders/GLTFLoader'
2 | import { OrbitControls } from '../../jsm/controls/OrbitControls'
3 |
4 | export default function (canvas, THREE) {
5 | let GLTFLoader = gLTF(THREE);
6 |
7 | const renderer = new THREE.WebGLRenderer({ canvas });
8 | renderer.shadowMap.enabled = true;
9 |
10 | const fov = 45;
11 | const aspect = 2; // the canvas default
12 | const near = 0.1;
13 | const far = 100;
14 | const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
15 | camera.position.set(0, 10, 20);
16 |
17 | const controls = new OrbitControls(camera, canvas);
18 | controls.target.set(0, 5, 0);
19 | controls.update();
20 |
21 | const scene = new THREE.Scene();
22 | scene.background = new THREE.Color('#DEFEFF');
23 |
24 | {
25 | const planeSize = 40;
26 |
27 | const loader = new THREE.TextureLoader();
28 | const texture = loader.load('https://threejsfundamentals.org/threejs/resources/images/checker.png');
29 | texture.wrapS = THREE.RepeatWrapping;
30 | texture.wrapT = THREE.RepeatWrapping;
31 | texture.magFilter = THREE.NearestFilter;
32 | const repeats = planeSize / 2;
33 | texture.repeat.set(repeats, repeats);
34 |
35 | const planeGeo = new THREE.PlaneBufferGeometry(planeSize, planeSize);
36 | const planeMat = new THREE.MeshPhongMaterial({
37 | map: texture,
38 | side: THREE.DoubleSide,
39 | });
40 | const mesh = new THREE.Mesh(planeGeo, planeMat);
41 | mesh.rotation.x = Math.PI * -.5;
42 | scene.add(mesh);
43 | }
44 |
45 | {
46 | const skyColor = 0xB1E1FF; // light blue
47 | const groundColor = 0xB97A20; // brownish orange
48 | const intensity = 1;
49 | const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
50 | scene.add(light);
51 | }
52 |
53 | {
54 | const color = 0xFFFFFF;
55 | const intensity = 1;
56 | const light = new THREE.DirectionalLight(color, intensity);
57 | light.castShadow = true;
58 | light.position.set(-250, 800, -850);
59 | light.target.position.set(-550, 40, -450);
60 |
61 | light.shadow.bias = -0.004;
62 | light.shadow.mapSize.width = 2048;
63 | light.shadow.mapSize.height = 2048;
64 |
65 | scene.add(light);
66 | scene.add(light.target);
67 | const cam = light.shadow.camera;
68 | cam.near = 1;
69 | cam.far = 2000;
70 | cam.left = -1500;
71 | cam.right = 1500;
72 | cam.top = 1500;
73 | cam.bottom = -1500;
74 |
75 | const cameraHelper = new THREE.CameraHelper(cam);
76 | scene.add(cameraHelper);
77 | cameraHelper.visible = false;
78 | const helper = new THREE.DirectionalLightHelper(light, 100);
79 | scene.add(helper);
80 | helper.visible = false;
81 |
82 | // function makeXYZGUI(gui, vector3, name, onChangeFn) {
83 | // const folder = gui.addFolder(name);
84 | // folder.add(vector3, 'x', vector3.x - 500, vector3.x + 500).onChange(onChangeFn);
85 | // folder.add(vector3, 'y', vector3.y - 500, vector3.y + 500).onChange(onChangeFn);
86 | // folder.add(vector3, 'z', vector3.z - 500, vector3.z + 500).onChange(onChangeFn);
87 | // folder.open();
88 | // }
89 |
90 | function updateCamera() {
91 | // update the light target's matrixWorld because it's needed by the helper
92 | light.updateMatrixWorld();
93 | light.target.updateMatrixWorld();
94 | helper.update();
95 | // update the light's shadow camera's projection matrix
96 | light.shadow.camera.updateProjectionMatrix();
97 | // and now update the camera helper we're using to show the light's shadow camera
98 | cameraHelper.update();
99 | }
100 | updateCamera();
101 |
102 | // class DimensionGUIHelper {
103 | // constructor(obj, minProp, maxProp) {
104 | // this.obj = obj;
105 | // this.minProp = minProp;
106 | // this.maxProp = maxProp;
107 | // }
108 | // get value() {
109 | // return this.obj[this.maxProp] * 2;
110 | // }
111 | // set value(v) {
112 | // this.obj[this.maxProp] = v / 2;
113 | // this.obj[this.minProp] = v / -2;
114 | // }
115 | // }
116 |
117 | // class MinMaxGUIHelper {
118 | // constructor(obj, minProp, maxProp, minDif) {
119 | // this.obj = obj;
120 | // this.minProp = minProp;
121 | // this.maxProp = maxProp;
122 | // this.minDif = minDif;
123 | // }
124 | // get min() {
125 | // return this.obj[this.minProp];
126 | // }
127 | // set min(v) {
128 | // this.obj[this.minProp] = v;
129 | // this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif);
130 | // }
131 | // get max() {
132 | // return this.obj[this.maxProp];
133 | // }
134 | // set max(v) {
135 | // this.obj[this.maxProp] = v;
136 | // this.min = this.min; // this will call the min setter
137 | // }
138 | // }
139 |
140 | // class VisibleGUIHelper {
141 | // constructor(...objects) {
142 | // this.objects = [...objects];
143 | // }
144 | // get value() {
145 | // return this.objects[0].visible;
146 | // }
147 | // set value(v) {
148 | // this.objects.forEach((obj) => {
149 | // obj.visible = v;
150 | // });
151 | // }
152 | // }
153 |
154 | // const gui = new GUI();
155 | // gui.close();
156 | // gui.add(new VisibleGUIHelper(helper, cameraHelper), 'value').name('show helpers');
157 | // gui.add(light.shadow, 'bias', -0.1, 0.1, 0.001);
158 | // {
159 | // const folder = gui.addFolder('Shadow Camera');
160 | // folder.open();
161 | // folder.add(new DimensionGUIHelper(light.shadow.camera, 'left', 'right'), 'value', 1, 4000)
162 | // .name('width')
163 | // .onChange(updateCamera);
164 | // folder.add(new DimensionGUIHelper(light.shadow.camera, 'bottom', 'top'), 'value', 1, 4000)
165 | // .name('height')
166 | // .onChange(updateCamera);
167 | // const minMaxGUIHelper = new MinMaxGUIHelper(light.shadow.camera, 'near', 'far', 0.1);
168 | // folder.add(minMaxGUIHelper, 'min', 1, 1000, 1).name('near').onChange(updateCamera);
169 | // folder.add(minMaxGUIHelper, 'max', 1, 4000, 1).name('far').onChange(updateCamera);
170 | // folder.add(light.shadow.camera, 'zoom', 0.01, 1.5, 0.01).onChange(updateCamera);
171 | // }
172 |
173 | // makeXYZGUI(gui, light.position, 'position', updateCamera);
174 | // makeXYZGUI(gui, light.target.position, 'target', updateCamera);
175 | }
176 |
177 | function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
178 | const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
179 | const halfFovY = THREE.Math.degToRad(camera.fov * .5);
180 | const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
181 | // compute a unit vector that points in the direction the camera is now
182 | // in the xz plane from the center of the box
183 | const direction = (new THREE.Vector3())
184 | .subVectors(camera.position, boxCenter)
185 | .multiply(new THREE.Vector3(1, 0, 1))
186 | .normalize();
187 |
188 | // move the camera to a position distance units way from the center
189 | // in whatever direction the camera was from the center already
190 | camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));
191 |
192 | // pick some near and far values for the frustum that
193 | // will contain the box.
194 | camera.near = boxSize / 100;
195 | camera.far = boxSize * 100;
196 |
197 | camera.updateProjectionMatrix();
198 |
199 | // point the camera to look at the center of the box
200 | camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
201 | }
202 |
203 | let curve;
204 | let curveObject;
205 | {
206 | const controlPoints = [
207 | [1.118281, 5.115846, -3.681386],
208 | [3.948875, 5.115846, -3.641834],
209 | [3.960072, 5.115846, -0.240352],
210 | [3.985447, 5.115846, 4.585005],
211 | [-3.793631, 5.115846, 4.585006],
212 | [-3.826839, 5.115846, -14.736200],
213 | [-14.542292, 5.115846, -14.765865],
214 | [-14.520929, 5.115846, -3.627002],
215 | [-5.452815, 5.115846, -3.634418],
216 | [-5.467251, 5.115846, 4.549161],
217 | [-13.266233, 5.115846, 4.567083],
218 | [-13.250067, 5.115846, -13.499271],
219 | [4.081842, 5.115846, -13.435463],
220 | [4.125436, 5.115846, -5.334928],
221 | [-14.521364, 5.115846, -5.239871],
222 | [-14.510466, 5.115846, 5.486727],
223 | [5.745666, 5.115846, 5.510492],
224 | [5.787942, 5.115846, -14.728308],
225 | [-5.423720, 5.115846, -14.761919],
226 | [-5.373599, 5.115846, -3.704133],
227 | [1.004861, 5.115846, -3.641834],
228 | ];
229 | const p0 = new THREE.Vector3();
230 | const p1 = new THREE.Vector3();
231 | let con = controlPoints.map((p, ndx) => {
232 | p0.set(...p);
233 | p1.set(...controlPoints[(ndx + 1) % controlPoints.length]);
234 | return [
235 | (new THREE.Vector3()).copy(p0),
236 | (new THREE.Vector3()).lerpVectors(p0, p1, 0.1),
237 | (new THREE.Vector3()).lerpVectors(p0, p1, 0.9),
238 | ];
239 | });
240 | curve = new THREE.CatmullRomCurve3(
241 | [].concat(...con),
242 | true,
243 | );
244 | {
245 | const points = curve.getPoints(250);
246 | const geometry = new THREE.BufferGeometry().setFromPoints(points);
247 | const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
248 | curveObject = new THREE.Line(geometry, material);
249 | curveObject.scale.set(100, 100, 100);
250 | curveObject.position.y = -621;
251 | curveObject.visible = false;
252 | scene.add(curveObject);
253 | }
254 | }
255 |
256 | const cars = [];
257 | {
258 | const gltfLoader = new GLTFLoader();
259 | gltfLoader.load('https://threejsfundamentals.org/threejs/resources/models/cartoon_lowpoly_small_city_free_pack/scene.gltf', (gltf) => {
260 | const root = gltf.scene;
261 | scene.add(root);
262 |
263 | root.traverse((obj) => {
264 | if (obj.castShadow !== undefined) {
265 | obj.castShadow = true;
266 | obj.receiveShadow = true;
267 | }
268 | });
269 |
270 | const loadedCars = root.getObjectByName('Cars');
271 | const fixes = [
272 | { prefix: 'Car_08', y: 0, rot: [Math.PI * .5, 0, Math.PI * .5], },
273 | { prefix: 'CAR_03', y: 33, rot: [0, Math.PI, 0], },
274 | { prefix: 'Car_04', y: 40, rot: [0, Math.PI, 0], },
275 | ];
276 |
277 | root.updateMatrixWorld();
278 | for (const car of loadedCars.children.slice()) {
279 | const fix = fixes.find(fix => car.name.startsWith(fix.prefix));
280 | const obj = new THREE.Object3D();
281 | car.position.set(0, fix.y, 0);
282 | car.rotation.set(...fix.rot);
283 | obj.add(car);
284 | scene.add(obj);
285 | cars.push(obj);
286 | }
287 |
288 | // compute the box that contains all the stuff
289 | // from root and below
290 | const box = new THREE.Box3().setFromObject(root);
291 |
292 | const boxSize = box.getSize(new THREE.Vector3()).length();
293 | const boxCenter = box.getCenter(new THREE.Vector3());
294 |
295 | // set the camera to frame the box
296 | frameArea(boxSize * 0.5, boxSize, boxCenter, camera);
297 |
298 | // update the Trackball controls to handle the new size
299 | controls.maxDistance = boxSize * 10;
300 | controls.target.copy(boxCenter);
301 | controls.update();
302 | }, (e) => {
303 | console.log('loading')
304 | });
305 | }
306 |
307 | function resizeRendererToDisplaySize(renderer) {
308 | const canvas = renderer.domElement;
309 | const width = canvas.clientWidth;
310 | const height = canvas.clientHeight;
311 | const needResize = canvas.width !== width || canvas.height !== height;
312 | if (needResize) {
313 | renderer.setSize(width, height, false);
314 | }
315 | return needResize;
316 | }
317 |
318 | // create 2 Vector3s we can use for path calculations
319 | const carPosition = new THREE.Vector3();
320 | const carTarget = new THREE.Vector3();
321 |
322 | function render(time) {
323 | time *= 0.001; // convert to seconds
324 |
325 | if (resizeRendererToDisplaySize(renderer)) {
326 | const canvas = renderer.domElement;
327 | camera.aspect = canvas.clientWidth / canvas.clientHeight;
328 | camera.updateProjectionMatrix();
329 | }
330 |
331 | {
332 | const pathTime = time * .01;
333 | const targetOffset = 0.01;
334 | cars.forEach((car, ndx) => {
335 | // a number between 0 and 1 to evenly space the cars
336 | const u = pathTime + ndx / cars.length;
337 |
338 | // get the first point
339 | curve.getPointAt(u % 1, carPosition);
340 | carPosition.applyMatrix4(curveObject.matrixWorld);
341 |
342 | // get a second point slightly further down the curve
343 | curve.getPointAt((u + targetOffset) % 1, carTarget);
344 | carTarget.applyMatrix4(curveObject.matrixWorld);
345 |
346 | // put the car at the first point (temporarily)
347 | car.position.copy(carPosition);
348 | // point the car the second point
349 | car.lookAt(carTarget);
350 |
351 | // put the car between the 2 points
352 | car.position.lerpVectors(carPosition, carTarget, 0.5);
353 | });
354 | }
355 |
356 | renderer.render(scene, camera);
357 |
358 | canvas.requestAnimationFrame(render);
359 | }
360 |
361 | canvas.requestAnimationFrame(render);
362 | }
--------------------------------------------------------------------------------
/pages/primitives/primitives.js:
--------------------------------------------------------------------------------
1 | const regeneratorRuntime = require('../../utils/runtime')
2 | export default function renderPrimitives(canvas, THREE) {
3 | const renderer = new THREE.WebGLRenderer({ canvas });
4 |
5 | const fov = 40;
6 | const aspect = 0.5; // the canvas default
7 | const near = 0.1;
8 | const far = 1000;
9 | const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
10 | camera.position.z = 150;
11 |
12 | const scene = new THREE.Scene();
13 | scene.background = new THREE.Color(0xAAAAAA);
14 |
15 | {
16 | const color = 0xFFFFFF;
17 | const intensity = 1;
18 | const light = new THREE.DirectionalLight(color, intensity);
19 | light.position.set(-1, 2, 4);
20 | scene.add(light);
21 | }
22 | {
23 | const color = 0xFFFFFF;
24 | const intensity = 1;
25 | const light = new THREE.DirectionalLight(color, intensity);
26 | light.position.set(1, -2, -4);
27 | scene.add(light);
28 | }
29 |
30 | const objects = [];
31 | const spread = 15;
32 |
33 | function addObject(x, y, obj) {
34 | obj.position.x = x * spread;
35 | obj.position.y = y * spread;
36 |
37 | scene.add(obj);
38 | objects.push(obj);
39 | }
40 |
41 | function createMaterial() {
42 | const material = new THREE.MeshPhongMaterial({
43 | side: THREE.DoubleSide,
44 | });
45 |
46 | const hue = Math.random();
47 | const saturation = 1;
48 | const luminance = .5;
49 | material.color.setHSL(hue, saturation, luminance);
50 |
51 | return material;
52 | }
53 |
54 | function addSolidGeometry(x, y, geometry) {
55 | const mesh = new THREE.Mesh(geometry, createMaterial());
56 | addObject(x, y, mesh);
57 | }
58 |
59 | function addLineGeometry(x, y, geometry) {
60 | const material = new THREE.LineBasicMaterial({ color: 0x000000 });
61 | const mesh = new THREE.LineSegments(geometry, material);
62 | addObject(x, y, mesh);
63 | }
64 |
65 | {
66 | const width = 8;
67 | const height = 8;
68 | const depth = 8;
69 | addSolidGeometry(-1, 2, new THREE.BoxBufferGeometry(width, height, depth));
70 | }
71 | {
72 | const radius = 7;
73 | const segments = 24;
74 | addSolidGeometry(0, 2, new THREE.CircleBufferGeometry(radius, segments));
75 | }
76 | {
77 | const radius = 6;
78 | const height = 8;
79 | const segments = 16;
80 | addSolidGeometry(1, 2, new THREE.ConeBufferGeometry(radius, height, segments));
81 | }
82 | {
83 | const radiusTop = 4;
84 | const radiusBottom = 4;
85 | const height = 8;
86 | const radialSegments = 12;
87 | addSolidGeometry(-1, 1, new THREE.CylinderBufferGeometry(radiusTop, radiusBottom, height, radialSegments));
88 | }
89 | {
90 | const radius = 7;
91 | addSolidGeometry(0, 1, new THREE.DodecahedronBufferGeometry(radius));
92 | }
93 | {
94 | const shape = new THREE.Shape();
95 | const x = -2.5;
96 | const y = -5;
97 | shape.moveTo(x + 2.5, y + 2.5);
98 | shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
99 | shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
100 | shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
101 | shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
102 | shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
103 | shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
104 |
105 | const extrudeSettings = {
106 | steps: 2,
107 | depth: 2,
108 | bevelEnabled: true,
109 | bevelThickness: 1,
110 | bevelSize: 1,
111 | bevelSegments: 2,
112 | };
113 |
114 | addSolidGeometry(1, 1, new THREE.ExtrudeBufferGeometry(shape, extrudeSettings));
115 | }
116 | {
117 | const radius = 7;
118 | addSolidGeometry(-1, 0, new THREE.IcosahedronBufferGeometry(radius));
119 | }
120 | {
121 | const points = [];
122 | for (let i = 0; i < 10; ++i) {
123 | points.push(new THREE.Vector2(Math.sin(i * 0.2) * 3 + 3, (i - 5) * .8));
124 | }
125 | addSolidGeometry(0, 0, new THREE.LatheBufferGeometry(points));
126 | }
127 | {
128 | const radius = 7;
129 | addSolidGeometry(1, 0, new THREE.OctahedronBufferGeometry(radius));
130 | }
131 | {
132 | /*
133 | from: https://github.com/mrdoob/three.js/blob/b8d8a8625465bd634aa68e5846354d69f34d2ff5/examples/js/ParametricGeometries.js
134 |
135 | The MIT License
136 |
137 | Copyright © 2010-2018 three.js authors
138 |
139 | Permission is hereby granted, free of charge, to any person obtaining a copy
140 | of this software and associated documentation files (the "Software"), to deal
141 | in the Software without restriction, including without limitation the rights
142 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
143 | copies of the Software, and to permit persons to whom the Software is
144 | furnished to do so, subject to the following conditions:
145 |
146 | The above copyright notice and this permission notice shall be included in
147 | all copies or substantial portions of the Software.
148 |
149 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
150 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
151 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
152 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
153 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
154 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
155 | THE SOFTWARE.
156 |
157 | */
158 | function klein(v, u, target) {
159 | u *= Math.PI;
160 | v *= 2 * Math.PI;
161 | u = u * 2;
162 |
163 | let x;
164 | let z;
165 |
166 | if (u < Math.PI) {
167 | x = 3 * Math.cos(u) * (1 + Math.sin(u)) + (2 * (1 - Math.cos(u) / 2)) * Math.cos(u) * Math.cos(v);
168 | z = -8 * Math.sin(u) - 2 * (1 - Math.cos(u) / 2) * Math.sin(u) * Math.cos(v);
169 | } else {
170 | x = 3 * Math.cos(u) * (1 + Math.sin(u)) + (2 * (1 - Math.cos(u) / 2)) * Math.cos(v + Math.PI);
171 | z = -8 * Math.sin(u);
172 | }
173 |
174 | const y = -2 * (1 - Math.cos(u) / 2) * Math.sin(v);
175 |
176 | target.set(x, y, z).multiplyScalar(0.75);
177 | }
178 |
179 | const slices = 25;
180 | const stacks = 25;
181 | addSolidGeometry(-1, -1, new THREE.ParametricBufferGeometry(klein, slices, stacks));
182 | }
183 | {
184 | const width = 9;
185 | const height = 9;
186 | const widthSegments = 2;
187 | const heightSegments = 2;
188 | addSolidGeometry(0, -1, new THREE.PlaneBufferGeometry(width, height, widthSegments, heightSegments));
189 | }
190 | {
191 | const verticesOfCube = [
192 | -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1,
193 | -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1,
194 | ];
195 | const indicesOfFaces = [
196 | 2, 1, 0, 0, 3, 2,
197 | 0, 4, 7, 7, 3, 0,
198 | 0, 1, 5, 5, 4, 0,
199 | 1, 2, 6, 6, 5, 1,
200 | 2, 3, 7, 7, 6, 2,
201 | 4, 5, 6, 6, 7, 4,
202 | ];
203 | const radius = 7;
204 | const detail = 2;
205 | addSolidGeometry(1, -1, new THREE.PolyhedronBufferGeometry(verticesOfCube, indicesOfFaces, radius, detail));
206 | }
207 | {
208 | const innerRadius = 2;
209 | const outerRadius = 7;
210 | const segments = 18;
211 | addSolidGeometry(-1, -2, new THREE.RingBufferGeometry(innerRadius, outerRadius, segments));
212 | }
213 | {
214 | const shape = new THREE.Shape();
215 | const x = -2.5;
216 | const y = -5;
217 | shape.moveTo(x + 2.5, y + 2.5);
218 | shape.bezierCurveTo(x + 2.5, y + 2.5, x + 2, y, x, y);
219 | shape.bezierCurveTo(x - 3, y, x - 3, y + 3.5, x - 3, y + 3.5);
220 | shape.bezierCurveTo(x - 3, y + 5.5, x - 1.5, y + 7.7, x + 2.5, y + 9.5);
221 | shape.bezierCurveTo(x + 6, y + 7.7, x + 8, y + 4.5, x + 8, y + 3.5);
222 | shape.bezierCurveTo(x + 8, y + 3.5, x + 8, y, x + 5, y);
223 | shape.bezierCurveTo(x + 3.5, y, x + 2.5, y + 2.5, x + 2.5, y + 2.5);
224 | addSolidGeometry(0, -2, new THREE.ShapeBufferGeometry(shape));
225 | }
226 | {
227 | const radius = 7;
228 | const widthSegments = 12;
229 | const heightSegments = 8;
230 | addSolidGeometry(1, -2, new THREE.SphereBufferGeometry(radius, widthSegments, heightSegments));
231 | }
232 | {
233 | const radius = 7;
234 | addSolidGeometry(-1, -3, new THREE.TetrahedronBufferGeometry(radius));
235 | }
236 | {
237 | const loader = new THREE.FontLoader();
238 | // promisify font loading
239 | function loadFont(url) {
240 | return new Promise((resolve, reject) => {
241 | loader.load(url, resolve, undefined, reject);
242 | });
243 | }
244 |
245 | async function doit() {
246 | const font = await loadFont('https://threejsfundamentals.org/threejs/resources/threejs/fonts/helvetiker_regular.typeface.json');
247 | const geometry = new THREE.TextBufferGeometry('three.js', {
248 | font: font,
249 | size: 3.0,
250 | height: .2,
251 | curveSegments: 12,
252 | bevelEnabled: true,
253 | bevelThickness: 0.15,
254 | bevelSize: .3,
255 | bevelSegments: 5,
256 | });
257 | const mesh = new THREE.Mesh(geometry, createMaterial());
258 | geometry.computeBoundingBox();
259 | geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1);
260 |
261 | const parent = new THREE.Object3D();
262 | parent.add(mesh);
263 |
264 | addObject(0, -3, parent);
265 | }
266 | doit();
267 | }
268 | {
269 | const radius = 5;
270 | const tubeRadius = 2;
271 | const radialSegments = 8;
272 | const tubularSegments = 24;
273 | addSolidGeometry(1, -3, new THREE.TorusBufferGeometry(radius, tubeRadius, radialSegments, tubularSegments));
274 | }
275 | //top four
276 | {
277 | const radius = 3.5;
278 | const tube = 1.5;
279 | const radialSegments = 8;
280 | const tubularSegments = 64;
281 | const p = 2;
282 | const q = 3;
283 | addSolidGeometry(-1.5, 3, new THREE.TorusKnotBufferGeometry(radius, tube, tubularSegments, radialSegments, p, q));
284 | }
285 | {
286 | class CustomSinCurve extends THREE.Curve {
287 | constructor(scale) {
288 | super();
289 | this.scale = scale;
290 | }
291 | getPoint(t) {
292 | const tx = t * 3 - 1.5;
293 | const ty = Math.sin(2 * Math.PI * t);
294 | const tz = 0;
295 | return new THREE.Vector3(tx, ty, tz).multiplyScalar(this.scale);
296 | }
297 | }
298 |
299 | const path = new CustomSinCurve(4);
300 | const tubularSegments = 20;
301 | const radius = 1;
302 | const radialSegments = 8;
303 | const closed = false;
304 | addSolidGeometry(-0.5, 3, new THREE.TubeBufferGeometry(path, tubularSegments, radius, radialSegments, closed));
305 | }
306 | {
307 | const width = 8;
308 | const height = 8;
309 | const depth = 8;
310 | const thresholdAngle = 15;
311 | addLineGeometry(0.5, 3, new THREE.EdgesGeometry(
312 | new THREE.BoxBufferGeometry(width, height, depth),
313 | thresholdAngle));
314 | }
315 | {
316 | const width = 8;
317 | const height = 8;
318 | const depth = 8;
319 | addLineGeometry(1.5, 3, new THREE.WireframeGeometry(new THREE.BoxBufferGeometry(width, height, depth)));
320 | }
321 |
322 | function resizeRendererToDisplaySize(renderer) {
323 | const canvas = renderer.domElement;
324 | // const width = canvas.clientWidth;
325 | // const height = canvas.clientHeight;
326 | let { width, height } = canvas.getBoundingClientRect()
327 | const needResize = canvas.getBoundingClientRect().width !== width || canvas.getBoundingClientRect().height !== height;
328 | if (needResize) {
329 | renderer.setSize(width, height, false);
330 | }
331 | return needResize;
332 | }
333 |
334 | function render(time) {
335 | time *= 0.001;
336 |
337 | if (resizeRendererToDisplaySize(renderer)) {
338 | const canvas = renderer.domElement;
339 | let { width, height } = canvas.getBoundingClientRect()
340 | // camera.aspect = canvas.clientWidth / canvas.clientHeight;
341 | camera.aspect = width / height;
342 | camera.updateProjectionMatrix();
343 | }
344 |
345 | objects.forEach((obj, ndx) => {
346 | const speed = .1 + ndx * .05;
347 | const rot = time * speed;
348 | obj.rotation.x = rot;
349 | obj.rotation.y = rot;
350 | });
351 |
352 | renderer.render(scene, camera);
353 |
354 | canvas.requestAnimationFrame(render);
355 | }
356 |
357 | canvas.requestAnimationFrame(render);
358 | }
--------------------------------------------------------------------------------
/jsm/utils/SkeletonUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author sunag / http://www.sunag.com.br
3 | */
4 | export default function (THREE) {
5 | let {
6 | AnimationClip,
7 | AnimationMixer,
8 | Euler,
9 | Matrix4,
10 | Quaternion,
11 | QuaternionKeyframeTrack,
12 | SkeletonHelper,
13 | Vector2,
14 | Vector3,
15 | VectorKeyframeTrack
16 | } = THREE;
17 |
18 | var SkeletonUtils = {
19 |
20 | retarget: function () {
21 |
22 | var pos = new Vector3(),
23 | quat = new Quaternion(),
24 | scale = new Vector3(),
25 | bindBoneMatrix = new Matrix4(),
26 | relativeMatrix = new Matrix4(),
27 | globalMatrix = new Matrix4();
28 |
29 | return function ( target, source, options ) {
30 |
31 | options = options || {};
32 | options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true;
33 | options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true;
34 | options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false;
35 | options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false;
36 | options.hip = options.hip !== undefined ? options.hip : "hip";
37 | options.names = options.names || {};
38 |
39 | var sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
40 | bones = target.isObject3D ? target.skeleton.bones : this.getBones( target ),
41 | bindBones,
42 | bone, name, boneTo,
43 | bonesPosition, i;
44 |
45 | // reset bones
46 |
47 | if ( target.isObject3D ) {
48 |
49 | target.skeleton.pose();
50 |
51 | } else {
52 |
53 | options.useTargetMatrix = true;
54 | options.preserveMatrix = false;
55 |
56 | }
57 |
58 | if ( options.preservePosition ) {
59 |
60 | bonesPosition = [];
61 |
62 | for ( i = 0; i < bones.length; i ++ ) {
63 |
64 | bonesPosition.push( bones[ i ].position.clone() );
65 |
66 | }
67 |
68 | }
69 |
70 | if ( options.preserveMatrix ) {
71 |
72 | // reset matrix
73 |
74 | target.updateMatrixWorld();
75 |
76 | target.matrixWorld.identity();
77 |
78 | // reset children matrix
79 |
80 | for ( i = 0; i < target.children.length; ++ i ) {
81 |
82 | target.children[ i ].updateMatrixWorld( true );
83 |
84 | }
85 |
86 | }
87 |
88 | if ( options.offsets ) {
89 |
90 | bindBones = [];
91 |
92 | for ( i = 0; i < bones.length; ++ i ) {
93 |
94 | bone = bones[ i ];
95 | name = options.names[ bone.name ] || bone.name;
96 |
97 | if ( options.offsets && options.offsets[ name ] ) {
98 |
99 | bone.matrix.multiply( options.offsets[ name ] );
100 |
101 | bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
102 |
103 | bone.updateMatrixWorld();
104 |
105 | }
106 |
107 | bindBones.push( bone.matrixWorld.clone() );
108 |
109 | }
110 |
111 | }
112 |
113 | for ( i = 0; i < bones.length; ++ i ) {
114 |
115 | bone = bones[ i ];
116 | name = options.names[ bone.name ] || bone.name;
117 |
118 | boneTo = this.getBoneByName( name, sourceBones );
119 |
120 | globalMatrix.copy( bone.matrixWorld );
121 |
122 | if ( boneTo ) {
123 |
124 | boneTo.updateMatrixWorld();
125 |
126 | if ( options.useTargetMatrix ) {
127 |
128 | relativeMatrix.copy( boneTo.matrixWorld );
129 |
130 | } else {
131 |
132 | relativeMatrix.getInverse( target.matrixWorld );
133 | relativeMatrix.multiply( boneTo.matrixWorld );
134 |
135 | }
136 |
137 | // ignore scale to extract rotation
138 |
139 | scale.setFromMatrixScale( relativeMatrix );
140 | relativeMatrix.scale( scale.set( 1 / scale.x, 1 / scale.y, 1 / scale.z ) );
141 |
142 | // apply to global matrix
143 |
144 | globalMatrix.makeRotationFromQuaternion( quat.setFromRotationMatrix( relativeMatrix ) );
145 |
146 | if ( target.isObject3D ) {
147 |
148 | var boneIndex = bones.indexOf( bone ),
149 | wBindMatrix = bindBones ? bindBones[ boneIndex ] : bindBoneMatrix.getInverse( target.skeleton.boneInverses[ boneIndex ] );
150 |
151 | globalMatrix.multiply( wBindMatrix );
152 |
153 | }
154 |
155 | globalMatrix.copyPosition( relativeMatrix );
156 |
157 | }
158 |
159 | if ( bone.parent && bone.parent.isBone ) {
160 |
161 | bone.matrix.getInverse( bone.parent.matrixWorld );
162 | bone.matrix.multiply( globalMatrix );
163 |
164 | } else {
165 |
166 | bone.matrix.copy( globalMatrix );
167 |
168 | }
169 |
170 | if ( options.preserveHipPosition && name === options.hip ) {
171 |
172 | bone.matrix.setPosition( pos.set( 0, bone.position.y, 0 ) );
173 |
174 | }
175 |
176 | bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
177 |
178 | bone.updateMatrixWorld();
179 |
180 | }
181 |
182 | if ( options.preservePosition ) {
183 |
184 | for ( i = 0; i < bones.length; ++ i ) {
185 |
186 | bone = bones[ i ];
187 | name = options.names[ bone.name ] || bone.name;
188 |
189 | if ( name !== options.hip ) {
190 |
191 | bone.position.copy( bonesPosition[ i ] );
192 |
193 | }
194 |
195 | }
196 |
197 | }
198 |
199 | if ( options.preserveMatrix ) {
200 |
201 | // restore matrix
202 |
203 | target.updateMatrixWorld( true );
204 |
205 | }
206 |
207 | };
208 |
209 | }(),
210 |
211 | retargetClip: function ( target, source, clip, options ) {
212 |
213 | options = options || {};
214 | options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false;
215 | options.fps = options.fps !== undefined ? options.fps : 30;
216 | options.names = options.names || [];
217 |
218 | if ( ! source.isObject3D ) {
219 |
220 | source = this.getHelperFromSkeleton( source );
221 |
222 | }
223 |
224 | var numFrames = Math.round( clip.duration * ( options.fps / 1000 ) * 1000 ),
225 | delta = 1 / options.fps,
226 | convertedTracks = [],
227 | mixer = new AnimationMixer( source ),
228 | bones = this.getBones( target.skeleton ),
229 | boneDatas = [],
230 | positionOffset,
231 | bone, boneTo, boneData,
232 | name, i, j;
233 |
234 | mixer.clipAction( clip ).play();
235 | mixer.update( 0 );
236 |
237 | source.updateMatrixWorld();
238 |
239 | for ( i = 0; i < numFrames; ++ i ) {
240 |
241 | var time = i * delta;
242 |
243 | this.retarget( target, source, options );
244 |
245 | for ( j = 0; j < bones.length; ++ j ) {
246 |
247 | name = options.names[ bones[ j ].name ] || bones[ j ].name;
248 |
249 | boneTo = this.getBoneByName( name, source.skeleton );
250 |
251 | if ( boneTo ) {
252 |
253 | bone = bones[ j ];
254 | boneData = boneDatas[ j ] = boneDatas[ j ] || { bone: bone };
255 |
256 | if ( options.hip === name ) {
257 |
258 | if ( ! boneData.pos ) {
259 |
260 | boneData.pos = {
261 | times: new Float32Array( numFrames ),
262 | values: new Float32Array( numFrames * 3 )
263 | };
264 |
265 | }
266 |
267 | if ( options.useFirstFramePosition ) {
268 |
269 | if ( i === 0 ) {
270 |
271 | positionOffset = bone.position.clone();
272 |
273 | }
274 |
275 | bone.position.sub( positionOffset );
276 |
277 | }
278 |
279 | boneData.pos.times[ i ] = time;
280 |
281 | bone.position.toArray( boneData.pos.values, i * 3 );
282 |
283 | }
284 |
285 | if ( ! boneData.quat ) {
286 |
287 | boneData.quat = {
288 | times: new Float32Array( numFrames ),
289 | values: new Float32Array( numFrames * 4 )
290 | };
291 |
292 | }
293 |
294 | boneData.quat.times[ i ] = time;
295 |
296 | bone.quaternion.toArray( boneData.quat.values, i * 4 );
297 |
298 | }
299 |
300 | }
301 |
302 | mixer.update( delta );
303 |
304 | source.updateMatrixWorld();
305 |
306 | }
307 |
308 | for ( i = 0; i < boneDatas.length; ++ i ) {
309 |
310 | boneData = boneDatas[ i ];
311 |
312 | if ( boneData ) {
313 |
314 | if ( boneData.pos ) {
315 |
316 | convertedTracks.push( new VectorKeyframeTrack(
317 | ".bones[" + boneData.bone.name + "].position",
318 | boneData.pos.times,
319 | boneData.pos.values
320 | ) );
321 |
322 | }
323 |
324 | convertedTracks.push( new QuaternionKeyframeTrack(
325 | ".bones[" + boneData.bone.name + "].quaternion",
326 | boneData.quat.times,
327 | boneData.quat.values
328 | ) );
329 |
330 | }
331 |
332 | }
333 |
334 | mixer.uncacheAction( clip );
335 |
336 | return new AnimationClip( clip.name, - 1, convertedTracks );
337 |
338 | },
339 |
340 | getHelperFromSkeleton: function ( skeleton ) {
341 |
342 | var source = new SkeletonHelper( skeleton.bones[ 0 ] );
343 | source.skeleton = skeleton;
344 |
345 | return source;
346 |
347 | },
348 |
349 | getSkeletonOffsets: function () {
350 |
351 | var targetParentPos = new Vector3(),
352 | targetPos = new Vector3(),
353 | sourceParentPos = new Vector3(),
354 | sourcePos = new Vector3(),
355 | targetDir = new Vector2(),
356 | sourceDir = new Vector2();
357 |
358 | return function ( target, source, options ) {
359 |
360 | options = options || {};
361 | options.hip = options.hip !== undefined ? options.hip : "hip";
362 | options.names = options.names || {};
363 |
364 | if ( ! source.isObject3D ) {
365 |
366 | source = this.getHelperFromSkeleton( source );
367 |
368 | }
369 |
370 | var nameKeys = Object.keys( options.names ),
371 | nameValues = Object.values( options.names ),
372 | sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones( source ),
373 | bones = target.isObject3D ? target.skeleton.bones : this.getBones( target ),
374 | offsets = [],
375 | bone, boneTo,
376 | name, i;
377 |
378 | target.skeleton.pose();
379 |
380 | for ( i = 0; i < bones.length; ++ i ) {
381 |
382 | bone = bones[ i ];
383 | name = options.names[ bone.name ] || bone.name;
384 |
385 | boneTo = this.getBoneByName( name, sourceBones );
386 |
387 | if ( boneTo && name !== options.hip ) {
388 |
389 | var boneParent = this.getNearestBone( bone.parent, nameKeys ),
390 | boneToParent = this.getNearestBone( boneTo.parent, nameValues );
391 |
392 | boneParent.updateMatrixWorld();
393 | boneToParent.updateMatrixWorld();
394 |
395 | targetParentPos.setFromMatrixPosition( boneParent.matrixWorld );
396 | targetPos.setFromMatrixPosition( bone.matrixWorld );
397 |
398 | sourceParentPos.setFromMatrixPosition( boneToParent.matrixWorld );
399 | sourcePos.setFromMatrixPosition( boneTo.matrixWorld );
400 |
401 | targetDir.subVectors(
402 | new Vector2( targetPos.x, targetPos.y ),
403 | new Vector2( targetParentPos.x, targetParentPos.y )
404 | ).normalize();
405 |
406 | sourceDir.subVectors(
407 | new Vector2( sourcePos.x, sourcePos.y ),
408 | new Vector2( sourceParentPos.x, sourceParentPos.y )
409 | ).normalize();
410 |
411 | var laterialAngle = targetDir.angle() - sourceDir.angle();
412 |
413 | var offset = new Matrix4().makeRotationFromEuler(
414 | new Euler(
415 | 0,
416 | 0,
417 | laterialAngle
418 | )
419 | );
420 |
421 | bone.matrix.multiply( offset );
422 |
423 | bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
424 |
425 | bone.updateMatrixWorld();
426 |
427 | offsets[ name ] = offset;
428 |
429 | }
430 |
431 | }
432 |
433 | return offsets;
434 |
435 | };
436 |
437 | }(),
438 |
439 | renameBones: function ( skeleton, names ) {
440 |
441 | var bones = this.getBones( skeleton );
442 |
443 | for ( var i = 0; i < bones.length; ++ i ) {
444 |
445 | var bone = bones[ i ];
446 |
447 | if ( names[ bone.name ] ) {
448 |
449 | bone.name = names[ bone.name ];
450 |
451 | }
452 |
453 | }
454 |
455 | return this;
456 |
457 | },
458 |
459 | getBones: function ( skeleton ) {
460 |
461 | return Array.isArray( skeleton ) ? skeleton : skeleton.bones;
462 |
463 | },
464 |
465 | getBoneByName: function ( name, skeleton ) {
466 |
467 | for ( var i = 0, bones = this.getBones( skeleton ); i < bones.length; i ++ ) {
468 |
469 | if ( name === bones[ i ].name )
470 |
471 | return bones[ i ];
472 |
473 | }
474 |
475 | },
476 |
477 | getNearestBone: function ( bone, names ) {
478 |
479 | while ( bone.isBone ) {
480 |
481 | if ( names.indexOf( bone.name ) !== - 1 ) {
482 |
483 | return bone;
484 |
485 | }
486 |
487 | bone = bone.parent;
488 |
489 | }
490 |
491 | },
492 |
493 | findBoneTrackData: function ( name, tracks ) {
494 |
495 | var regexp = /\[(.*)\]\.(.*)/,
496 | result = { name: name };
497 |
498 | for ( var i = 0; i < tracks.length; ++ i ) {
499 |
500 | // 1 is track name
501 | // 2 is track type
502 | var trackData = regexp.exec( tracks[ i ].name );
503 |
504 | if ( trackData && name === trackData[ 1 ] ) {
505 |
506 | result[ trackData[ 2 ] ] = i;
507 |
508 | }
509 |
510 | }
511 |
512 | return result;
513 |
514 | },
515 |
516 | getEqualsBonesNames: function ( skeleton, targetSkeleton ) {
517 |
518 | var sourceBones = this.getBones( skeleton ),
519 | targetBones = this.getBones( targetSkeleton ),
520 | bones = [];
521 |
522 | search : for ( var i = 0; i < sourceBones.length; i ++ ) {
523 |
524 | var boneName = sourceBones[ i ].name;
525 |
526 | for ( var j = 0; j < targetBones.length; j ++ ) {
527 |
528 | if ( boneName === targetBones[ j ].name ) {
529 |
530 | bones.push( boneName );
531 |
532 | continue search;
533 |
534 | }
535 |
536 | }
537 |
538 | }
539 |
540 | return bones;
541 |
542 | },
543 |
544 | clone: function ( source ) {
545 |
546 | var sourceLookup = new Map();
547 | var cloneLookup = new Map();
548 |
549 | var clone = source.clone();
550 |
551 | parallelTraverse( source, clone, function ( sourceNode, clonedNode ) {
552 |
553 | sourceLookup.set( clonedNode, sourceNode );
554 | cloneLookup.set( sourceNode, clonedNode );
555 |
556 | } );
557 |
558 | clone.traverse( function ( node ) {
559 |
560 | if ( ! node.isSkinnedMesh ) return;
561 |
562 | var clonedMesh = node;
563 | var sourceMesh = sourceLookup.get( node );
564 | var sourceBones = sourceMesh.skeleton.bones;
565 |
566 | clonedMesh.skeleton = sourceMesh.skeleton.clone();
567 | clonedMesh.bindMatrix.copy( sourceMesh.bindMatrix );
568 |
569 | clonedMesh.skeleton.bones = sourceBones.map( function ( bone ) {
570 |
571 | return cloneLookup.get( bone );
572 |
573 | } );
574 |
575 | clonedMesh.bind( clonedMesh.skeleton, clonedMesh.bindMatrix );
576 |
577 | } );
578 |
579 | return clone;
580 |
581 | }
582 |
583 | };
584 |
585 |
586 | function parallelTraverse( a, b, callback ) {
587 |
588 | callback( a, b );
589 |
590 | for ( var i = 0; i < a.children.length; i ++ ) {
591 |
592 | parallelTraverse( a.children[ i ], b.children[ i ], callback );
593 |
594 | }
595 |
596 | }
597 |
598 | return { SkeletonUtils };
599 | }
--------------------------------------------------------------------------------
/jsm/loaders/STLLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author aleeper / http://adamleeper.com/
3 | * @author mrdoob / http://mrdoob.com/
4 | * @author gero3 / https://github.com/gero3
5 | * @author Mugen87 / https://github.com/Mugen87
6 | * @author neverhood311 / https://github.com/neverhood311
7 | *
8 | * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
9 | *
10 | * Supports both binary and ASCII encoded files, with automatic detection of type.
11 | *
12 | * The loader returns a non-indexed buffer geometry.
13 | *
14 | * Limitations:
15 | * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL).
16 | * There is perhaps some question as to how valid it is to always assume little-endian-ness.
17 | * ASCII decoding assumes file is UTF-8.
18 | *
19 | * Usage:
20 | * var loader = new STLLoader();
21 | * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
22 | * scene.add( new THREE.Mesh( geometry ) );
23 | * });
24 | *
25 | * For binary STLs geometry might contain colors for vertices. To use it:
26 | * // use the same code to load STL as above
27 | * if (geometry.hasColors) {
28 | * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors });
29 | * } else { .... }
30 | * var mesh = new THREE.Mesh( geometry, material );
31 | *
32 | * For ASCII STLs containing multiple solids, each solid is assigned to a different group.
33 | * Groups can be used to assign a different color by defining an array of materials with the same length of
34 | * geometry.groups and passing it to the Mesh constructor:
35 | *
36 | * var mesh = new THREE.Mesh( geometry, material );
37 | *
38 | * For example:
39 | *
40 | * var materials = [];
41 | * var nGeometryGroups = geometry.groups.length;
42 | *
43 | * var colorMap = ...; // Some logic to index colors.
44 | *
45 | * for (var i = 0; i < nGeometryGroups; i++) {
46 | *
47 | * var material = new THREE.MeshPhongMaterial({
48 | * color: colorMap[i],
49 | * wireframe: false
50 | * });
51 | *
52 | * }
53 | *
54 | * materials.push(material);
55 | * var mesh = new THREE.Mesh(geometry, materials);
56 | */
57 |
58 | export default function (THREE) {
59 | let {
60 | BufferAttribute,
61 | BufferGeometry,
62 | FileLoader,
63 | Float32BufferAttribute,
64 | Loader,
65 | LoaderUtils,
66 | Vector3
67 | } = THREE;
68 |
69 |
70 | var STLLoader = function ( manager ) {
71 |
72 | Loader.call( this, manager );
73 |
74 | };
75 |
76 | STLLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
77 |
78 | constructor: STLLoader,
79 |
80 | load: function ( url, onLoad, onProgress, onError ) {
81 |
82 | var scope = this;
83 |
84 | var loader = new FileLoader( scope.manager );
85 | loader.setPath( scope.path );
86 | loader.setResponseType( 'arraybuffer' );
87 | loader.load( url, function ( text ) {
88 |
89 | try {
90 |
91 | onLoad( scope.parse( text ) );
92 |
93 | } catch ( exception ) {
94 |
95 | if ( onError ) {
96 |
97 | onError( exception );
98 |
99 | }
100 |
101 | }
102 |
103 | }, onProgress, onError );
104 |
105 | },
106 |
107 | parse: function ( data ) {
108 |
109 | function isBinary( data ) {
110 |
111 | var expect, face_size, n_faces, reader;
112 | reader = new DataView( data );
113 | face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 );
114 | n_faces = reader.getUint32( 80, true );
115 | expect = 80 + ( 32 / 8 ) + ( n_faces * face_size );
116 |
117 | if ( expect === reader.byteLength ) {
118 |
119 | return true;
120 |
121 | }
122 |
123 | // An ASCII STL data must begin with 'solid ' as the first six bytes.
124 | // However, ASCII STLs lacking the SPACE after the 'd' are known to be
125 | // plentiful. So, check the first 5 bytes for 'solid'.
126 |
127 | // Several encodings, such as UTF-8, precede the text with up to 5 bytes:
128 | // https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
129 | // Search for "solid" to start anywhere after those prefixes.
130 |
131 | // US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd'
132 |
133 | var solid = [ 115, 111, 108, 105, 100 ];
134 |
135 | for ( var off = 0; off < 5; off ++ ) {
136 |
137 | // If "solid" text is matched to the current offset, declare it to be an ASCII STL.
138 |
139 | if ( matchDataViewAt( solid, reader, off ) ) return false;
140 |
141 | }
142 |
143 | // Couldn't find "solid" text at the beginning; it is binary STL.
144 |
145 | return true;
146 |
147 | }
148 |
149 | function matchDataViewAt( query, reader, offset ) {
150 |
151 | // Check if each byte in query matches the corresponding byte from the current offset
152 |
153 | for ( var i = 0, il = query.length; i < il; i ++ ) {
154 |
155 | if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false;
156 |
157 | }
158 |
159 | return true;
160 |
161 | }
162 |
163 | function parseBinary( data ) {
164 |
165 | var reader = new DataView( data );
166 | var faces = reader.getUint32( 80, true );
167 |
168 | var r, g, b, hasColors = false, colors;
169 | var defaultR, defaultG, defaultB, alpha;
170 |
171 | // process STL header
172 | // check for default color in header ("COLOR=rgba" sequence).
173 |
174 | for ( var index = 0; index < 80 - 10; index ++ ) {
175 |
176 | if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) &&
177 | ( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) &&
178 | ( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) {
179 |
180 | hasColors = true;
181 | colors = new Float32Array( faces * 3 * 3 );
182 |
183 | defaultR = reader.getUint8( index + 6 ) / 255;
184 | defaultG = reader.getUint8( index + 7 ) / 255;
185 | defaultB = reader.getUint8( index + 8 ) / 255;
186 | alpha = reader.getUint8( index + 9 ) / 255;
187 |
188 | }
189 |
190 | }
191 |
192 | var dataOffset = 84;
193 | var faceLength = 12 * 4 + 2;
194 |
195 | var geometry = new BufferGeometry();
196 |
197 | var vertices = new Float32Array( faces * 3 * 3 );
198 | var normals = new Float32Array( faces * 3 * 3 );
199 |
200 | for ( var face = 0; face < faces; face ++ ) {
201 |
202 | var start = dataOffset + face * faceLength;
203 | var normalX = reader.getFloat32( start, true );
204 | var normalY = reader.getFloat32( start + 4, true );
205 | var normalZ = reader.getFloat32( start + 8, true );
206 |
207 | if ( hasColors ) {
208 |
209 | var packedColor = reader.getUint16( start + 48, true );
210 |
211 | if ( ( packedColor & 0x8000 ) === 0 ) {
212 |
213 | // facet has its own unique color
214 |
215 | r = ( packedColor & 0x1F ) / 31;
216 | g = ( ( packedColor >> 5 ) & 0x1F ) / 31;
217 | b = ( ( packedColor >> 10 ) & 0x1F ) / 31;
218 |
219 | } else {
220 |
221 | r = defaultR;
222 | g = defaultG;
223 | b = defaultB;
224 |
225 | }
226 |
227 | }
228 |
229 | for ( var i = 1; i <= 3; i ++ ) {
230 |
231 | var vertexstart = start + i * 12;
232 | var componentIdx = ( face * 3 * 3 ) + ( ( i - 1 ) * 3 );
233 |
234 | vertices[ componentIdx ] = reader.getFloat32( vertexstart, true );
235 | vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true );
236 | vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true );
237 |
238 | normals[ componentIdx ] = normalX;
239 | normals[ componentIdx + 1 ] = normalY;
240 | normals[ componentIdx + 2 ] = normalZ;
241 |
242 | if ( hasColors ) {
243 |
244 | colors[ componentIdx ] = r;
245 | colors[ componentIdx + 1 ] = g;
246 | colors[ componentIdx + 2 ] = b;
247 |
248 | }
249 |
250 | }
251 |
252 | }
253 |
254 | geometry.addAttribute( 'position', new BufferAttribute( vertices, 3 ) );
255 | geometry.addAttribute( 'normal', new BufferAttribute( normals, 3 ) );
256 |
257 | if ( hasColors ) {
258 |
259 | geometry.addAttribute( 'color', new BufferAttribute( colors, 3 ) );
260 | geometry.hasColors = true;
261 | geometry.alpha = alpha;
262 |
263 | }
264 |
265 | return geometry;
266 |
267 | }
268 |
269 | function parseASCII( data ) {
270 |
271 | var geometry = new BufferGeometry();
272 | var patternSolid = /solid([\s\S]*?)endsolid/g;
273 | var patternFace = /facet([\s\S]*?)endfacet/g;
274 | var faceCounter = 0;
275 |
276 | var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source;
277 | var patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' );
278 | var patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' );
279 |
280 | var vertices = [];
281 | var normals = [];
282 |
283 | var normal = new Vector3();
284 |
285 | var result;
286 |
287 | var groupVertexes = [];
288 | var groupCount = 0;
289 | var startVertex = 0;
290 | var endVertex = 0;
291 |
292 | while ( ( result = patternSolid.exec( data ) ) !== null ) {
293 |
294 | startVertex = endVertex;
295 |
296 | var solid = result[ 0 ];
297 |
298 | while ( ( result = patternFace.exec( solid ) ) !== null ) {
299 |
300 | var vertexCountPerFace = 0;
301 | var normalCountPerFace = 0;
302 |
303 | var text = result[ 0 ];
304 |
305 | while ( ( result = patternNormal.exec( text ) ) !== null ) {
306 |
307 | normal.x = parseFloat( result[ 1 ] );
308 | normal.y = parseFloat( result[ 2 ] );
309 | normal.z = parseFloat( result[ 3 ] );
310 | normalCountPerFace ++;
311 |
312 | }
313 |
314 | while ( ( result = patternVertex.exec( text ) ) !== null ) {
315 |
316 | vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );
317 | normals.push( normal.x, normal.y, normal.z );
318 | vertexCountPerFace ++;
319 | endVertex ++;
320 |
321 | }
322 |
323 | // every face have to own ONE valid normal
324 |
325 | if ( normalCountPerFace !== 1 ) {
326 |
327 | console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter );
328 |
329 | }
330 |
331 | // each face have to own THREE valid vertices
332 |
333 | if ( vertexCountPerFace !== 3 ) {
334 |
335 | console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter );
336 |
337 | }
338 |
339 | faceCounter ++;
340 |
341 | }
342 |
343 | groupVertexes.push( { startVertex: startVertex, endVertex: endVertex } );
344 | groupCount ++;
345 |
346 | }
347 |
348 | geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
349 | geometry.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
350 |
351 | if ( groupCount > 0 ) {
352 |
353 | for ( var i = 0; i < groupVertexes.length; i ++ ) {
354 |
355 | geometry.addGroup( groupVertexes[ i ].startVertex, groupVertexes[ i ].endVertex, i );
356 |
357 | }
358 |
359 | }
360 |
361 | return geometry;
362 |
363 | }
364 |
365 | function ensureString( buffer ) {
366 |
367 | if ( typeof buffer !== 'string' ) {
368 |
369 | return LoaderUtils.decodeText( new Uint8Array( buffer ) );
370 |
371 | }
372 |
373 | return buffer;
374 |
375 | }
376 |
377 | function ensureBinary( buffer ) {
378 |
379 | if ( typeof buffer === 'string' ) {
380 |
381 | var array_buffer = new Uint8Array( buffer.length );
382 | for ( var i = 0; i < buffer.length; i ++ ) {
383 |
384 | array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian
385 |
386 | }
387 |
388 | return array_buffer.buffer || array_buffer;
389 |
390 | } else {
391 |
392 | return buffer;
393 |
394 | }
395 |
396 | }
397 |
398 | // start
399 |
400 | var binData = ensureBinary( data );
401 |
402 | return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) );
403 |
404 | }
405 |
406 | } );
407 |
408 | return { STLLoader };
409 | }
--------------------------------------------------------------------------------
/jsm/loaders/RGBELoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Nikos M. / https://github.com/foo123/
3 | */
4 | export default function (THREE) {
5 | let {
6 | DataTextureLoader,
7 | FloatType,
8 | HalfFloatType,
9 | LinearEncoding,
10 | LinearFilter,
11 | NearestFilter,
12 | RGBEEncoding,
13 | RGBEFormat,
14 | RGBFormat,
15 | UnsignedByteType
16 | } = THREE;
17 |
18 | // https://github.com/mrdoob/three.js/issues/5552
19 | // http://en.wikipedia.org/wiki/RGBE_image_format
20 |
21 | var RGBELoader = function ( manager ) {
22 |
23 | DataTextureLoader.call( this, manager );
24 |
25 | this.type = UnsignedByteType;
26 |
27 | };
28 |
29 | RGBELoader.prototype = Object.assign( Object.create( DataTextureLoader.prototype ), {
30 |
31 | constructor: RGBELoader,
32 |
33 | // adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html
34 |
35 | parse: function ( buffer ) {
36 |
37 | var
38 | /* return codes for rgbe routines */
39 | //RGBE_RETURN_SUCCESS = 0,
40 | RGBE_RETURN_FAILURE = - 1,
41 |
42 | /* default error routine. change this to change error handling */
43 | rgbe_read_error = 1,
44 | rgbe_write_error = 2,
45 | rgbe_format_error = 3,
46 | rgbe_memory_error = 4,
47 | rgbe_error = function ( rgbe_error_code, msg ) {
48 |
49 | switch ( rgbe_error_code ) {
50 |
51 | case rgbe_read_error: console.error( "RGBELoader Read Error: " + ( msg || '' ) );
52 | break;
53 | case rgbe_write_error: console.error( "RGBELoader Write Error: " + ( msg || '' ) );
54 | break;
55 | case rgbe_format_error: console.error( "RGBELoader Bad File Format: " + ( msg || '' ) );
56 | break;
57 | default:
58 | case rgbe_memory_error: console.error( "RGBELoader: Error: " + ( msg || '' ) );
59 |
60 | }
61 | return RGBE_RETURN_FAILURE;
62 |
63 | },
64 |
65 | /* offsets to red, green, and blue components in a data (float) pixel */
66 | //RGBE_DATA_RED = 0,
67 | //RGBE_DATA_GREEN = 1,
68 | //RGBE_DATA_BLUE = 2,
69 |
70 | /* number of floats per pixel, use 4 since stored in rgba image format */
71 | //RGBE_DATA_SIZE = 4,
72 |
73 | /* flags indicating which fields in an rgbe_header_info are valid */
74 | RGBE_VALID_PROGRAMTYPE = 1,
75 | RGBE_VALID_FORMAT = 2,
76 | RGBE_VALID_DIMENSIONS = 4,
77 |
78 | NEWLINE = "\n",
79 |
80 | fgets = function ( buffer, lineLimit, consume ) {
81 |
82 | lineLimit = ! lineLimit ? 1024 : lineLimit;
83 | var p = buffer.pos,
84 | i = - 1, len = 0, s = '', chunkSize = 128,
85 | chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) )
86 | ;
87 | while ( ( 0 > ( i = chunk.indexOf( NEWLINE ) ) ) && ( len < lineLimit ) && ( p < buffer.byteLength ) ) {
88 |
89 | s += chunk; len += chunk.length;
90 | p += chunkSize;
91 | chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) );
92 |
93 | }
94 |
95 | if ( - 1 < i ) {
96 |
97 | /*for (i=l-1; i>=0; i--) {
98 | byteCode = m.charCodeAt(i);
99 | if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++;
100 | else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2;
101 | if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate
102 | }*/
103 | if ( false !== consume ) buffer.pos += len + i + 1;
104 | return s + chunk.slice( 0, i );
105 |
106 | }
107 | return false;
108 |
109 | },
110 |
111 | /* minimal header reading. modify if you want to parse more information */
112 | RGBE_ReadHeader = function ( buffer ) {
113 |
114 | var line, match,
115 |
116 | // regexes to parse header info fields
117 | magic_token_re = /^#\?(\S+)$/,
118 | gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/,
119 | exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/,
120 | format_re = /^\s*FORMAT=(\S+)\s*$/,
121 | dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/,
122 |
123 | // RGBE format header struct
124 | header = {
125 |
126 | valid: 0, /* indicate which fields are valid */
127 |
128 | string: '', /* the actual header string */
129 |
130 | comments: '', /* comments found in header */
131 |
132 | programtype: 'RGBE', /* listed at beginning of file to identify it after "#?". defaults to "RGBE" */
133 |
134 | format: '', /* RGBE format, default 32-bit_rle_rgbe */
135 |
136 | gamma: 1.0, /* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */
137 |
138 | exposure: 1.0, /* a value of 1.0 in an image corresponds to watts/steradian/m^2. defaults to 1.0 */
139 |
140 | width: 0, height: 0 /* image dimensions, width/height */
141 |
142 | };
143 |
144 | if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) {
145 |
146 | return rgbe_error( rgbe_read_error, "no header found" );
147 |
148 | }
149 | /* if you want to require the magic token then uncomment the next line */
150 | if ( ! ( match = line.match( magic_token_re ) ) ) {
151 |
152 | return rgbe_error( rgbe_format_error, "bad initial token" );
153 |
154 | }
155 | header.valid |= RGBE_VALID_PROGRAMTYPE;
156 | header.programtype = match[ 1 ];
157 | header.string += line + "\n";
158 |
159 | while ( true ) {
160 |
161 | line = fgets( buffer );
162 | if ( false === line ) break;
163 | header.string += line + "\n";
164 |
165 | if ( '#' === line.charAt( 0 ) ) {
166 |
167 | header.comments += line + "\n";
168 | continue; // comment line
169 |
170 | }
171 |
172 | if ( match = line.match( gamma_re ) ) {
173 |
174 | header.gamma = parseFloat( match[ 1 ], 10 );
175 |
176 | }
177 | if ( match = line.match( exposure_re ) ) {
178 |
179 | header.exposure = parseFloat( match[ 1 ], 10 );
180 |
181 | }
182 | if ( match = line.match( format_re ) ) {
183 |
184 | header.valid |= RGBE_VALID_FORMAT;
185 | header.format = match[ 1 ];//'32-bit_rle_rgbe';
186 |
187 | }
188 | if ( match = line.match( dimensions_re ) ) {
189 |
190 | header.valid |= RGBE_VALID_DIMENSIONS;
191 | header.height = parseInt( match[ 1 ], 10 );
192 | header.width = parseInt( match[ 2 ], 10 );
193 |
194 | }
195 |
196 | if ( ( header.valid & RGBE_VALID_FORMAT ) && ( header.valid & RGBE_VALID_DIMENSIONS ) ) break;
197 |
198 | }
199 |
200 | if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) {
201 |
202 | return rgbe_error( rgbe_format_error, "missing format specifier" );
203 |
204 | }
205 | if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) {
206 |
207 | return rgbe_error( rgbe_format_error, "missing image size specifier" );
208 |
209 | }
210 |
211 | return header;
212 |
213 | },
214 |
215 | RGBE_ReadPixels_RLE = function ( buffer, w, h ) {
216 |
217 | var data_rgba, offset, pos, count, byteValue,
218 | scanline_buffer, ptr, ptr_end, i, l, off, isEncodedRun,
219 | scanline_width = w, num_scanlines = h, rgbeStart
220 | ;
221 |
222 | if (
223 | // run length encoding is not allowed so read flat
224 | ( ( scanline_width < 8 ) || ( scanline_width > 0x7fff ) ) ||
225 | // this file is not run length encoded
226 | ( ( 2 !== buffer[ 0 ] ) || ( 2 !== buffer[ 1 ] ) || ( buffer[ 2 ] & 0x80 ) )
227 | ) {
228 |
229 | // return the flat buffer
230 | return new Uint8Array( buffer );
231 |
232 | }
233 |
234 | if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) {
235 |
236 | return rgbe_error( rgbe_format_error, "wrong scanline width" );
237 |
238 | }
239 |
240 | data_rgba = new Uint8Array( 4 * w * h );
241 |
242 | if ( ! data_rgba || ! data_rgba.length ) {
243 |
244 | return rgbe_error( rgbe_memory_error, "unable to allocate buffer space" );
245 |
246 | }
247 |
248 | offset = 0; pos = 0; ptr_end = 4 * scanline_width;
249 | rgbeStart = new Uint8Array( 4 );
250 | scanline_buffer = new Uint8Array( ptr_end );
251 |
252 | // read in each successive scanline
253 | while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) {
254 |
255 | if ( pos + 4 > buffer.byteLength ) {
256 |
257 | return rgbe_error( rgbe_read_error );
258 |
259 | }
260 |
261 | rgbeStart[ 0 ] = buffer[ pos ++ ];
262 | rgbeStart[ 1 ] = buffer[ pos ++ ];
263 | rgbeStart[ 2 ] = buffer[ pos ++ ];
264 | rgbeStart[ 3 ] = buffer[ pos ++ ];
265 |
266 | if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) {
267 |
268 | return rgbe_error( rgbe_format_error, "bad rgbe scanline format" );
269 |
270 | }
271 |
272 | // read each of the four channels for the scanline into the buffer
273 | // first red, then green, then blue, then exponent
274 | ptr = 0;
275 | while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) {
276 |
277 | count = buffer[ pos ++ ];
278 | isEncodedRun = count > 128;
279 | if ( isEncodedRun ) count -= 128;
280 |
281 | if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) {
282 |
283 | return rgbe_error( rgbe_format_error, "bad scanline data" );
284 |
285 | }
286 |
287 | if ( isEncodedRun ) {
288 |
289 | // a (encoded) run of the same value
290 | byteValue = buffer[ pos ++ ];
291 | for ( i = 0; i < count; i ++ ) {
292 |
293 | scanline_buffer[ ptr ++ ] = byteValue;
294 |
295 | }
296 | //ptr += count;
297 |
298 | } else {
299 |
300 | // a literal-run
301 | scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr );
302 | ptr += count; pos += count;
303 |
304 | }
305 |
306 | }
307 |
308 |
309 | // now convert data from buffer into rgba
310 | // first red, then green, then blue, then exponent (alpha)
311 | l = scanline_width; //scanline_buffer.byteLength;
312 | for ( i = 0; i < l; i ++ ) {
313 |
314 | off = 0;
315 | data_rgba[ offset ] = scanline_buffer[ i + off ];
316 | off += scanline_width; //1;
317 | data_rgba[ offset + 1 ] = scanline_buffer[ i + off ];
318 | off += scanline_width; //1;
319 | data_rgba[ offset + 2 ] = scanline_buffer[ i + off ];
320 | off += scanline_width; //1;
321 | data_rgba[ offset + 3 ] = scanline_buffer[ i + off ];
322 | offset += 4;
323 |
324 | }
325 |
326 | num_scanlines --;
327 |
328 | }
329 |
330 | return data_rgba;
331 |
332 | };
333 |
334 | var RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) {
335 |
336 | var e = sourceArray[ sourceOffset + 3 ];
337 | var scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
338 |
339 | destArray[ destOffset + 0 ] = sourceArray[ sourceOffset + 0 ] * scale;
340 | destArray[ destOffset + 1 ] = sourceArray[ sourceOffset + 1 ] * scale;
341 | destArray[ destOffset + 2 ] = sourceArray[ sourceOffset + 2 ] * scale;
342 |
343 | };
344 |
345 | var RGBEByteToRGBHalf = ( function () {
346 |
347 | // Source: http://gamedev.stackexchange.com/questions/17326/conversion-of-a-number-from-single-precision-floating-point-representation-to-a/17410#17410
348 |
349 | var floatView = new Float32Array( 1 );
350 | var int32View = new Int32Array( floatView.buffer );
351 |
352 | /* This method is faster than the OpenEXR implementation (very often
353 | * used, eg. in Ogre), with the additional benefit of rounding, inspired
354 | * by James Tursa?s half-precision code. */
355 | function toHalf( val ) {
356 |
357 | floatView[ 0 ] = val;
358 | var x = int32View[ 0 ];
359 |
360 | var bits = ( x >> 16 ) & 0x8000; /* Get the sign */
361 | var m = ( x >> 12 ) & 0x07ff; /* Keep one extra bit for rounding */
362 | var e = ( x >> 23 ) & 0xff; /* Using int is faster here */
363 |
364 | /* If zero, or denormal, or exponent underflows too much for a denormal
365 | * half, return signed zero. */
366 | if ( e < 103 ) return bits;
367 |
368 | /* If NaN, return NaN. If Inf or exponent overflow, return Inf. */
369 | if ( e > 142 ) {
370 |
371 | bits |= 0x7c00;
372 | /* If exponent was 0xff and one mantissa bit was set, it means NaN,
373 | * not Inf, so make sure we set one mantissa bit too. */
374 | bits |= ( ( e == 255 ) ? 0 : 1 ) && ( x & 0x007fffff );
375 | return bits;
376 |
377 | }
378 |
379 | /* If exponent underflows but not too much, return a denormal */
380 | if ( e < 113 ) {
381 |
382 | m |= 0x0800;
383 | /* Extra rounding may overflow and set mantissa to 0 and exponent
384 | * to 1, which is OK. */
385 | bits |= ( m >> ( 114 - e ) ) + ( ( m >> ( 113 - e ) ) & 1 );
386 | return bits;
387 |
388 | }
389 |
390 | bits |= ( ( e - 112 ) << 10 ) | ( m >> 1 );
391 | /* Extra rounding. An overflow will set mantissa to 0 and increment
392 | * the exponent, which is OK. */
393 | bits += m & 1;
394 | return bits;
395 |
396 | }
397 |
398 | return function ( sourceArray, sourceOffset, destArray, destOffset ) {
399 |
400 | var e = sourceArray[ sourceOffset + 3 ];
401 | var scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
402 |
403 | destArray[ destOffset + 0 ] = toHalf( sourceArray[ sourceOffset + 0 ] * scale );
404 | destArray[ destOffset + 1 ] = toHalf( sourceArray[ sourceOffset + 1 ] * scale );
405 | destArray[ destOffset + 2 ] = toHalf( sourceArray[ sourceOffset + 2 ] * scale );
406 |
407 | };
408 |
409 | } )();
410 |
411 | var byteArray = new Uint8Array( buffer );
412 | byteArray.pos = 0;
413 | var rgbe_header_info = RGBE_ReadHeader( byteArray );
414 |
415 | if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) {
416 |
417 | var w = rgbe_header_info.width,
418 | h = rgbe_header_info.height,
419 | image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h );
420 |
421 | if ( RGBE_RETURN_FAILURE !== image_rgba_data ) {
422 |
423 | switch ( this.type ) {
424 |
425 | case UnsignedByteType:
426 |
427 | var data = image_rgba_data;
428 | var format = RGBEFormat; // handled as THREE.RGBAFormat in shaders
429 | var type = UnsignedByteType;
430 | break;
431 |
432 | case FloatType:
433 |
434 | var numElements = ( image_rgba_data.length / 4 ) * 3;
435 | var floatArray = new Float32Array( numElements );
436 |
437 | for ( var j = 0; j < numElements; j ++ ) {
438 |
439 | RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 3 );
440 |
441 | }
442 |
443 | var data = floatArray;
444 | var format = RGBFormat;
445 | var type = FloatType;
446 | break;
447 |
448 | case HalfFloatType:
449 |
450 | var numElements = ( image_rgba_data.length / 4 ) * 3;
451 | var halfArray = new Uint16Array( numElements );
452 |
453 | for ( var j = 0; j < numElements; j ++ ) {
454 |
455 | RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 3 );
456 |
457 | }
458 |
459 | var data = halfArray;
460 | var format = RGBFormat;
461 | var type = HalfFloatType;
462 | break;
463 |
464 | default:
465 |
466 | console.error( 'THREE.RGBELoader: unsupported type: ', this.type );
467 | break;
468 |
469 | }
470 |
471 | return {
472 | width: w, height: h,
473 | data: data,
474 | header: rgbe_header_info.string,
475 | gamma: rgbe_header_info.gamma,
476 | exposure: rgbe_header_info.exposure,
477 | format: format,
478 | type: type
479 | };
480 |
481 | }
482 |
483 | }
484 |
485 | return null;
486 |
487 | },
488 |
489 | setDataType: function ( value ) {
490 |
491 | this.type = value;
492 | return this;
493 |
494 | },
495 |
496 | load: function ( url, onLoad, onProgress, onError ) {
497 |
498 | function onLoadCallback( texture, texData ) {
499 |
500 | switch ( texture.type ) {
501 |
502 | case UnsignedByteType:
503 |
504 | texture.encoding = RGBEEncoding;
505 | texture.minFilter = NearestFilter;
506 | texture.magFilter = NearestFilter;
507 | texture.generateMipmaps = false;
508 | texture.flipY = true;
509 | break;
510 |
511 | case FloatType:
512 |
513 | texture.encoding = LinearEncoding;
514 | texture.minFilter = LinearFilter;
515 | texture.magFilter = LinearFilter;
516 | texture.generateMipmaps = false;
517 | texture.flipY = true;
518 | break;
519 |
520 | case HalfFloatType:
521 |
522 | texture.encoding = LinearEncoding;
523 | texture.minFilter = LinearFilter;
524 | texture.magFilter = LinearFilter;
525 | texture.generateMipmaps = false;
526 | texture.flipY = true;
527 | break;
528 |
529 | }
530 |
531 | if ( onLoad ) onLoad( texture, texData );
532 |
533 | }
534 |
535 | return DataTextureLoader.prototype.load.call( this, url, onLoadCallback, onProgress, onError );
536 |
537 | }
538 |
539 | } );
540 |
541 | return { RGBELoader };
542 | }
--------------------------------------------------------------------------------
/jsm/controls/TrackballControls.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author Eberhard Graether / http://egraether.com/
3 | * @author Mark Lundin / http://mark-lundin.com
4 | * @author Simone Manini / http://daron1337.github.io
5 | * @author Luca Antiga / http://lantiga.github.io
6 | */
7 | export default function (THREE) {
8 |
9 | let {
10 | EventDispatcher,
11 | MOUSE,
12 | Quaternion,
13 | Vector2,
14 | Vector3,
15 | global: window,
16 | } = THREE;
17 | let document = window.document;
18 |
19 |
20 | var TrackballControls = function (object, domElement) {
21 |
22 | if (domElement === undefined) console.warn('THREE.TrackballControls: The second parameter "domElement" is now mandatory.');
23 | if (domElement === document) console.error('THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.');
24 |
25 | var _this = this;
26 | var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
27 |
28 | this.object = object;
29 | this.domElement = domElement;
30 |
31 | // API
32 |
33 | this.enabled = true;
34 |
35 | this.screen = { left: 0, top: 0, width: 0, height: 0 };
36 |
37 | this.rotateSpeed = 1.0;
38 | this.zoomSpeed = 1.2;
39 | this.panSpeed = 0.3;
40 |
41 | this.noRotate = false;
42 | this.noZoom = false;
43 | this.noPan = false;
44 |
45 | this.staticMoving = false;
46 | this.dynamicDampingFactor = 0.2;
47 |
48 | this.minDistance = 0;
49 | this.maxDistance = Infinity;
50 |
51 | this.keys = [65 /*A*/, 83 /*S*/, 68 /*D*/];
52 |
53 | this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.ZOOM, RIGHT: MOUSE.PAN };
54 |
55 | // internals
56 |
57 | this.target = new Vector3();
58 |
59 | var EPS = 0.000001;
60 |
61 | var lastPosition = new Vector3();
62 | var lastZoom = 1;
63 |
64 | var _state = STATE.NONE,
65 | _keyState = STATE.NONE,
66 |
67 | _eye = new Vector3(),
68 |
69 | _movePrev = new Vector2(),
70 | _moveCurr = new Vector2(),
71 |
72 | _lastAxis = new Vector3(),
73 | _lastAngle = 0,
74 |
75 | _zoomStart = new Vector2(),
76 | _zoomEnd = new Vector2(),
77 |
78 | _touchZoomDistanceStart = 0,
79 | _touchZoomDistanceEnd = 0,
80 |
81 | _panStart = new Vector2(),
82 | _panEnd = new Vector2();
83 |
84 | // for reset
85 |
86 | this.target0 = this.target.clone();
87 | this.position0 = this.object.position.clone();
88 | this.up0 = this.object.up.clone();
89 | this.zoom0 = this.object.zoom;
90 |
91 | // events
92 |
93 | var changeEvent = { type: 'change' };
94 | var startEvent = { type: 'start' };
95 | var endEvent = { type: 'end' };
96 |
97 |
98 | // methods
99 |
100 | this.handleResize = function () {
101 |
102 | var box = this.domElement.getBoundingClientRect();
103 | // adjustments come from similar code in the jquery offset() function
104 | // var d = this.domElement.ownerDocument.documentElement;
105 | var d = document.documentElement;
106 | this.screen.left = box.left + window.scrollX - d.clientLeft;
107 | this.screen.top = box.top + window.scrollY - d.clientTop;
108 | this.screen.width = box.width;
109 | this.screen.height = box.height;
110 |
111 | };
112 |
113 | var getMouseOnScreen = (function () {
114 |
115 | var vector = new Vector2();
116 |
117 | return function getMouseOnScreen(pageX, pageY) {
118 |
119 | vector.set(
120 | (pageX - _this.screen.left) / _this.screen.width,
121 | (pageY - _this.screen.top) / _this.screen.height
122 | );
123 |
124 | return vector;
125 |
126 | };
127 |
128 | }());
129 |
130 | var getMouseOnCircle = (function () {
131 |
132 | var vector = new Vector2();
133 |
134 | return function getMouseOnCircle(pageX, pageY) {
135 |
136 | vector.set(
137 | ((pageX - _this.screen.width * 0.5 - _this.screen.left) / (_this.screen.width * 0.5)),
138 | ((_this.screen.height + 2 * (_this.screen.top - pageY)) / _this.screen.width) // screen.width intentional
139 | );
140 |
141 | return vector;
142 |
143 | };
144 |
145 | }());
146 |
147 | this.rotateCamera = (function () {
148 |
149 | var axis = new Vector3(),
150 | quaternion = new Quaternion(),
151 | eyeDirection = new Vector3(),
152 | objectUpDirection = new Vector3(),
153 | objectSidewaysDirection = new Vector3(),
154 | moveDirection = new Vector3(),
155 | angle;
156 |
157 | return function rotateCamera() {
158 |
159 | moveDirection.set(_moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0);
160 | angle = moveDirection.length();
161 |
162 | if (angle) {
163 |
164 | _eye.copy(_this.object.position).sub(_this.target);
165 |
166 | eyeDirection.copy(_eye).normalize();
167 | objectUpDirection.copy(_this.object.up).normalize();
168 | objectSidewaysDirection.crossVectors(objectUpDirection, eyeDirection).normalize();
169 |
170 | objectUpDirection.setLength(_moveCurr.y - _movePrev.y);
171 | objectSidewaysDirection.setLength(_moveCurr.x - _movePrev.x);
172 |
173 | moveDirection.copy(objectUpDirection.add(objectSidewaysDirection));
174 |
175 | axis.crossVectors(moveDirection, _eye).normalize();
176 |
177 | angle *= _this.rotateSpeed;
178 | quaternion.setFromAxisAngle(axis, angle);
179 |
180 | _eye.applyQuaternion(quaternion);
181 | _this.object.up.applyQuaternion(quaternion);
182 |
183 | _lastAxis.copy(axis);
184 | _lastAngle = angle;
185 |
186 | } else if (!_this.staticMoving && _lastAngle) {
187 |
188 | _lastAngle *= Math.sqrt(1.0 - _this.dynamicDampingFactor);
189 | _eye.copy(_this.object.position).sub(_this.target);
190 | quaternion.setFromAxisAngle(_lastAxis, _lastAngle);
191 | _eye.applyQuaternion(quaternion);
192 | _this.object.up.applyQuaternion(quaternion);
193 |
194 | }
195 |
196 | _movePrev.copy(_moveCurr);
197 |
198 | };
199 |
200 | }());
201 |
202 |
203 | this.zoomCamera = function () {
204 |
205 | var factor;
206 |
207 | if (_state === STATE.TOUCH_ZOOM_PAN) {
208 |
209 | factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
210 | _touchZoomDistanceStart = _touchZoomDistanceEnd;
211 |
212 | if (_this.object.isPerspectiveCamera) {
213 |
214 | _eye.multiplyScalar(factor);
215 |
216 | } else if (_this.object.isOrthographicCamera) {
217 |
218 | _this.object.zoom *= factor;
219 | _this.object.updateProjectionMatrix();
220 |
221 | } else {
222 |
223 | console.warn('THREE.TrackballControls: Unsupported camera type');
224 |
225 | }
226 |
227 | } else {
228 |
229 | factor = 1.0 + (_zoomEnd.y - _zoomStart.y) * _this.zoomSpeed;
230 |
231 | if (factor !== 1.0 && factor > 0.0) {
232 |
233 | if (_this.object.isPerspectiveCamera) {
234 |
235 | _eye.multiplyScalar(factor);
236 |
237 | } else if (_this.object.isOrthographicCamera) {
238 |
239 | _this.object.zoom /= factor;
240 | _this.object.updateProjectionMatrix();
241 |
242 | } else {
243 |
244 | console.warn('THREE.TrackballControls: Unsupported camera type');
245 |
246 | }
247 |
248 | }
249 |
250 | if (_this.staticMoving) {
251 |
252 | _zoomStart.copy(_zoomEnd);
253 |
254 | } else {
255 |
256 | _zoomStart.y += (_zoomEnd.y - _zoomStart.y) * this.dynamicDampingFactor;
257 |
258 | }
259 |
260 | }
261 |
262 | };
263 |
264 | this.panCamera = (function () {
265 |
266 | var mouseChange = new Vector2(),
267 | objectUp = new Vector3(),
268 | pan = new Vector3();
269 |
270 | return function panCamera() {
271 |
272 | mouseChange.copy(_panEnd).sub(_panStart);
273 |
274 | if (mouseChange.lengthSq()) {
275 |
276 | if (_this.object.isOrthographicCamera) {
277 |
278 | var scale_x = (_this.object.right - _this.object.left) / _this.object.zoom / _this.domElement.clientWidth;
279 | var scale_y = (_this.object.top - _this.object.bottom) / _this.object.zoom / _this.domElement.clientWidth;
280 |
281 | mouseChange.x *= scale_x;
282 | mouseChange.y *= scale_y;
283 |
284 | }
285 |
286 | mouseChange.multiplyScalar(_eye.length() * _this.panSpeed);
287 |
288 | pan.copy(_eye).cross(_this.object.up).setLength(mouseChange.x);
289 | pan.add(objectUp.copy(_this.object.up).setLength(mouseChange.y));
290 |
291 | _this.object.position.add(pan);
292 | _this.target.add(pan);
293 |
294 | if (_this.staticMoving) {
295 |
296 | _panStart.copy(_panEnd);
297 |
298 | } else {
299 |
300 | _panStart.add(mouseChange.subVectors(_panEnd, _panStart).multiplyScalar(_this.dynamicDampingFactor));
301 |
302 | }
303 |
304 | }
305 |
306 | };
307 |
308 | }());
309 |
310 | this.checkDistances = function () {
311 |
312 | if (!_this.noZoom || !_this.noPan) {
313 |
314 | if (_eye.lengthSq() > _this.maxDistance * _this.maxDistance) {
315 |
316 | _this.object.position.addVectors(_this.target, _eye.setLength(_this.maxDistance));
317 | _zoomStart.copy(_zoomEnd);
318 |
319 | }
320 |
321 | if (_eye.lengthSq() < _this.minDistance * _this.minDistance) {
322 |
323 | _this.object.position.addVectors(_this.target, _eye.setLength(_this.minDistance));
324 | _zoomStart.copy(_zoomEnd);
325 |
326 | }
327 |
328 | }
329 |
330 | };
331 |
332 | this.update = function () {
333 |
334 | _eye.subVectors(_this.object.position, _this.target);
335 |
336 | if (!_this.noRotate) {
337 |
338 | _this.rotateCamera();
339 |
340 | }
341 |
342 | if (!_this.noZoom) {
343 |
344 | _this.zoomCamera();
345 |
346 | }
347 |
348 | if (!_this.noPan) {
349 |
350 | _this.panCamera();
351 |
352 | }
353 |
354 | _this.object.position.addVectors(_this.target, _eye);
355 |
356 | if (_this.object.isPerspectiveCamera) {
357 |
358 | _this.checkDistances();
359 |
360 | _this.object.lookAt(_this.target);
361 |
362 | if (lastPosition.distanceToSquared(_this.object.position) > EPS) {
363 |
364 | _this.dispatchEvent(changeEvent);
365 |
366 | lastPosition.copy(_this.object.position);
367 |
368 | }
369 |
370 | } else if (_this.object.isOrthographicCamera) {
371 |
372 | _this.object.lookAt(_this.target);
373 |
374 | if (lastPosition.distanceToSquared(_this.object.position) > EPS || lastZoom !== _this.object.zoom) {
375 |
376 | _this.dispatchEvent(changeEvent);
377 |
378 | lastPosition.copy(_this.object.position);
379 | lastZoom = _this.object.zoom;
380 |
381 | }
382 |
383 | } else {
384 |
385 | console.warn('THREE.TrackballControls: Unsupported camera type');
386 |
387 | }
388 |
389 | };
390 |
391 | this.reset = function () {
392 |
393 | _state = STATE.NONE;
394 | _keyState = STATE.NONE;
395 |
396 | _this.target.copy(_this.target0);
397 | _this.object.position.copy(_this.position0);
398 | _this.object.up.copy(_this.up0);
399 | _this.object.zoom = _this.zoom0;
400 |
401 | _this.object.updateProjectionMatrix();
402 |
403 | _eye.subVectors(_this.object.position, _this.target);
404 |
405 | _this.object.lookAt(_this.target);
406 |
407 | _this.dispatchEvent(changeEvent);
408 |
409 | lastPosition.copy(_this.object.position);
410 | lastZoom = _this.object.zoom;
411 |
412 | };
413 |
414 | // listeners
415 |
416 | function keydown(event) {
417 |
418 | if (_this.enabled === false) return;
419 |
420 | window.removeEventListener('keydown', keydown);
421 |
422 | if (_keyState !== STATE.NONE) {
423 |
424 | return;
425 |
426 | } else if (event.keyCode === _this.keys[STATE.ROTATE] && !_this.noRotate) {
427 |
428 | _keyState = STATE.ROTATE;
429 |
430 | } else if (event.keyCode === _this.keys[STATE.ZOOM] && !_this.noZoom) {
431 |
432 | _keyState = STATE.ZOOM;
433 |
434 | } else if (event.keyCode === _this.keys[STATE.PAN] && !_this.noPan) {
435 |
436 | _keyState = STATE.PAN;
437 |
438 | }
439 |
440 | }
441 |
442 | function keyup() {
443 |
444 | if (_this.enabled === false) return;
445 |
446 | _keyState = STATE.NONE;
447 |
448 | window.addEventListener('keydown', keydown, false);
449 |
450 | }
451 |
452 | function mousedown(event) {
453 |
454 | if (_this.enabled === false) return;
455 |
456 | event.preventDefault();
457 | event.stopPropagation();
458 |
459 | if (_state === STATE.NONE) {
460 |
461 | switch (event.button) {
462 |
463 | case _this.mouseButtons.LEFT:
464 | _state = STATE.ROTATE;
465 | break;
466 |
467 | case _this.mouseButtons.MIDDLE:
468 | _state = STATE.ZOOM;
469 | break;
470 |
471 | case _this.mouseButtons.RIGHT:
472 | _state = STATE.PAN;
473 | break;
474 |
475 | default:
476 | _state = STATE.NONE;
477 |
478 | }
479 |
480 | }
481 |
482 | var state = (_keyState !== STATE.NONE) ? _keyState : _state;
483 |
484 | if (state === STATE.ROTATE && !_this.noRotate) {
485 |
486 | _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
487 | _movePrev.copy(_moveCurr);
488 |
489 | } else if (state === STATE.ZOOM && !_this.noZoom) {
490 |
491 | _zoomStart.copy(getMouseOnScreen(event.pageX, event.pageY));
492 | _zoomEnd.copy(_zoomStart);
493 |
494 | } else if (state === STATE.PAN && !_this.noPan) {
495 |
496 | _panStart.copy(getMouseOnScreen(event.pageX, event.pageY));
497 | _panEnd.copy(_panStart);
498 |
499 | }
500 |
501 | document.addEventListener('mousemove', mousemove, false);
502 | document.addEventListener('mouseup', mouseup, false);
503 |
504 | _this.dispatchEvent(startEvent);
505 |
506 | }
507 |
508 | function mousemove(event) {
509 |
510 | if (_this.enabled === false) return;
511 |
512 | event.preventDefault();
513 | event.stopPropagation();
514 |
515 | var state = (_keyState !== STATE.NONE) ? _keyState : _state;
516 |
517 | if (state === STATE.ROTATE && !_this.noRotate) {
518 |
519 | _movePrev.copy(_moveCurr);
520 | _moveCurr.copy(getMouseOnCircle(event.pageX, event.pageY));
521 |
522 | } else if (state === STATE.ZOOM && !_this.noZoom) {
523 |
524 | _zoomEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
525 |
526 | } else if (state === STATE.PAN && !_this.noPan) {
527 |
528 | _panEnd.copy(getMouseOnScreen(event.pageX, event.pageY));
529 |
530 | }
531 |
532 | }
533 |
534 | function mouseup(event) {
535 |
536 | if (_this.enabled === false) return;
537 |
538 | event.preventDefault();
539 | event.stopPropagation();
540 |
541 | _state = STATE.NONE;
542 |
543 | document.removeEventListener('mousemove', mousemove);
544 | document.removeEventListener('mouseup', mouseup);
545 | _this.dispatchEvent(endEvent);
546 |
547 | }
548 |
549 | function mousewheel(event) {
550 |
551 | if (_this.enabled === false) return;
552 |
553 | if (_this.noZoom === true) return;
554 |
555 | event.preventDefault();
556 | event.stopPropagation();
557 |
558 | switch (event.deltaMode) {
559 |
560 | case 2:
561 | // Zoom in pages
562 | _zoomStart.y -= event.deltaY * 0.025;
563 | break;
564 |
565 | case 1:
566 | // Zoom in lines
567 | _zoomStart.y -= event.deltaY * 0.01;
568 | break;
569 |
570 | default:
571 | // undefined, 0, assume pixels
572 | _zoomStart.y -= event.deltaY * 0.00025;
573 | break;
574 |
575 | }
576 |
577 | _this.dispatchEvent(startEvent);
578 | _this.dispatchEvent(endEvent);
579 |
580 | }
581 |
582 | function touchstart(event) {
583 |
584 | if (_this.enabled === false) return;
585 |
586 | event.preventDefault();
587 |
588 | switch (event.touches.length) {
589 |
590 | case 1:
591 | _state = STATE.TOUCH_ROTATE;
592 | _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
593 | _movePrev.copy(_moveCurr);
594 | break;
595 |
596 | default: // 2 or more
597 | _state = STATE.TOUCH_ZOOM_PAN;
598 | var dx = event.touches[0].pageX - event.touches[1].pageX;
599 | var dy = event.touches[0].pageY - event.touches[1].pageY;
600 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt(dx * dx + dy * dy);
601 |
602 | var x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
603 | var y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
604 | _panStart.copy(getMouseOnScreen(x, y));
605 | _panEnd.copy(_panStart);
606 | break;
607 |
608 | }
609 |
610 | _this.dispatchEvent(startEvent);
611 |
612 | }
613 |
614 | function touchmove(event) {
615 |
616 | if (_this.enabled === false) return;
617 |
618 | event.preventDefault();
619 | event.stopPropagation();
620 |
621 | switch (event.touches.length) {
622 |
623 | case 1:
624 | _movePrev.copy(_moveCurr);
625 | _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
626 | break;
627 |
628 | default: // 2 or more
629 | var dx = event.touches[0].pageX - event.touches[1].pageX;
630 | var dy = event.touches[0].pageY - event.touches[1].pageY;
631 | _touchZoomDistanceEnd = Math.sqrt(dx * dx + dy * dy);
632 |
633 | var x = (event.touches[0].pageX + event.touches[1].pageX) / 2;
634 | var y = (event.touches[0].pageY + event.touches[1].pageY) / 2;
635 | _panEnd.copy(getMouseOnScreen(x, y));
636 | break;
637 |
638 | }
639 |
640 | }
641 |
642 | function touchend(event) {
643 |
644 | if (_this.enabled === false) return;
645 |
646 | switch (event.touches.length) {
647 |
648 | case 0:
649 | _state = STATE.NONE;
650 | break;
651 |
652 | case 1:
653 | _state = STATE.TOUCH_ROTATE;
654 | _moveCurr.copy(getMouseOnCircle(event.touches[0].pageX, event.touches[0].pageY));
655 | _movePrev.copy(_moveCurr);
656 | break;
657 |
658 | }
659 |
660 | _this.dispatchEvent(endEvent);
661 |
662 | }
663 |
664 | function contextmenu(event) {
665 |
666 | if (_this.enabled === false) return;
667 |
668 | event.preventDefault();
669 |
670 | }
671 |
672 | this.dispose = function () {
673 |
674 | this.domElement.removeEventListener('contextmenu', contextmenu, false);
675 | this.domElement.removeEventListener('mousedown', mousedown, false);
676 | this.domElement.removeEventListener('wheel', mousewheel, false);
677 |
678 | this.domElement.removeEventListener('touchstart', touchstart, false);
679 | this.domElement.removeEventListener('touchend', touchend, false);
680 | this.domElement.removeEventListener('touchmove', touchmove, false);
681 |
682 | document.removeEventListener('mousemove', mousemove, false);
683 | document.removeEventListener('mouseup', mouseup, false);
684 |
685 | window.removeEventListener('keydown', keydown, false);
686 | window.removeEventListener('keyup', keyup, false);
687 |
688 | };
689 |
690 | this.domElement.addEventListener('contextmenu', contextmenu, false);
691 | this.domElement.addEventListener('mousedown', mousedown, false);
692 | this.domElement.addEventListener('wheel', mousewheel, false);
693 |
694 | this.domElement.addEventListener('touchstart', touchstart, false);
695 | this.domElement.addEventListener('touchend', touchend, false);
696 | this.domElement.addEventListener('touchmove', touchmove, false);
697 |
698 | window.addEventListener('keydown', keydown, false);
699 | window.addEventListener('keyup', keyup, false);
700 |
701 | this.handleResize();
702 |
703 | // force an update at start
704 | this.update();
705 |
706 | };
707 |
708 | TrackballControls.prototype = Object.create(EventDispatcher.prototype);
709 | TrackballControls.prototype.constructor = TrackballControls;
710 |
711 | return { TrackballControls };
712 | }
--------------------------------------------------------------------------------
/jsm/loaders/OBJLoader.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com/
3 | */
4 |
5 | export default function (THREE) {
6 | let {
7 | BufferGeometry,
8 | FileLoader,
9 | Float32BufferAttribute,
10 | Group,
11 | LineBasicMaterial,
12 | LineSegments,
13 | Loader,
14 | Material,
15 | Mesh,
16 | MeshPhongMaterial,
17 | NoColors,
18 | Points,
19 | PointsMaterial,
20 | VertexColors
21 | } = THREE;
22 |
23 |
24 |
25 | // o object_name | g group_name
26 | var object_pattern = /^[og]\s*(.+)?/;
27 | // mtllib file_reference
28 | var material_library_pattern = /^mtllib /;
29 | // usemtl material_name
30 | var material_use_pattern = /^usemtl /;
31 |
32 | function ParserState() {
33 |
34 | var state = {
35 | objects: [],
36 | object: {},
37 |
38 | vertices: [],
39 | normals: [],
40 | colors: [],
41 | uvs: [],
42 |
43 | materialLibraries: [],
44 |
45 | startObject: function (name, fromDeclaration) {
46 |
47 | // If the current object (initial from reset) is not from a g/o declaration in the parsed
48 | // file. We need to use it for the first parsed g/o to keep things in sync.
49 | if (this.object && this.object.fromDeclaration === false) {
50 |
51 | this.object.name = name;
52 | this.object.fromDeclaration = (fromDeclaration !== false);
53 | return;
54 |
55 | }
56 |
57 | var previousMaterial = (this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined);
58 |
59 | if (this.object && typeof this.object._finalize === 'function') {
60 |
61 | this.object._finalize(true);
62 |
63 | }
64 |
65 | this.object = {
66 | name: name || '',
67 | fromDeclaration: (fromDeclaration !== false),
68 |
69 | geometry: {
70 | vertices: [],
71 | normals: [],
72 | colors: [],
73 | uvs: []
74 | },
75 | materials: [],
76 | smooth: true,
77 |
78 | startMaterial: function (name, libraries) {
79 |
80 | var previous = this._finalize(false);
81 |
82 | // New usemtl declaration overwrites an inherited material, except if faces were declared
83 | // after the material, then it must be preserved for proper MultiMaterial continuation.
84 | if (previous && (previous.inherited || previous.groupCount <= 0)) {
85 |
86 | this.materials.splice(previous.index, 1);
87 |
88 | }
89 |
90 | var material = {
91 | index: this.materials.length,
92 | name: name || '',
93 | mtllib: (Array.isArray(libraries) && libraries.length > 0 ? libraries[libraries.length - 1] : ''),
94 | smooth: (previous !== undefined ? previous.smooth : this.smooth),
95 | groupStart: (previous !== undefined ? previous.groupEnd : 0),
96 | groupEnd: - 1,
97 | groupCount: - 1,
98 | inherited: false,
99 |
100 | clone: function (index) {
101 |
102 | var cloned = {
103 | index: (typeof index === 'number' ? index : this.index),
104 | name: this.name,
105 | mtllib: this.mtllib,
106 | smooth: this.smooth,
107 | groupStart: 0,
108 | groupEnd: - 1,
109 | groupCount: - 1,
110 | inherited: false
111 | };
112 | cloned.clone = this.clone.bind(cloned);
113 | return cloned;
114 |
115 | }
116 | };
117 |
118 | this.materials.push(material);
119 |
120 | return material;
121 |
122 | },
123 |
124 | currentMaterial: function () {
125 |
126 | if (this.materials.length > 0) {
127 |
128 | return this.materials[this.materials.length - 1];
129 |
130 | }
131 |
132 | return undefined;
133 |
134 | },
135 |
136 | _finalize: function (end) {
137 |
138 | var lastMultiMaterial = this.currentMaterial();
139 | if (lastMultiMaterial && lastMultiMaterial.groupEnd === - 1) {
140 |
141 | lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
142 | lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
143 | lastMultiMaterial.inherited = false;
144 |
145 | }
146 |
147 | // Ignore objects tail materials if no face declarations followed them before a new o/g started.
148 | if (end && this.materials.length > 1) {
149 |
150 | for (var mi = this.materials.length - 1; mi >= 0; mi--) {
151 |
152 | if (this.materials[mi].groupCount <= 0) {
153 |
154 | this.materials.splice(mi, 1);
155 |
156 | }
157 |
158 | }
159 |
160 | }
161 |
162 | // Guarantee at least one empty material, this makes the creation later more straight forward.
163 | if (end && this.materials.length === 0) {
164 |
165 | this.materials.push({
166 | name: '',
167 | smooth: this.smooth
168 | });
169 |
170 | }
171 |
172 | return lastMultiMaterial;
173 |
174 | }
175 | };
176 |
177 | // Inherit previous objects material.
178 | // Spec tells us that a declared material must be set to all objects until a new material is declared.
179 | // If a usemtl declaration is encountered while this new object is being parsed, it will
180 | // overwrite the inherited material. Exception being that there was already face declarations
181 | // to the inherited material, then it will be preserved for proper MultiMaterial continuation.
182 |
183 | if (previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function') {
184 |
185 | var declared = previousMaterial.clone(0);
186 | declared.inherited = true;
187 | this.object.materials.push(declared);
188 |
189 | }
190 |
191 | this.objects.push(this.object);
192 |
193 | },
194 |
195 | finalize: function () {
196 |
197 | if (this.object && typeof this.object._finalize === 'function') {
198 |
199 | this.object._finalize(true);
200 |
201 | }
202 |
203 | },
204 |
205 | parseVertexIndex: function (value, len) {
206 |
207 | var index = parseInt(value, 10);
208 | return (index >= 0 ? index - 1 : index + len / 3) * 3;
209 |
210 | },
211 |
212 | parseNormalIndex: function (value, len) {
213 |
214 | var index = parseInt(value, 10);
215 | return (index >= 0 ? index - 1 : index + len / 3) * 3;
216 |
217 | },
218 |
219 | parseUVIndex: function (value, len) {
220 |
221 | var index = parseInt(value, 10);
222 | return (index >= 0 ? index - 1 : index + len / 2) * 2;
223 |
224 | },
225 |
226 | addVertex: function (a, b, c) {
227 |
228 | var src = this.vertices;
229 | var dst = this.object.geometry.vertices;
230 |
231 | dst.push(src[a + 0], src[a + 1], src[a + 2]);
232 | dst.push(src[b + 0], src[b + 1], src[b + 2]);
233 | dst.push(src[c + 0], src[c + 1], src[c + 2]);
234 |
235 | },
236 |
237 | addVertexPoint: function (a) {
238 |
239 | var src = this.vertices;
240 | var dst = this.object.geometry.vertices;
241 |
242 | dst.push(src[a + 0], src[a + 1], src[a + 2]);
243 |
244 | },
245 |
246 | addVertexLine: function (a) {
247 |
248 | var src = this.vertices;
249 | var dst = this.object.geometry.vertices;
250 |
251 | dst.push(src[a + 0], src[a + 1], src[a + 2]);
252 |
253 | },
254 |
255 | addNormal: function (a, b, c) {
256 |
257 | var src = this.normals;
258 | var dst = this.object.geometry.normals;
259 |
260 | dst.push(src[a + 0], src[a + 1], src[a + 2]);
261 | dst.push(src[b + 0], src[b + 1], src[b + 2]);
262 | dst.push(src[c + 0], src[c + 1], src[c + 2]);
263 |
264 | },
265 |
266 | addColor: function (a, b, c) {
267 |
268 | var src = this.colors;
269 | var dst = this.object.geometry.colors;
270 |
271 | dst.push(src[a + 0], src[a + 1], src[a + 2]);
272 | dst.push(src[b + 0], src[b + 1], src[b + 2]);
273 | dst.push(src[c + 0], src[c + 1], src[c + 2]);
274 |
275 | },
276 |
277 | addUV: function (a, b, c) {
278 |
279 | var src = this.uvs;
280 | var dst = this.object.geometry.uvs;
281 |
282 | dst.push(src[a + 0], src[a + 1]);
283 | dst.push(src[b + 0], src[b + 1]);
284 | dst.push(src[c + 0], src[c + 1]);
285 |
286 | },
287 |
288 | addUVLine: function (a) {
289 |
290 | var src = this.uvs;
291 | var dst = this.object.geometry.uvs;
292 |
293 | dst.push(src[a + 0], src[a + 1]);
294 |
295 | },
296 |
297 | addFace: function (a, b, c, ua, ub, uc, na, nb, nc) {
298 |
299 | var vLen = this.vertices.length;
300 |
301 | var ia = this.parseVertexIndex(a, vLen);
302 | var ib = this.parseVertexIndex(b, vLen);
303 | var ic = this.parseVertexIndex(c, vLen);
304 |
305 | this.addVertex(ia, ib, ic);
306 |
307 | if (this.colors.length > 0) {
308 |
309 | this.addColor(ia, ib, ic);
310 |
311 | }
312 |
313 | if (ua !== undefined && ua !== '') {
314 |
315 | var uvLen = this.uvs.length;
316 | ia = this.parseUVIndex(ua, uvLen);
317 | ib = this.parseUVIndex(ub, uvLen);
318 | ic = this.parseUVIndex(uc, uvLen);
319 | this.addUV(ia, ib, ic);
320 |
321 | }
322 |
323 | if (na !== undefined && na !== '') {
324 |
325 | // Normals are many times the same. If so, skip function call and parseInt.
326 | var nLen = this.normals.length;
327 | ia = this.parseNormalIndex(na, nLen);
328 |
329 | ib = na === nb ? ia : this.parseNormalIndex(nb, nLen);
330 | ic = na === nc ? ia : this.parseNormalIndex(nc, nLen);
331 |
332 | this.addNormal(ia, ib, ic);
333 |
334 | }
335 |
336 | },
337 |
338 | addPointGeometry: function (vertices) {
339 |
340 | this.object.geometry.type = 'Points';
341 |
342 | var vLen = this.vertices.length;
343 |
344 | for (var vi = 0, l = vertices.length; vi < l; vi++) {
345 |
346 | this.addVertexPoint(this.parseVertexIndex(vertices[vi], vLen));
347 |
348 | }
349 |
350 | },
351 |
352 | addLineGeometry: function (vertices, uvs) {
353 |
354 | this.object.geometry.type = 'Line';
355 |
356 | var vLen = this.vertices.length;
357 | var uvLen = this.uvs.length;
358 |
359 | for (var vi = 0, l = vertices.length; vi < l; vi++) {
360 |
361 | this.addVertexLine(this.parseVertexIndex(vertices[vi], vLen));
362 |
363 | }
364 |
365 | for (var uvi = 0, l = uvs.length; uvi < l; uvi++) {
366 |
367 | this.addUVLine(this.parseUVIndex(uvs[uvi], uvLen));
368 |
369 | }
370 |
371 | }
372 |
373 | };
374 |
375 | state.startObject('', false);
376 |
377 | return state;
378 |
379 | }
380 |
381 | //
382 |
383 | function OBJLoader(manager) {
384 |
385 | Loader.call(this, manager);
386 |
387 | this.materials = null;
388 |
389 | }
390 |
391 | OBJLoader.prototype = Object.assign(Object.create(Loader.prototype), {
392 |
393 | constructor: OBJLoader,
394 |
395 | load: function (url, onLoad, onProgress, onError) {
396 |
397 | var scope = this;
398 |
399 | var loader = new FileLoader(scope.manager);
400 | loader.setPath(this.path);
401 | loader.load(url, function (text) {
402 |
403 | onLoad(scope.parse(text));
404 |
405 | }, onProgress, onError);
406 |
407 | },
408 |
409 | setMaterials: function (materials) {
410 |
411 | this.materials = materials;
412 |
413 | return this;
414 |
415 | },
416 |
417 | parse: function (text) {
418 |
419 | console.time('OBJLoader');
420 |
421 | var state = new ParserState();
422 |
423 | if (text.indexOf('\r\n') !== - 1) {
424 |
425 | // This is faster than String.split with regex that splits on both
426 | text = text.replace(/\r\n/g, '\n');
427 |
428 | }
429 |
430 | if (text.indexOf('\\\n') !== - 1) {
431 |
432 | // join lines separated by a line continuation character (\)
433 | text = text.replace(/\\\n/g, '');
434 |
435 | }
436 |
437 | var lines = text.split('\n');
438 | var line = '', lineFirstChar = '';
439 | var lineLength = 0;
440 | var result = [];
441 |
442 | // Faster to just trim left side of the line. Use if available.
443 | var trimLeft = (typeof ''.trimLeft === 'function');
444 |
445 | for (var i = 0, l = lines.length; i < l; i++) {
446 |
447 | line = lines[i];
448 |
449 | line = trimLeft ? line.trimLeft() : line.trim();
450 |
451 | lineLength = line.length;
452 |
453 | if (lineLength === 0) continue;
454 |
455 | lineFirstChar = line.charAt(0);
456 |
457 | // @todo invoke passed in handler if any
458 | if (lineFirstChar === '#') continue;
459 |
460 | if (lineFirstChar === 'v') {
461 | // \f -> 匹配一个换页
462 | // \n -> 匹配一个换行符
463 | // \r -> 匹配一个回车符
464 | // \t -> 匹配一个制表符
465 | // \v -> 匹配一个垂直制表符
466 | // \s+ 表示匹配任意多个上面的字符
467 | var data = line.split(/\s+/);
468 | // var data = line.split(" ");
469 |
470 | switch (data[0]) {
471 |
472 | case 'v':
473 | state.vertices.push(
474 | parseFloat(data[1]),
475 | parseFloat(data[2]),
476 | parseFloat(data[3])
477 | );
478 | if (data.length >= 7) {
479 |
480 | state.colors.push(
481 | parseFloat(data[4]),
482 | parseFloat(data[5]),
483 | parseFloat(data[6])
484 |
485 | );
486 |
487 | }
488 | break;
489 | case 'vn':
490 | state.normals.push(
491 | parseFloat(data[1]),
492 | parseFloat(data[2]),
493 | parseFloat(data[3])
494 | );
495 | break;
496 | case 'vt':
497 | state.uvs.push(
498 | parseFloat(data[1]),
499 | parseFloat(data[2])
500 | );
501 | break;
502 |
503 | }
504 |
505 | } else if (lineFirstChar === 'f') {
506 |
507 | var lineData = line.substr(1).trim();
508 | var vertexData = lineData.split(/\s+/);
509 | // var vertexData = lineData.split(" ");
510 | var faceVertices = [];
511 |
512 | // Parse the face vertex data into an easy to work with format
513 |
514 | for (var j = 0, jl = vertexData.length; j < jl; j++) {
515 |
516 | var vertex = vertexData[j];
517 |
518 | if (vertex.length > 0) {
519 |
520 | var vertexParts = vertex.split('/');
521 | faceVertices.push(vertexParts);
522 |
523 | }
524 |
525 | }
526 |
527 | // Draw an edge between the first vertex and all subsequent vertices to form an n-gon
528 |
529 | var v1 = faceVertices[0];
530 |
531 | for (var j = 1, jl = faceVertices.length - 1; j < jl; j++) {
532 |
533 | var v2 = faceVertices[j];
534 | var v3 = faceVertices[j + 1];
535 |
536 | state.addFace(
537 | v1[0], v2[0], v3[0],
538 | v1[1], v2[1], v3[1],
539 | v1[2], v2[2], v3[2]
540 | );
541 |
542 | }
543 |
544 | } else if (lineFirstChar === 'l') {
545 |
546 | var lineParts = line.substring(1).trim().split(" ");
547 | var lineVertices = [], lineUVs = [];
548 |
549 | if (line.indexOf("/") === - 1) {
550 |
551 | lineVertices = lineParts;
552 |
553 | } else {
554 |
555 | for (var li = 0, llen = lineParts.length; li < llen; li++) {
556 |
557 | var parts = lineParts[li].split("/");
558 |
559 | if (parts[0] !== "") lineVertices.push(parts[0]);
560 | if (parts[1] !== "") lineUVs.push(parts[1]);
561 |
562 | }
563 |
564 | }
565 | state.addLineGeometry(lineVertices, lineUVs);
566 |
567 | } else if (lineFirstChar === 'p') {
568 |
569 | var lineData = line.substr(1).trim();
570 | var pointData = lineData.split(" ");
571 |
572 | state.addPointGeometry(pointData);
573 |
574 | } else if ((result = object_pattern.exec(line)) !== null) {
575 |
576 | // o object_name
577 | // or
578 | // g group_name
579 |
580 | // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
581 | // var name = result[ 0 ].substr( 1 ).trim();
582 | var name = (" " + result[0].substr(1).trim()).substr(1);
583 |
584 | state.startObject(name);
585 |
586 | } else if (material_use_pattern.test(line)) {
587 |
588 | // material
589 |
590 | state.object.startMaterial(line.substring(7).trim(), state.materialLibraries);
591 |
592 | } else if (material_library_pattern.test(line)) {
593 |
594 | // mtl file
595 |
596 | state.materialLibraries.push(line.substring(7).trim());
597 |
598 | } else if (lineFirstChar === 's') {
599 |
600 | result = line.split(' ');
601 |
602 | // smooth shading
603 |
604 | // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
605 | // but does not define a usemtl for each face set.
606 | // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
607 | // This requires some care to not create extra material on each smooth value for "normal" obj files.
608 | // where explicit usemtl defines geometry groups.
609 | // Example asset: examples/models/obj/cerberus/Cerberus.obj
610 |
611 | /*
612 | * http://paulbourke.net/dataformats/obj/
613 | * or
614 | * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
615 | *
616 | * From chapter "Grouping" Syntax explanation "s group_number":
617 | * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
618 | * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
619 | * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
620 | * than 0."
621 | */
622 | if (result.length > 1) {
623 |
624 | var value = result[1].trim().toLowerCase();
625 | state.object.smooth = (value !== '0' && value !== 'off');
626 |
627 | } else {
628 |
629 | // ZBrush can produce "s" lines #11707
630 | state.object.smooth = true;
631 |
632 | }
633 | var material = state.object.currentMaterial();
634 | if (material) material.smooth = state.object.smooth;
635 |
636 | } else {
637 |
638 | // Handle null terminated files without exception
639 | if (line === '\0') continue;
640 |
641 | throw new Error('THREE.OBJLoader: Unexpected line: "' + line + '"');
642 |
643 | }
644 |
645 | }
646 |
647 | state.finalize();
648 |
649 | var container = new Group();
650 | container.materialLibraries = [].concat(state.materialLibraries);
651 |
652 | for (var i = 0, l = state.objects.length; i < l; i++) {
653 |
654 | var object = state.objects[i];
655 | var geometry = object.geometry;
656 | var materials = object.materials;
657 | var isLine = (geometry.type === 'Line');
658 | var isPoints = (geometry.type === 'Points');
659 | var hasVertexColors = false;
660 |
661 | // Skip o/g line declarations that did not follow with any faces
662 | if (geometry.vertices.length === 0) continue;
663 |
664 | var buffergeometry = new BufferGeometry();
665 |
666 | buffergeometry.setAttribute('position', new Float32BufferAttribute(geometry.vertices, 3));
667 |
668 | if (geometry.normals.length > 0) {
669 |
670 | buffergeometry.setAttribute('normal', new Float32BufferAttribute(geometry.normals, 3));
671 |
672 | } else {
673 |
674 | buffergeometry.computeVertexNormals();
675 |
676 | }
677 |
678 | if (geometry.colors.length > 0) {
679 |
680 | hasVertexColors = true;
681 | buffergeometry.setAttribute('color', new Float32BufferAttribute(geometry.colors, 3));
682 |
683 | }
684 |
685 | if (geometry.uvs.length > 0) {
686 |
687 | buffergeometry.setAttribute('uv', new Float32BufferAttribute(geometry.uvs, 2));
688 |
689 | }
690 |
691 | // Create materials
692 |
693 | var createdMaterials = [];
694 |
695 | for (var mi = 0, miLen = materials.length; mi < miLen; mi++) {
696 |
697 | var sourceMaterial = materials[mi];
698 | var material = undefined;
699 |
700 | if (this.materials !== null) {
701 |
702 | material = this.materials.create(sourceMaterial.name);
703 |
704 | // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
705 | if (isLine && material && !(material instanceof LineBasicMaterial)) {
706 |
707 | var materialLine = new LineBasicMaterial();
708 | Material.prototype.copy.call(materialLine, material);
709 | materialLine.color.copy(material.color);
710 | material = materialLine;
711 |
712 | } else if (isPoints && material && !(material instanceof PointsMaterial)) {
713 |
714 | var materialPoints = new PointsMaterial({ size: 10, sizeAttenuation: false });
715 | Material.prototype.copy.call(materialPoints, material);
716 | materialPoints.color.copy(material.color);
717 | materialPoints.map = material.map;
718 | material = materialPoints;
719 |
720 | }
721 |
722 | }
723 |
724 | if (!material) {
725 |
726 | if (isLine) {
727 |
728 | material = new LineBasicMaterial();
729 |
730 | } else if (isPoints) {
731 |
732 | material = new PointsMaterial({ size: 1, sizeAttenuation: false });
733 |
734 | } else {
735 |
736 | material = new MeshPhongMaterial();
737 |
738 | }
739 |
740 | material.name = sourceMaterial.name;
741 |
742 | }
743 |
744 | material.flatShading = sourceMaterial.smooth ? false : true;
745 | material.vertexColors = hasVertexColors ? VertexColors : NoColors;
746 |
747 | createdMaterials.push(material);
748 |
749 | }
750 |
751 | // Create mesh
752 |
753 | var mesh;
754 |
755 | if (createdMaterials.length > 1) {
756 |
757 | for (var mi = 0, miLen = materials.length; mi < miLen; mi++) {
758 |
759 | var sourceMaterial = materials[mi];
760 | buffergeometry.addGroup(sourceMaterial.groupStart, sourceMaterial.groupCount, mi);
761 |
762 | }
763 |
764 | if (isLine) {
765 |
766 | mesh = new LineSegments(buffergeometry, createdMaterials);
767 |
768 | } else if (isPoints) {
769 |
770 | mesh = new Points(buffergeometry, createdMaterials);
771 |
772 | } else {
773 |
774 | mesh = new Mesh(buffergeometry, createdMaterials);
775 |
776 | }
777 |
778 | } else {
779 |
780 | if (isLine) {
781 |
782 | mesh = new LineSegments(buffergeometry, createdMaterials[0]);
783 |
784 | } else if (isPoints) {
785 |
786 | mesh = new Points(buffergeometry, createdMaterials[0]);
787 |
788 | } else {
789 |
790 | mesh = new Mesh(buffergeometry, createdMaterials[0]);
791 |
792 | }
793 |
794 | }
795 |
796 | mesh.name = object.name;
797 |
798 | container.add(mesh);
799 |
800 | }
801 |
802 | console.timeEnd('OBJLoader');
803 |
804 | return container;
805 |
806 | }
807 |
808 | });
809 |
810 | return OBJLoader;
811 |
812 | }
813 |
--------------------------------------------------------------------------------