├── .gitignore
├── .jshintignore
├── .npmignore
├── LICENSE
├── README.md
├── build
└── glsl2js.js
├── dist
├── clay-viewer.js
└── clay-viewer.min.js
├── editor
├── asset
│ └── texture
│ │ ├── Barce_Rooftop_C.hdr
│ │ ├── Factory_Catwalk.hdr
│ │ ├── Grand_Canyon_C.hdr
│ │ ├── Hall.hdr
│ │ ├── Ice_Lake.hdr
│ │ ├── Old_Industrial_Hall.hdr
│ │ └── pisa.hdr
├── bundle.js
├── css
│ ├── controlKit.css
│ ├── iconfont
│ │ ├── iconfont.css
│ │ ├── iconfont.eot
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ └── iconfont.woff
│ └── main.css
├── electron.js
├── icon.icns
├── icon.ico
├── img
│ ├── chessboard.jpg
│ └── loading.gif
├── index-electron.html
├── index.html
├── lib
│ ├── FileAPI.js
│ ├── FileSaver.js
│ ├── browserfs.min.js
│ ├── browserfs.min.js.map
│ ├── controlKit.js
│ ├── filer.min.js
│ ├── idb.filesystem.js
│ ├── ion.rangeSlider.js
│ ├── ion.rangeSlider
│ │ ├── css
│ │ │ ├── ion.rangeSlider.css
│ │ │ ├── ion.rangeSlider.skinFlat.css
│ │ │ ├── ion.rangeSlider.skinHTML5.css
│ │ │ ├── ion.rangeSlider.skinModern.css
│ │ │ ├── ion.rangeSlider.skinNice.css
│ │ │ ├── ion.rangeSlider.skinSimple.css
│ │ │ └── normalize.css
│ │ └── img
│ │ │ ├── sprite-skin-flat.png
│ │ │ ├── sprite-skin-modern.png
│ │ │ ├── sprite-skin-nice.png
│ │ │ └── sprite-skin-simple.png
│ ├── jquery.min.js
│ ├── jszip.min.js
│ ├── pace.min.js
│ └── sweetalert.min.js
├── package.json
├── src
│ ├── browser
│ │ ├── convert.js
│ │ ├── download.js
│ │ └── openURL.js
│ ├── debug
│ │ ├── BoundingBoxGizmo.js
│ │ ├── Lines3DGeometry.js
│ │ ├── edge.glsl
│ │ ├── edge.glsl.js
│ │ ├── lines3d.glsl
│ │ ├── lines3d.glsl.js
│ │ └── renderOutline.js
│ ├── electron
│ │ ├── convert.js
│ │ ├── download.js
│ │ └── openURL.js
│ ├── env.js
│ ├── getDefaultMaterialConfig.js
│ ├── getDefaultSceneConfig.js
│ ├── glTFHelper.js
│ ├── main.js
│ ├── project.js
│ ├── timeline.js
│ └── ui
│ │ └── Texture.js
└── webpack.config.js
├── examples
├── asset
│ ├── DamagedHelmet
│ │ ├── DamagedHelmet.bin
│ │ ├── DamagedHelmet.gltf
│ │ ├── Default_AO.jpg
│ │ ├── Default_albedo.jpg
│ │ ├── Default_emissive.jpg
│ │ ├── Default_metalRoughness.jpg
│ │ └── Default_normal.jpg
│ └── texture
│ │ └── pisa.hdr
├── bg.jpg
├── lib
│ └── dat.gui.js
└── view.html
├── index.js
├── jsconfig.json
├── package.json
├── rollup.config.js
├── screenshots
├── editor.jpg
└── editor2.jpg
└── src
├── GestureMgr.js
├── HotspotManager.js
├── Viewer.js
├── defaultSceneConfig.js
├── graphic
├── DOF.glsl
├── DOF.glsl.js
├── EdgePass.js
├── EffectCompositor.js
├── RenderMain.js
├── SSAO.glsl
├── SSAO.glsl.js
├── SSAOPass.js
├── SSR.glsl
├── SSR.glsl.js
├── SSRPass.js
├── SceneHelper.js
├── TemporalSuperSampling.js
├── composite.js
├── compositeMobile.js
├── edge.glsl
├── edge.glsl.js
├── ground.glsl
├── ground.glsl.js
├── halton.js
├── helper.js
└── poissonKernel.js
└── util
└── getBoundingBoxWithSkinning.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.swp
3 | .project
4 | node_modules
5 | .idea/
6 | .vscode/
7 | npm-debug.log
8 | .jshintrc
9 | editor/electron
10 | editor/dist
11 |
12 |
13 | examples/baidu_asset
14 | examples/baidu_asset2
15 | examples/baidu_asset3
16 | examples/baidu_asset4
17 | examples/sample_asset
18 | examples/other
19 | examples/all.html
20 | examples/all2.html
21 | examples/all3.html
22 | examples/all4.html
23 | examples/test-view.html
--------------------------------------------------------------------------------
/.jshintignore:
--------------------------------------------------------------------------------
1 | node_modules/**
2 | dist/*.js
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /editor
2 | /examples
3 | /screenshots
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2017, Baidu Inc.
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | * Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | * Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Clay Viewer
2 |
3 | 3D model viewer with high quality rendering based on [ClayGL](https://github.com/pissang/claygl) and [glTF2.0/GLB](https://github.com/KhronosGroup/glTF) export.
4 |
5 |
6 | ## App
7 |
8 | [Download App](https://github.com/pissang/clay-viewer/releases/) on Windows and macOS with FBX/DAE/OBj import and glTF2.0/GLB export. Use it as a common model preview tool!
9 |
10 | ## Editor
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ## Loader
21 |
22 | ```js
23 | var viewer = new ClayViewer(document.getElementById('main'), {
24 | // Full config at
25 | // https://github.com/pissang/clay-viewer/blob/master/src/defaultSceneConfig.js
26 | devicePixelRatio: 1,
27 | // Enable shadow
28 | shadow: true,
29 | shadowQuality: 'high',
30 | // Environment panorama texture url.
31 | environment: 'env.jpg',
32 | mainLight: {
33 | intensity: 2.0
34 | },
35 | ambientCubemapLight: {
36 | exposure: 1,
37 | diffuseIntensity: 0.2,
38 | texture: 'asset/texture/example1.jpg'
39 | },
40 | postEffect: {
41 | // Enable post effect
42 | enable: true,
43 | bloom: {
44 | // Enable bloom
45 | enable: true
46 | },
47 | screenSpaceAmbientOcculusion: {
48 | // Enable screen space ambient occulusion
49 | enable: true
50 | }
51 | }
52 | });
53 |
54 | // Load a glTF model
55 | // Model will be fit in 10x10x10 automatically after load.
56 | // Return an eventful object.
57 | viewer.loadModel('asset/xiniu/xiniu_walk_as.gltf', {
58 | // Shading mode. 'standard'|'lambert'
59 | shader: 'standard'
60 | })
61 | // Model loaded. not include textures.
62 | .on('loadmodel', function (modelStat) {
63 | // Set camera options.
64 | viewer.setCameraControl({
65 | // Alpha is rotation from bottom to up.
66 | alpha: 10,
67 | // Beta is rotation from left to right.
68 | beta: 30,
69 | distance: 20,
70 | // Min distance of zoom.
71 | minDistance: 1,
72 | // Max distance of zoom.
73 | maxDistance: 100,
74 |
75 | // Center of target.
76 | center: [0, 0, 0],
77 |
78 | // If auto rotate.
79 | autoRotate: false,
80 |
81 | // Degree per second.
82 | autoRotateSpeed: 60,
83 |
84 | // Direction of autoRotate. cw or ccw when looking top down.
85 | autoRotateDirection: 'cw',
86 |
87 | // Start auto rotating after still for the given time
88 | autoRotateAfterStill: 30
89 | });
90 |
91 | // Set main light options.
92 | viewer.setMainLight({
93 | // Main light intensity
94 | intensity: 1,
95 | // Main light color string
96 | color: '#fff',
97 | // Alpha is rotation from bottom to up.
98 | alpha: 45,
99 | // Beta is rotation from left to right.
100 | beta: 45
101 | });
102 | // Set ambient light options
103 | viewer.setAmbientLight({
104 | // Ambient light intensity
105 | intensity: 0.8
106 | });
107 |
108 | viewer.start();
109 |
110 | // Load extra animation glTF
111 | viewer.loadAnimation('asset/xiniu/xiniu_stand_as.gltf')
112 | .on('success', function () {
113 | console.log('Changed animation')
114 | });
115 | // Animation pause and start
116 | viewer.pauseAnimation();
117 | viewer.resumeAnimation();
118 |
119 | // Print model stat.
120 | console.log('Model loaded:');
121 | console.log('三角面:', modelStat.triangleCount);
122 | console.log('顶点:', modelStat.vertexCount);
123 | console.log('场景节点:', modelStat.nodeCount);
124 | console.log('Mesh:', modelStat.meshCount);
125 | console.log('材质:', modelStat.materialCount);
126 | console.log('纹理:', modelStat.textureCount);
127 | })
128 | .on('ready', function () {
129 | console.log('All loaded inlcuding textures.');
130 | })
131 | .on('error', function () {
132 | console.log('Model load error');
133 | });
134 |
135 | ```
136 |
137 | [Here](https://github.com/pissang/clay-viewer/blob/master/src/defaultSceneConfig.js) is the full graphic configuration
138 |
139 | ## Converter
140 |
141 | ClayGL provide a python tool for converting FBX to glTF 2.0.
142 |
143 | https://github.com/pissang/claygl/blob/master/tools/fbx2gltf.py
144 |
145 | Needs [python3.3](https://www.python.org/download/releases/3.3.0/) and [FBX SDK 2018.1.1](http://usa.autodesk.com/adsk/servlet/pc/item?siteID=123112&id=26416130)
146 |
147 | ```
148 | usage: fbx2gltf.py [-h] [-e EXCLUDE] [-t TIMERANGE] [-o OUTPUT]
149 | [-f FRAMERATE] [-p POSE] [-q] [-b]
150 | file
151 |
152 | FBX to glTF converter
153 |
154 | positional arguments:
155 | file
156 |
157 | optional arguments:
158 | -h, --help show this help message and exit
159 | -e EXCLUDE, --exclude EXCLUDE
160 | Data excluded. Can be: scene,animation
161 | -t TIMERANGE, --timerange TIMERANGE
162 | Export animation time, in format
163 | 'startSecond,endSecond'
164 | -o OUTPUT, --output OUTPUT
165 | Ouput glTF file path
166 | -f FRAMERATE, --framerate FRAMERATE
167 | Animation frame per second
168 | -p POSE, --pose POSE Start pose time
169 | -q, --quantize Quantize accessors with WEB3D_quantized_attributes
170 | extension
171 | -b, --binary Export glTF-binary
172 | --beautify Beautify json output.
173 | --noflipv If not flip v in texcoord.
174 | ```
175 |
176 |
177 | ## Seperate scene and animation
178 |
179 | Export scene
180 |
181 | ```bash
182 | # exclude animation
183 | fbx2gltf2.py -e animation -p 0 xxx.fbx
184 | ```
185 |
186 | Export animation
187 |
188 | ```bash
189 | # exclude scene, 0 to 20 second, 20 framerate.
190 | fbx2gltf2.py -e scene -t 0,20 -f 20 -o xxx_ani.gltf xxx.fbx
191 | ```
192 |
193 | Load scene and animation asynchronously
194 |
195 | ```js
196 | viewer.loadModel('asset/xiniu/xiniu.gltf')
197 | // Model loaded. not include textures.
198 | .on('loadmodel', function (modelStat) {
199 | viewer.start();
200 | // Load extra animation glTF
201 | viewer.loadAnimation('asset/xiniu/xiniu_ani.gltf');
202 | });
203 | ```
204 |
205 | ## Build
206 |
207 | ```bash
208 | npm install
209 | # Build loader
210 | npm run build
211 | # Build editor
212 | webpack --config editor/webpack.config.js
213 | ```
214 |
--------------------------------------------------------------------------------
/build/glsl2js.js:
--------------------------------------------------------------------------------
1 | var glob = require('glob');
2 | var fs = require('fs');
3 |
4 | glob(__dirname + '/../{src,editor}/**/*.glsl', function (err, files) {
5 | files.forEach(function (filePath) {
6 | var glslCode = fs.readFileSync(filePath, 'utf-8');
7 | // TODO Remove comment
8 | glslCode = glslCode.replace(/\/\/.*\n/g, '');
9 | glslCode = glslCode.replace(/ +/g, ' ');
10 |
11 | // var dir = path.dirname(filePath);
12 | // var baseName = path.basename(filePath, '.essl');
13 | fs.writeFileSync(
14 | filePath + '.js',
15 | 'export default ' + JSON.stringify(glslCode) + ';\n',
16 | 'utf-8'
17 | );
18 | });
19 | });
--------------------------------------------------------------------------------
/editor/asset/texture/Barce_Rooftop_C.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/asset/texture/Barce_Rooftop_C.hdr
--------------------------------------------------------------------------------
/editor/asset/texture/Factory_Catwalk.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/asset/texture/Factory_Catwalk.hdr
--------------------------------------------------------------------------------
/editor/asset/texture/Grand_Canyon_C.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/asset/texture/Grand_Canyon_C.hdr
--------------------------------------------------------------------------------
/editor/asset/texture/Hall.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/asset/texture/Hall.hdr
--------------------------------------------------------------------------------
/editor/asset/texture/Ice_Lake.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/asset/texture/Ice_Lake.hdr
--------------------------------------------------------------------------------
/editor/asset/texture/Old_Industrial_Hall.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/asset/texture/Old_Industrial_Hall.hdr
--------------------------------------------------------------------------------
/editor/asset/texture/pisa.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/asset/texture/pisa.hdr
--------------------------------------------------------------------------------
/editor/css/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 |
2 | @font-face {font-family: "iconfont";
3 | src: url('iconfont.eot?t=1508676521288'); /* IE9*/
4 | src: url('iconfont.eot?t=1508676521288#iefix') format('embedded-opentype'), /* IE6-IE8 */
5 | url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAa4AAsAAAAACYwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZXP0iTY21hcAAAAYAAAAB5AAAByJwO0b1nbHlmAAAB/AAAApcAAAMES2xUGWhlYWQAAASUAAAAMQAAADYPQ6zvaGhlYQAABMgAAAAgAAAAJAfeA4RobXR4AAAE6AAAABgAAAAYF+n//GxvY2EAAAUAAAAADgAAAA4DCAJGbWF4cAAABRAAAAAfAAAAIAEVAF1uYW1lAAAFMAAAAUUAAAJtPlT+fXBvc3QAAAZ4AAAAPwAAAFHq+J8SeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sM4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDxbytzwv4EhhrmB4SJQmBEkBwAzwA1meJzFkcENhDAMBMcQTggogCKoBF0tPBEP2qE1twHrBB5UwEYTZVdWHMVAA9RiEglsxwitSi3nNV3OE7P8QEul8+a9j774cZ5K3+6RqfpZ4ZLuqqKj/fhM9l3rt4a8/28Xc9hu9ETvCzETHwsxJ18K+kf8KNBcQ60cIwAAAHicVVJNaxNRFH33vcxMkkkmme/MRzKZmWSm6UfSzqRToW0ixW+qFsSFLqSFbi101U2h2QgWxLopBZdFkHTnSmn7B0T8C4La/1AXdepLQcTH43DuhXc559yHGIQuv5NTUkEyGkMz6AZaQQjYCfAEXAU37LbxBKguo+qKQEI/dDnfa5NF0D1W0aKkG+gsx5ZAgBrEbpSEbRzCbLeH5yHSqgCGZT6SmrZE3kC+EtZepPfwIaiOb5d6U+ndyb4S1eXsVkGSDEl6lWUZJotxpiTAc13LMbk8m75jSqZ66rSwAwUjNJefFOuWtPayu1Ft6jmAwQBkqy6874umSO+2qcmSwZWL2YpZ9BsKbJ3xFblQDX4iesjlxWUnQ0gLmegmuoUQownYZ33Xa+M+JH2IOTeMHdDUZtCBIAySWEuiZDakfuK5KI40BxRtLtIoo55ZToBwhlxwcnjN/XqYvg4X/AJnZnOt7Q4+7Wy1QvL7m2pt2JrB18X5Fo9JQZfHZaNhABT1ckeslcesTVtVGvIDs91QwR6fVq6L8vru7voqD58rjf2mVQtwxtR+5CtKEbzmFycAXI+bb5uWgSvUE6b7+0T2yB1UQg7dnO+xqqhoNP5kVuwG7v+lD4+PTMcxRwALf5lJbjvmxdkVs0f4j1/N3ycfySaaQF06vxtQ4wrNKFmkK9cF4NoQ9mAuGSU2SoSjXVrTLqnB6IN4LEblwhHPHxXKO8NMZrhDEXgggtATLAISZH4dH//KUHwG8XIULcfkaZnLH+RquYM8V14jw53BkJDhwJmCksVqjCCQ+5Mn5wxzfnJ8zqRLQTx6RsVRrXuEpVp5hHSxBzpoCgdscIaXbPggiulDe2marI4ZRroiTUrpimHQCP8A+EqGwwB4nGNgZGBgAOKjT422xvPbfGXgZmEAgWtCW1bC6P9//uuzMDMXArkcDEwgUQBgPQx6AAAAeJxjYGRgYG7438AQw8Lw/8//vyzMDEARFMAGAKB8BmkEAAAAA+kAAAQA//wEAAAABAAAAAQAAAAAAAAAAHYA4AEQAWYBggAAeJxjYGRgYGBjCGRgZQABJiDmAkIGhv9gPgMAEUgBcwB4nGWPTU7DMBCFX/oHpBKqqGCH5AViASj9EatuWFRq911036ZOmyqJI8et1ANwHo7ACTgC3IA78EgnmzaWx9+8eWNPANzgBx6O3y33kT1cMjtyDRe4F65TfxBukF+Em2jjVbhF/U3YxzOmwm10YXmD17hi9oR3YQ8dfAjXcI1P4Tr1L+EG+Vu4iTv8CrfQ8erCPuZeV7iNRy/2x1YvnF6p5UHFockikzm/gple75KFrdLqnGtbxCZTg6BfSVOdaVvdU+zXQ+ciFVmTqgmrOkmMyq3Z6tAFG+fyUa8XiR6EJuVYY/62xgKOcQWFJQ6MMUIYZIjK6Og7VWb0r7FDwl57Vj3N53RbFNT/c4UBAvTPXFO6stJ5Ok+BPV8bUnV0K27LnpQ0kV7NSRKyQl7WtlRC6gE2ZVeOEXpc0Yk/KGdI/wAJWm7IAAAAeJxjYGKAAC4G7ICNkYmRmZGFkZWRjZGdgbGCIyW/PC8nPzGFtSCxtDiVtSi1OLWEA0iW5qbq5jMwAADVgQuNAA==') format('woff'),
6 | url('iconfont.ttf?t=1508676521288') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
7 | url('iconfont.svg?t=1508676521288#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family:"iconfont" !important;
12 | font-size:16px;
13 | font-style:normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-download:before { content: "\e66e"; }
19 |
20 | .icon-pause:before { content: "\e618"; }
21 |
22 | .icon-reset:before { content: "\e60b"; }
23 |
24 | .icon-resume:before { content: "\e6a5"; }
25 |
26 |
--------------------------------------------------------------------------------
/editor/css/iconfont/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/css/iconfont/iconfont.eot
--------------------------------------------------------------------------------
/editor/css/iconfont/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
46 |
--------------------------------------------------------------------------------
/editor/css/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/css/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/editor/css/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/css/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/editor/css/main.css:
--------------------------------------------------------------------------------
1 |
2 | body {
3 | margin: 0;
4 | background: #000;
5 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
6 | overflow: hidden;
7 | user-select: none;
8 | }
9 | #main {
10 | position: absolute;
11 | left: 0px;
12 | right: 250px;
13 | top: 0;
14 | bottom: 0;
15 | }
16 |
17 | #viewport {
18 | position: absolute;
19 | top: 0;
20 | left: 0;
21 | width: 100%;
22 | height: 100%;
23 | }
24 |
25 | #loading {
26 | top: 0;
27 | left: 0;
28 | right: 0;
29 | bottom: 0;
30 | position: absolute;
31 | text-align: center;
32 | z-index: 1000;
33 | background-color: rgba(0,0,0,0.5);
34 | }
35 | #loading-text {
36 | position: absolute;
37 | top: 50%;
38 | margin-top: 50px;
39 | left: 0;
40 | right: 0;
41 | text-transform: uppercase;
42 | font-size: 24px;
43 | color: #aaa;
44 | text-align: center;
45 | }
46 |
47 | #toolbar {
48 | position: absolute;
49 | right: 280px;
50 | top: 10px;
51 | border-radius: 3px;
52 | background-color: rgba(255,255,255,0.5);
53 | display: none;
54 | z-index: 10;
55 | /* box-shadow: 0px 0px 10px rgba(122,122,122,0.4); */
56 | }
57 |
58 | #toolbar .iconfont {
59 | display: inline-block;
60 | padding: 10px;
61 | text-align: center;
62 | cursor: pointer;
63 | border-radius: 3px;
64 | }
65 | #toolbar .iconfont:hover {
66 | background-color: rgba(255,255,255,0.5);
67 | }
68 |
69 |
70 | #tip {
71 | position: absolute;
72 | width: 500px;
73 | left: 50%;
74 | top: 300px;
75 | margin-left: -250px;
76 | text-align: center;
77 | font-size: 20px;
78 | padding: 30px 10px;
79 |
80 | border-radius: 3px;
81 | background-color: rgba(255,255,255,0.5);
82 | z-index: 10;
83 |
84 | display: none;
85 | }
86 |
87 | #tip .hint {
88 | font-size: 14px;
89 | margin-top: 20px;
90 | }
91 |
92 | #tip ul {
93 | margin: 0;
94 | padding: 0;
95 | }
96 | #tip li {
97 | list-style: none;
98 | margin: 0;
99 | padding: 0;
100 | }
101 |
102 | #credits {
103 | position: absolute;
104 | width: 400px;
105 | bottom: 10px;
106 | left: 50%;
107 | margin-left: -200px;
108 | text-align: center;
109 | z-index: 1000;
110 | font-size: 12px;
111 | color: #555;
112 | }
113 |
114 | #credits .source {
115 | color: #000;
116 | font-size: 14px;
117 | margin-right: 14px;
118 | }
119 | #credits a {
120 | color: #111;
121 | }
122 |
123 | #timeline {
124 | position: absolute;
125 | width: 600px;
126 | right: 50px;
127 | bottom: 50px;
128 | height: 115px;
129 | display: none;
130 | border-radius: 5px;
131 | background-color: rgba(0,0,0,0.7);
132 | }
133 |
134 | #timeline-range-wrap {
135 | position: relative;
136 | height: 45px;
137 | margin-top: 10px;
138 | }
139 |
140 | #timeline-progress-wrap {
141 | position: relative;
142 | height: 50px;
143 | }
144 |
145 | #timeline-range {
146 | left: 45px;
147 | right: 10px;
148 | position: absolute;
149 | }
150 |
151 | #timeline-progress {
152 | left: 45px;
153 | right: 10px;
154 | position: absolute;
155 | }
156 |
157 | #timeline-pause-resume {
158 | position: absolute;
159 | left: 10px;
160 | color: #fff;
161 | font-size: 26px;
162 | bottom: 5px;
163 | cursor: pointer;
164 | }
165 |
166 | #timeline-progress .irs-bar {
167 | opacity: 0.3;
168 | }
169 | #timeline-progress .irs-min,
170 | #timeline-progress .irs-max {
171 | display: none;
172 | }
173 | #timeline-progress .irs-single {
174 | top: 42px;
175 | }
176 | #timeline-progress .irs-single::after {
177 | top: -6px;
178 | bottom: auto;
179 | border-top-color: transparent;
180 | border-bottom-color: #ed5565;
181 | }
182 |
183 |
184 | #controlKit .panel .group-list .group .sub-group-list .sub-group ul li.drag-hover {
185 | background: #585656;
186 | }
187 | #controlKit .panel .group-list .group .sub-group-list .sub-group ul li:after {
188 | display: block;
189 | visibility: hidden;
190 | content: '';
191 | height: 0;
192 | clear: both;
193 | }
194 | #controlKit .panel .group-list .group .sub-group-list .sub-group {
195 | padding: 5px;
196 | }
197 | #controlKit .panel .group-list .group .sub-group-list .sub-group .wrap .label {
198 | width: 45%;
199 | }
200 | #controlKit .panel .group-list .group .sub-group-list .sub-group .wrap .wrap,
201 | #controlKit .panel .svg-wrap, #controlKit .panel .canvas-wrap,
202 | #controlKit .panel .wrap-slider {
203 | width: 55%;
204 | }
205 |
206 | .texture-wrap {
207 | width: 55%;
208 | float: right;
209 | /* cursor: pointer; */
210 | overflow-x: hidden;
211 | padding-top: 3px;
212 | }
213 | .texture-wrap img {
214 | height: 25px;
215 | max-width: 25px;
216 | display: inline-block;
217 | vertical-align: top;
218 | }
219 | .texture-wrap .texture-upload {
220 | display: inline-block;
221 | width: 75px!important;
222 | line-height: 25px;
223 | text-align: left;
224 | text-transform: none!important;
225 | font-weight: normal!important;
226 | padding-left: 4px!important;
227 | font-size: 11px;
228 | text-overflow: ellipsis;
229 | overflow: hidden;
230 | direction: rtl;
231 | }
232 | .texture-wrap .texture-delete {
233 | display: inline-block;
234 | width: 15px!important;
235 | vertical-align: top;
236 | text-align: center;
237 | line-height: 25px;
238 | text-transform: none!important;
239 | }
240 | .texture-wrap .texture-delete::after {
241 | content: 'x';
242 | }
243 |
244 |
245 |
246 | .pace {
247 | -webkit-pointer-events: none;
248 | pointer-events: none;
249 | -webkit-user-select: none;
250 | -moz-user-select: none;
251 | user-select: none;
252 | }
253 |
254 | .pace-inactive {
255 | display: none;
256 | }
257 |
258 | .pace .pace-progress {
259 | background: #29d;
260 | position: fixed;
261 | z-index: 2000;
262 | top: 0;
263 | right: 100%;
264 | width: 100%;
265 | height: 2px;
266 | }
267 |
268 | .pace .pace-progress-inner {
269 | display: block;
270 | position: absolute;
271 | right: 0px;
272 | width: 100px;
273 | height: 100%;
274 | box-shadow: 0 0 10px #29d, 0 0 5px #29d;
275 | opacity: 1.0;
276 | transform: rotate(3deg) translate(0px, -4px);
277 | }
278 |
279 | .pace .pace-activity,
280 | #background-progress .spinner {
281 | z-index: 2000;
282 | width: 14px;
283 | height: 14px;
284 | border: solid 2px transparent;
285 | border-top-color: #29d;
286 | border-left-color: #29d;
287 | border-radius: 10px;
288 | animation: pace-spinner 400ms linear infinite;
289 | }
290 |
291 | .pace .pace-activity {
292 | display: block;
293 | position: fixed;
294 | top: 15px;
295 | right: 15px;
296 | }
297 |
298 | #background-progress {
299 | display: none;
300 | position: absolute;
301 | right: 15px;
302 | bottom: 15px;
303 | color: #029d;
304 | font-size: 12px;
305 | }
306 | #background-progress div {
307 | display: inline-block;
308 | margin-right: 3px;
309 | }
310 |
311 | @-webkit-keyframes pace-spinner {
312 | 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); }
313 | 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); }
314 | }
315 | @-moz-keyframes pace-spinner {
316 | 0% { -moz-transform: rotate(0deg); transform: rotate(0deg); }
317 | 100% { -moz-transform: rotate(360deg); transform: rotate(360deg); }
318 | }
319 | @-o-keyframes pace-spinner {
320 | 0% { -o-transform: rotate(0deg); transform: rotate(0deg); }
321 | 100% { -o-transform: rotate(360deg); transform: rotate(360deg); }
322 | }
323 | @-ms-keyframes pace-spinner {
324 | 0% { -ms-transform: rotate(0deg); transform: rotate(0deg); }
325 | 100% { -ms-transform: rotate(360deg); transform: rotate(360deg); }
326 | }
327 | @keyframes pace-spinner {
328 | 0% { transform: rotate(0deg); transform: rotate(0deg); }
329 | 100% { transform: rotate(360deg); transform: rotate(360deg); }
330 | }
331 |
332 |
333 | .sk-folding-cube {
334 | top: 50%;
335 | left: 50%;
336 | margin-left: -20px;
337 | margin-top: -20px;
338 | width: 40px;
339 | height: 40px;
340 | position: absolute;
341 | -webkit-transform: rotateZ(45deg);
342 | transform: rotateZ(45deg); }
343 | .sk-folding-cube .sk-cube {
344 | float: left;
345 | width: 50%;
346 | height: 50%;
347 | position: relative;
348 | -webkit-transform: scale(1.1);
349 | -ms-transform: scale(1.1);
350 | transform: scale(1.1); }
351 | .sk-folding-cube .sk-cube:before {
352 | content: '';
353 | position: absolute;
354 | top: 0;
355 | left: 0;
356 | width: 100%;
357 | height: 100%;
358 | background-color: #fff;
359 | -webkit-animation: sk-foldCubeAngle 2.4s infinite linear both;
360 | animation: sk-foldCubeAngle 2.4s infinite linear both;
361 | -webkit-transform-origin: 100% 100%;
362 | -ms-transform-origin: 100% 100%;
363 | transform-origin: 100% 100%; }
364 | .sk-folding-cube .sk-cube2 {
365 | -webkit-transform: scale(1.1) rotateZ(90deg);
366 | transform: scale(1.1) rotateZ(90deg); }
367 | .sk-folding-cube .sk-cube3 {
368 | -webkit-transform: scale(1.1) rotateZ(180deg);
369 | transform: scale(1.1) rotateZ(180deg); }
370 | .sk-folding-cube .sk-cube4 {
371 | -webkit-transform: scale(1.1) rotateZ(270deg);
372 | transform: scale(1.1) rotateZ(270deg); }
373 | .sk-folding-cube .sk-cube2:before {
374 | -webkit-animation-delay: 0.3s;
375 | animation-delay: 0.3s; }
376 | .sk-folding-cube .sk-cube3:before {
377 | -webkit-animation-delay: 0.6s;
378 | animation-delay: 0.6s; }
379 | .sk-folding-cube .sk-cube4:before {
380 | -webkit-animation-delay: 0.9s;
381 | animation-delay: 0.9s; }
382 | @-webkit-keyframes sk-foldCubeAngle {
383 | 0%, 10% {
384 | -webkit-transform: perspective(140px) rotateX(-180deg);
385 | transform: perspective(140px) rotateX(-180deg);
386 | opacity: 0; }
387 | 25%, 75% {
388 | -webkit-transform: perspective(140px) rotateX(0deg);
389 | transform: perspective(140px) rotateX(0deg);
390 | opacity: 1; }
391 | 90%, 100% {
392 | -webkit-transform: perspective(140px) rotateY(180deg);
393 | transform: perspective(140px) rotateY(180deg);
394 | opacity: 0; } }
395 | @keyframes sk-foldCubeAngle {
396 | 0%, 10% {
397 | -webkit-transform: perspective(140px) rotateX(-180deg);
398 | transform: perspective(140px) rotateX(-180deg);
399 | opacity: 0; }
400 | 25%, 75% {
401 | -webkit-transform: perspective(140px) rotateX(0deg);
402 | transform: perspective(140px) rotateX(0deg);
403 | opacity: 1; }
404 | 90%, 100% {
405 | -webkit-transform: perspective(140px) rotateY(180deg);
406 | transform: perspective(140px) rotateY(180deg);
407 | opacity: 0; } }
408 |
409 |
--------------------------------------------------------------------------------
/editor/electron.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron');
2 |
3 | const app = electron.app;
4 | const BrowserWindow = electron.BrowserWindow;
5 |
6 | const path = require('path');
7 | const url = require('url');
8 |
9 | let mainWindow;
10 |
11 | function createWindow() {
12 | mainWindow = new BrowserWindow({
13 | width: 1200,
14 | height: 700
15 | });
16 |
17 | mainWindow.loadURL(url.format({
18 | pathname: path.join(__dirname, 'index-electron.html'),
19 | protocol: 'file:',
20 | slashes: true
21 | }));
22 |
23 | mainWindow.on('closed', () => {
24 | mainWindow = null;
25 | });
26 | }
27 |
28 | app.on('ready', createWindow);
29 | // Quit when all windows are closed.
30 | app.on('window-all-closed', function () {
31 | // On OS X it is common for applications and their menu bar
32 | // to stay active until the user quits explicitly with Cmd + Q
33 | // if (process.platform !== 'darwin') {
34 | app.quit();
35 | // }
36 | })
37 |
38 | app.on('activate', function () {
39 | // On OS X it's common to re-create a window in the app when the
40 | // dock icon is clicked and there are no other windows open.
41 | if (mainWindow === null) {
42 | createWindow();
43 | }
44 | });
--------------------------------------------------------------------------------
/editor/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/icon.icns
--------------------------------------------------------------------------------
/editor/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/icon.ico
--------------------------------------------------------------------------------
/editor/img/chessboard.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/img/chessboard.jpg
--------------------------------------------------------------------------------
/editor/img/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/editor/img/loading.gif
--------------------------------------------------------------------------------
/editor/index-electron.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Clay Viewer
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Drag model and texture files or folder on the viewport.
18 |
19 | Supported format: FBX, DAE, OBJ, glTF2.0
20 | Try glTF2.0 Models from:
21 |
26 |
27 |
28 |
35 |
48 |
52 |
53 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
75 |
76 |
79 |
80 |
--------------------------------------------------------------------------------
/editor/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Clay Viewer
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Drag glTF2.0 and texture files or folder on the viewport.
18 |
19 | Get glTF2.0 models from
20 |
28 |
29 |
30 |
33 |
34 |
41 |
54 |
58 |
59 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/editor/lib/FileSaver.js:
--------------------------------------------------------------------------------
1 | /* FileSaver.js
2 | * A saveAs() FileSaver implementation.
3 | * 1.3.2
4 | * 2016-06-16 18:25:19
5 | *
6 | * By Eli Grey, http://eligrey.com
7 | * License: MIT
8 | * See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
9 | */
10 |
11 | /*global self */
12 | /*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
13 |
14 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
15 |
16 | var saveAs = saveAs || (function(view) {
17 | "use strict";
18 | // IE <10 is explicitly unsupported
19 | if (typeof view === "undefined" || typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
20 | return;
21 | }
22 | var
23 | doc = view.document
24 | // only get URL when necessary in case Blob.js hasn't overridden it yet
25 | , get_URL = function() {
26 | return view.URL || view.webkitURL || view;
27 | }
28 | , save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
29 | , can_use_save_link = "download" in save_link
30 | , click = function(node) {
31 | var event = new MouseEvent("click");
32 | node.dispatchEvent(event);
33 | }
34 | , is_safari = /constructor/i.test(view.HTMLElement) || view.safari
35 | , is_chrome_ios =/CriOS\/[\d]+/.test(navigator.userAgent)
36 | , throw_outside = function(ex) {
37 | (view.setImmediate || view.setTimeout)(function() {
38 | throw ex;
39 | }, 0);
40 | }
41 | , force_saveable_type = "application/octet-stream"
42 | // the Blob API is fundamentally broken as there is no "downloadfinished" event to subscribe to
43 | , arbitrary_revoke_timeout = 1000 * 40 // in ms
44 | , revoke = function(file) {
45 | var revoker = function() {
46 | if (typeof file === "string") { // file is an object URL
47 | get_URL().revokeObjectURL(file);
48 | } else { // file is a File
49 | file.remove();
50 | }
51 | };
52 | setTimeout(revoker, arbitrary_revoke_timeout);
53 | }
54 | , dispatch = function(filesaver, event_types, event) {
55 | event_types = [].concat(event_types);
56 | var i = event_types.length;
57 | while (i--) {
58 | var listener = filesaver["on" + event_types[i]];
59 | if (typeof listener === "function") {
60 | try {
61 | listener.call(filesaver, event || filesaver);
62 | } catch (ex) {
63 | throw_outside(ex);
64 | }
65 | }
66 | }
67 | }
68 | , auto_bom = function(blob) {
69 | // prepend BOM for UTF-8 XML and text/* types (including HTML)
70 | // note: your browser will automatically convert UTF-16 U+FEFF to EF BB BF
71 | if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
72 | return new Blob([String.fromCharCode(0xFEFF), blob], {type: blob.type});
73 | }
74 | return blob;
75 | }
76 | , FileSaver = function(blob, name, no_auto_bom) {
77 | if (!no_auto_bom) {
78 | blob = auto_bom(blob);
79 | }
80 | // First try a.download, then web filesystem, then object URLs
81 | var
82 | filesaver = this
83 | , type = blob.type
84 | , force = type === force_saveable_type
85 | , object_url
86 | , dispatch_all = function() {
87 | dispatch(filesaver, "writestart progress write writeend".split(" "));
88 | }
89 | // on any filesys errors revert to saving with object URLs
90 | , fs_error = function() {
91 | if ((is_chrome_ios || (force && is_safari)) && view.FileReader) {
92 | // Safari doesn't allow downloading of blob urls
93 | var reader = new FileReader();
94 | reader.onloadend = function() {
95 | var url = is_chrome_ios ? reader.result : reader.result.replace(/^data:[^;]*;/, 'data:attachment/file;');
96 | var popup = view.open(url, '_blank');
97 | if(!popup) view.location.href = url;
98 | url=undefined; // release reference before dispatching
99 | filesaver.readyState = filesaver.DONE;
100 | dispatch_all();
101 | };
102 | reader.readAsDataURL(blob);
103 | filesaver.readyState = filesaver.INIT;
104 | return;
105 | }
106 | // don't create more object URLs than needed
107 | if (!object_url) {
108 | object_url = get_URL().createObjectURL(blob);
109 | }
110 | if (force) {
111 | view.location.href = object_url;
112 | } else {
113 | var opened = view.open(object_url, "_blank");
114 | if (!opened) {
115 | // Apple does not allow window.open, see https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html
116 | view.location.href = object_url;
117 | }
118 | }
119 | filesaver.readyState = filesaver.DONE;
120 | dispatch_all();
121 | revoke(object_url);
122 | }
123 | ;
124 | filesaver.readyState = filesaver.INIT;
125 |
126 | if (can_use_save_link) {
127 | object_url = get_URL().createObjectURL(blob);
128 | setTimeout(function() {
129 | save_link.href = object_url;
130 | save_link.download = name;
131 | click(save_link);
132 | dispatch_all();
133 | revoke(object_url);
134 | filesaver.readyState = filesaver.DONE;
135 | });
136 | return;
137 | }
138 |
139 | fs_error();
140 | }
141 | , FS_proto = FileSaver.prototype
142 | , saveAs = function(blob, name, no_auto_bom) {
143 | return new FileSaver(blob, name || blob.name || "download", no_auto_bom);
144 | }
145 | ;
146 | // IE 10+ (native saveAs)
147 | if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
148 | return function(blob, name, no_auto_bom) {
149 | name = name || blob.name || "download";
150 |
151 | if (!no_auto_bom) {
152 | blob = auto_bom(blob);
153 | }
154 | return navigator.msSaveOrOpenBlob(blob, name);
155 | };
156 | }
157 |
158 | FS_proto.abort = function(){};
159 | FS_proto.readyState = FS_proto.INIT = 0;
160 | FS_proto.WRITING = 1;
161 | FS_proto.DONE = 2;
162 |
163 | FS_proto.error =
164 | FS_proto.onwritestart =
165 | FS_proto.onprogress =
166 | FS_proto.onwrite =
167 | FS_proto.onabort =
168 | FS_proto.onerror =
169 | FS_proto.onwriteend =
170 | null;
171 |
172 | return saveAs;
173 | }(
174 | typeof self !== "undefined" && self
175 | || typeof window !== "undefined" && window
176 | || this.content
177 | ));
178 | // `self` is undefined in Firefox for Android content script context
179 | // while `this` is nsIContentFrameMessageManager
180 | // with an attribute `content` that corresponds to the window
181 |
182 | if (typeof module !== "undefined" && module.exports) {
183 | module.exports.saveAs = saveAs;
184 | } else if ((typeof define !== "undefined" && define !== null) && (define.amd !== null)) {
185 | define("FileSaver.js", function() {
186 | return saveAs;
187 | });
188 | }
--------------------------------------------------------------------------------
/editor/lib/filer.min.js:
--------------------------------------------------------------------------------
1 | /*! (c) 2016 Copyright (c) 2016 Eric Bidelman. All rights reserved.
2 |
3 | * @version v0.4.5 (Apache) */
4 | var $jscomp={scope:{}};$jscomp.defineProperty="function"==typeof Object.defineProperties?Object.defineProperty:function(a,c,b){if(b.get||b.set)throw new TypeError("ES3 does not support getters and setters.");a!=Array.prototype&&a!=Object.prototype&&(a[c]=b.value)};$jscomp.getGlobal=function(a){return"undefined"!=typeof window&&window===a?a:"undefined"!=typeof global?global:a};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_";
5 | $jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(a){return $jscomp.SYMBOL_PREFIX+(a||"")+$jscomp.symbolCounter_++};
6 | $jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var a=$jscomp.global.Symbol.iterator;a||(a=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[a]&&$jscomp.defineProperty(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(a){var c=0;return $jscomp.iteratorPrototype(function(){return c 0.0) {
18 | currProj = clipNear(currProj, nextProj);
19 | }
20 | else if (prevProj.w > 0.0) {
21 | currProj = clipNear(currProj, prevProj);
22 | }
23 | }
24 |
25 | vec2 prevScreen = (prevProj.xy / abs(prevProj.w) + 1.0) * 0.5 * viewport.zw;
26 | vec2 currScreen = (currProj.xy / abs(currProj.w) + 1.0) * 0.5 * viewport.zw;
27 | vec2 nextScreen = (nextProj.xy / abs(nextProj.w) + 1.0) * 0.5 * viewport.zw;
28 |
29 | vec2 dir;
30 | float len = offset;
31 | // Start point
32 | if (position == positionPrev) {
33 | dir = normalize(nextScreen - currScreen);
34 | }
35 | // End point
36 | else if (position == positionNext) {
37 | dir = normalize(currScreen - prevScreen);
38 | }
39 | else {
40 | vec2 dirA = normalize(currScreen - prevScreen);
41 | vec2 dirB = normalize(nextScreen - currScreen);
42 |
43 | vec2 tanget = normalize(dirA + dirB);
44 |
45 | // TODO, simple miterLimit
46 | float miter = 1.0 / max(dot(tanget, dirA), 0.5);
47 | len *= miter;
48 | dir = tanget;
49 | }
50 |
51 | dir = vec2(-dir.y, dir.x) * len;
52 | currScreen += dir;
53 |
54 | currProj.xy = (currScreen / viewport.zw - 0.5) * 2.0 * abs(currProj.w);
55 | @end
56 |
57 |
58 | @export ecgl.meshLines3D.vertex
59 |
60 | // https://mattdesl.svbtle.com/drawing-lines-is-hard
61 | attribute vec3 position: POSITION;
62 | attribute vec3 positionPrev;
63 | attribute vec3 positionNext;
64 | attribute float offset;
65 | attribute vec4 a_Color : COLOR;
66 |
67 | #ifdef VERTEX_ANIMATION
68 | attribute vec3 prevPosition;
69 | attribute vec3 prevPositionPrev;
70 | attribute vec3 prevPositionNext;
71 | uniform float percent : 1.0;
72 | #endif
73 |
74 | uniform mat4 worldViewProjection : WORLDVIEWPROJECTION;
75 | uniform vec4 viewport : VIEWPORT;
76 | uniform float near : NEAR;
77 |
78 | varying vec4 v_Color;
79 |
80 | @import ecgl.lines3D.clipNear
81 |
82 | void main()
83 | {
84 | @import ecgl.lines3D.expandLine
85 |
86 | gl_Position = currProj;
87 |
88 | v_Color = a_Color;
89 | }
90 | @end
91 |
92 |
93 | @export ecgl.meshLines3D.fragment
94 |
95 | uniform vec4 color : [1.0, 1.0, 1.0, 1.0];
96 |
97 | varying vec4 v_Color;
98 |
99 | @import clay.util.srgb
100 |
101 | void main()
102 | {
103 | #ifdef SRGB_DECODE
104 | gl_FragColor = sRGBToLinear(color * v_Color);
105 | #else
106 | gl_FragColor = color * v_Color;
107 | #endif
108 | }
109 |
110 | @end
--------------------------------------------------------------------------------
/editor/src/debug/lines3d.glsl.js:
--------------------------------------------------------------------------------
1 | export default "@export ecgl.lines3D.clipNear\n\nvec4 clipNear(vec4 p1, vec4 p2) {\n float n = (p1.w - near) / (p1.w - p2.w);\n return vec4(mix(p1.xy, p2.xy, n), -near, near);\n}\n\n@end\n\n@export ecgl.lines3D.expandLine\n vec4 prevProj = worldViewProjection * vec4(positionPrev, 1.0);\n vec4 currProj = worldViewProjection * vec4(position, 1.0);\n vec4 nextProj = worldViewProjection * vec4(positionNext, 1.0);\n\n if (currProj.w < 0.0) {\n if (nextProj.w > 0.0) {\n currProj = clipNear(currProj, nextProj);\n }\n else if (prevProj.w > 0.0) {\n currProj = clipNear(currProj, prevProj);\n }\n }\n\n vec2 prevScreen = (prevProj.xy / abs(prevProj.w) + 1.0) * 0.5 * viewport.zw;\n vec2 currScreen = (currProj.xy / abs(currProj.w) + 1.0) * 0.5 * viewport.zw;\n vec2 nextScreen = (nextProj.xy / abs(nextProj.w) + 1.0) * 0.5 * viewport.zw;\n\n vec2 dir;\n float len = offset;\n if (position == positionPrev) {\n dir = normalize(nextScreen - currScreen);\n }\n else if (position == positionNext) {\n dir = normalize(currScreen - prevScreen);\n }\n else {\n vec2 dirA = normalize(currScreen - prevScreen);\n vec2 dirB = normalize(nextScreen - currScreen);\n\n vec2 tanget = normalize(dirA + dirB);\n\n float miter = 1.0 / max(dot(tanget, dirA), 0.5);\n len *= miter;\n dir = tanget;\n }\n\n dir = vec2(-dir.y, dir.x) * len;\n currScreen += dir;\n\n currProj.xy = (currScreen / viewport.zw - 0.5) * 2.0 * abs(currProj.w);\n@end\n\n\n@export ecgl.meshLines3D.vertex\n\nattribute vec3 position: POSITION;\nattribute vec3 positionPrev;\nattribute vec3 positionNext;\nattribute float offset;\nattribute vec4 a_Color : COLOR;\n\n#ifdef VERTEX_ANIMATION\nattribute vec3 prevPosition;\nattribute vec3 prevPositionPrev;\nattribute vec3 prevPositionNext;\nuniform float percent : 1.0;\n#endif\n\nuniform mat4 worldViewProjection : WORLDVIEWPROJECTION;\nuniform vec4 viewport : VIEWPORT;\nuniform float near : NEAR;\n\nvarying vec4 v_Color;\n\n@import ecgl.lines3D.clipNear\n\nvoid main()\n{\n @import ecgl.lines3D.expandLine\n\n gl_Position = currProj;\n\n v_Color = a_Color;\n}\n@end\n\n\n@export ecgl.meshLines3D.fragment\n\nuniform vec4 color : [1.0, 1.0, 1.0, 1.0];\n\nvarying vec4 v_Color;\n\n@import clay.util.srgb\n\nvoid main()\n{\n#ifdef SRGB_DECODE\n gl_FragColor = sRGBToLinear(color * v_Color);\n#else\n gl_FragColor = color * v_Color;\n#endif\n}\n\n@end";
2 |
--------------------------------------------------------------------------------
/editor/src/debug/renderOutline.js:
--------------------------------------------------------------------------------
1 | import Texture2D from 'claygl/src/Texture2D';
2 | import Texture from 'claygl/src/Texture';
3 | import Pass from 'claygl/src/compositor/Pass';
4 | import Shader from 'claygl/src/Shader';
5 | import FrameBuffer from 'claygl/src/FrameBuffer';
6 | import Material from 'claygl/src/Material';
7 |
8 | import edgeShader from './edge.glsl.js';
9 | Shader['import'](edgeShader);
10 |
11 | var texture = new Texture2D();
12 | var framebuffer = new FrameBuffer();
13 | framebuffer.attach(texture);
14 |
15 | var edgePass = new Pass({
16 | fragment: Shader.source('qmv.editor.edge')
17 | });
18 |
19 | var outlineBasicMaterial = new Material({
20 | shader: new Shader(Shader.source('clay.basic.vertex'), Shader.source('clay.basic.fragment'))
21 | });
22 |
23 | export default function (viewer, meshes, camera) {
24 | var renderer = viewer.getRenderer();
25 | texture.width = renderer.getWidth();
26 | texture.height = renderer.getHeight();
27 |
28 | framebuffer.bind(renderer);
29 | renderer.gl.clearColor(0, 0, 0, 0);
30 | renderer.gl.clear(renderer.gl.COLOR_BUFFER_BIT | renderer.gl.DEPTH_BUFFER_BIT);
31 | camera.update();
32 | renderer.renderPass(meshes, camera, {
33 | getMaterial: function () {
34 | return outlineBasicMaterial;
35 | }
36 | });
37 | framebuffer.unbind(renderer);
38 |
39 | edgePass.setUniform('edgeWidth', 1.5);
40 | edgePass.setUniform('edgeColor', [1, 1, 0, 1]);
41 | edgePass.setUniform('texture', texture);
42 | edgePass.setUniform('textureSize', [texture.width, texture.height]);
43 | edgePass.render(renderer);
44 | }
--------------------------------------------------------------------------------
/editor/src/electron/convert.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | // const shell = require('shelljs');
3 | const path = require('path');
4 | const electron = require('electron');
5 | const child_process = require('child_process');
6 | const os = require('os');
7 |
8 | // Modules for model converting
9 | module.exports = function (files) {
10 | let appDataPath = electron.remote.app.getPath('documents');
11 | let QMVPath = `${appDataPath}/Clay Viewer`;
12 | let modelTmpPath = `${QMVPath}/tmp/`;
13 | if (!fs.existsSync(QMVPath)) {
14 | fs.mkdirSync(QMVPath);
15 | }
16 | if (!fs.existsSync(modelTmpPath)) {
17 | fs.mkdirSync(modelTmpPath);
18 | }
19 | return Promise.all(files.map(function (file, idx) {
20 | return new Promise(function (resolve, reject) {
21 | FileAPI.readAsArrayBuffer(file, function (evt) {
22 | if (evt.type == 'load') {
23 | fs.writeFile(modelTmpPath + file.name, new Buffer(evt.result), function () {
24 | resolve(file.name);
25 | });
26 | }
27 | });
28 | });
29 | })).then(function (nameList) {
30 | var firstModelFileName = nameList.find(function (name) {
31 | return path.extname(name) !== '.mtl';
32 | });
33 | return new Promise(function (resolve, reject) {
34 | let glTFFileName = path.basename(firstModelFileName, path.extname(firstModelFileName));
35 | let glTFPath = `${modelTmpPath}/${glTFFileName}.gltf`;
36 | let glTFBinPath = `${modelTmpPath}/${glTFFileName}.bin`;
37 | let fullPath = `${modelTmpPath}${firstModelFileName}`;
38 |
39 | let exePath = path.join(electron.remote.app.getAppPath(), 'electron/convert/dist/fbx2gltf/fbx2gltf');
40 | if (os.platform() === 'win32') {
41 | exePath += '.exe';
42 | }
43 | child_process.execFile(exePath, [fullPath], function (error, stdout, stderr) {
44 | if (fs.existsSync(glTFPath)) {
45 | resolve({
46 | name: glTFFileName,
47 | json: fs.readFileSync(glTFPath, 'utf-8'),
48 | buffer: fs.readFileSync(glTFBinPath)
49 | });
50 | }
51 | else {
52 | reject(error || stderr || stdout);
53 | }
54 | });
55 | });
56 | });
57 | }
--------------------------------------------------------------------------------
/editor/src/electron/download.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const electron = require('electron');
3 |
4 | export default function (file, fileName) {
5 | electron.remote.dialog.showSaveDialog({
6 | title: 'Download',
7 | defaultPath: fileName
8 | }, function (filePath) {
9 | FileAPI.readAsArrayBuffer(file, function (evt) {
10 | if (evt.type == 'load') {
11 | fs.writeFile(filePath, new Buffer(evt.result));
12 | }
13 | });
14 | });
15 | }
--------------------------------------------------------------------------------
/editor/src/electron/openURL.js:
--------------------------------------------------------------------------------
1 | var shell = require('electron').shell;
2 |
3 | export default function (url) {
4 | shell.openExternal(url);
5 | }
--------------------------------------------------------------------------------
/editor/src/env.js:
--------------------------------------------------------------------------------
1 | export default {
2 | ENV_TEXTURE_ROOT: './asset/texture/',
3 |
4 | SUPPORTED_MODEL_FILES: ['fbx', 'obj', 'mtl', 'dae', 'dxf'],
5 |
6 | AUTO_SAVE: true
7 | };
--------------------------------------------------------------------------------
/editor/src/getDefaultMaterialConfig.js:
--------------------------------------------------------------------------------
1 |
2 | export default function () {
3 | return {
4 |
5 | name: '',
6 |
7 | type: 'pbrMetallicRoughness',
8 |
9 | color: '#fff',
10 |
11 | transparent: false,
12 | alpha: 1,
13 | alphaCutoff: 0,
14 |
15 | diffuseMap: '',
16 | normalMap: '',
17 | parallaxOcclusionScale: 0.01,
18 | parallaxOcclusionMap: '',
19 |
20 | emission: '#fff',
21 | emissionIntensity: 0,
22 | emissiveMap: '',
23 |
24 | uvRepeat: [1, 1],
25 |
26 | // Metallic and roughness
27 | metalness: 0,
28 | roughness: 0.5,
29 | metalnessMap: '',
30 | roughnessMap: '',
31 |
32 | // Specular and glossiness
33 | glossiness: 0.5,
34 | specularColor: '#111',
35 | glossinessMap: '',
36 | specularMap: '',
37 |
38 | $alphaRange: [0, 1],
39 | $alphaCutoffRange: [0, 1],
40 | $metalnessRange: [0, 1],
41 | $roughnessRange: [0, 1],
42 | $glossinessRange: [0, 1],
43 | $parallaxOcclusionScaleRange: [0, 0.1],
44 |
45 | $textureTiling: 1
46 | };
47 | }
--------------------------------------------------------------------------------
/editor/src/getDefaultSceneConfig.js:
--------------------------------------------------------------------------------
1 | import env from './env';
2 |
3 | export default function () {
4 | return {
5 |
6 | preZ: true,
7 |
8 | materials: [],
9 |
10 | takes: [],
11 |
12 | textureFlipY: false,
13 |
14 | zUpToYUp: false,
15 |
16 | shadow: true,
17 |
18 | environment: 'auto',
19 |
20 | viewControl: {
21 | alpha: 20,
22 | beta: 30,
23 | distance: 18
24 | },
25 |
26 | ground: {
27 | show: true,
28 |
29 | grid: false
30 | },
31 |
32 | mainLight: {
33 | // If enable shadow of main light.
34 | shadow: true,
35 | // Quality of main light shadow. 'low'|'medium'|'high'|'ultra'
36 | shadowQuality: 'medium',
37 | // Intensity of main light
38 | intensity: 0.8,
39 | // Color of main light
40 | color: '#fff',
41 | // Alpha is rotation from bottom to up.
42 | alpha: 45,
43 | // Beta is rotation from left to right.
44 | beta: 45,
45 |
46 | $padAngle: [0.25, 0.5]
47 | },
48 | // Configuration of secondary light
49 | secondaryLight: {
50 | // If enable shadow of secondary light.
51 | shadow: true,
52 | shadowQuality: 'medium',
53 | // Intensity of secondary light. Defaultly not enable secondary light.
54 | intensity: 0,
55 | // Color of secondary light.
56 | color: '#fff',
57 | alpha: 60,
58 | beta: -50,
59 |
60 | $padAngle: [-50 / 180, 60 / 90]
61 | },
62 | // Configuration of tertiary light
63 | tertiaryLight: {
64 | // If enable shadow of tertiary light.
65 | shadow: true,
66 | shadowQuality: 'medium',
67 | // Intensity of secondary light. Defaultly not enable secondary light.
68 | intensity: 0,
69 | // Color of tertiary light.
70 | color: '#fff',
71 | alpha: 89,
72 | beta: 0,
73 |
74 | $padAngle: [0, 89 / 90]
75 | },
76 | // Configuration of constant ambient light.
77 | // Which will add a constant color on any surface.
78 | ambientLight: {
79 | // ambient light intensity.
80 | intensity: 0.0,
81 | // ambient light color.
82 | color: '#fff'
83 | },
84 | ambientCubemapLight: {
85 | // Environment panorama texture url for cubemap lighting
86 | texture: env.ENV_TEXTURE_ROOT + 'pisa.hdr',
87 |
88 | $texture: 'pisa',
89 | $textureOptions: ['pisa', 'Barce_Rooftop_C', 'Factory_Catwalk', 'Grand_Canyon_C', 'Ice_Lake', 'Hall', 'Old_Industrial_Hall'],
90 |
91 | // Exposure factor when parsing hdr format.
92 | exposure: 3,
93 | // Intensity of diffuse radiance.
94 | diffuseIntensity: 0.3,
95 | // Intensity of specular radiance.
96 | specularIntensity: 0.5
97 | },
98 | // Configuration about post effects.
99 | postEffect: {
100 | // If enable post effects.
101 | enable: true,
102 | // Configuration about bloom post effect
103 | bloom: {
104 | // If enable bloom
105 | enable: true,
106 | // Intensity of bloom
107 | intensity: 0.1
108 | },
109 | // Configuration about depth of field
110 | depthOfField: {
111 | enable: false,
112 | // Focal distance of camera in word space.
113 | focalDistance: 4,
114 | // Focal range of camera in word space. in this range image will be absolutely sharp.
115 | focalRange: 1,
116 | // Max out of focus blur radius.
117 | blurRadius: 5,
118 | // fstop of camera. Smaller fstop will have shallow depth of field
119 | fstop: 10,
120 | // Blur quality. 'low'|'medium'|'high'|'ultra'
121 | quality: 'medium',
122 |
123 | $qualityOptions: ['low', 'medium', 'high', 'ultra']
124 | },
125 | // Configuration about screen space ambient occulusion
126 | screenSpaceAmbientOcclusion: {
127 | // If enable SSAO
128 | enable: false,
129 | // Sampling radius in work space.
130 | // Larger will produce more soft concat shadow.
131 | // But also needs higher quality or it will have more obvious artifacts
132 | radius: 1.5,
133 | // Quality of SSAO. 'low'|'medium'|'high'|'ultra'
134 | quality: 'medium',
135 | // Intensity of SSAO
136 | intensity: 1,
137 |
138 | $qualityOptions: ['low', 'medium', 'high', 'ultra']
139 | },
140 | // Configuration about screen space reflection
141 | screenSpaceReflection: {
142 | enable: false,
143 | // If physically corrected.
144 | physical: false,
145 | // Quality of SSR. 'low'|'medium'|'high'|'ultra'
146 | quality: 'medium',
147 | // Surface with less roughness will have reflection.
148 | maxRoughness: 0.8,
149 |
150 | $qualityOptions: ['low', 'medium', 'high', 'ultra']
151 | },
152 | // Configuration about color correction
153 | colorCorrection: {
154 | // If enable color correction
155 | enable: true,
156 | exposure: 0,
157 | brightness: 0,
158 | contrast: 1,
159 | saturation: 1,
160 | // Lookup texture for color correction.
161 | // See https://ecomfe.github.io/echarts-doc/public/cn/option-gl.html#globe.postEffect.colorCorrection.lookupTexture
162 | lookupTexture: ''
163 | },
164 | FXAA: {
165 | // If enable FXAA
166 | enable: false
167 | }
168 | }
169 | };
170 | };
--------------------------------------------------------------------------------
/editor/src/timeline.js:
--------------------------------------------------------------------------------
1 |
2 | function showTimeline() {
3 | document.getElementById('timeline').style.display = 'block';
4 | }
5 | function hideTimeline() {
6 | document.getElementById('timeline').style.display = 'none';
7 | stopAnimation();
8 | }
9 |
10 | var isPlay = false;
11 | var currentTime = 0;
12 | var startTime = 0;
13 | var duration;
14 |
15 | var viewer;
16 | var animationToken;
17 | var pauseBtnClickListener;
18 |
19 |
20 | function updateAnimationUI(_viewer) {
21 | viewer = _viewer;
22 | duration = Math.floor(_viewer.getAnimationDuration());
23 | duration > 0 ? (showTimeline()) : (hideTimeline());
24 |
25 | if (duration <= 0) {
26 | return;
27 | }
28 | var pauseBtn = document.getElementById('timeline-pause-resume');
29 |
30 | pauseBtn.removeEventListener('click', pauseBtnClickListener);
31 | pauseBtn.addEventListener('click', pauseBtnClickListener = function () {
32 | if (isPlay) {
33 | stopAnimation();
34 | }
35 | else {
36 | startAnimation(_animationToken);
37 | }
38 | });
39 |
40 |
41 | // Reset time
42 | startTime = 0;
43 | currentTime = 0;
44 |
45 | updateControlPosition();
46 |
47 | var _animationToken = Math.random();
48 | animationToken = _animationToken;
49 |
50 | if (duration > 0) {
51 | startAnimation(_animationToken);
52 | }
53 | else {
54 | stopAnimation();
55 | }
56 | var _oldIsPlay = null;
57 | if (!$('#timeline-progress input').data('ionRangeSlider')) {
58 | $('#timeline-progress input').ionRangeSlider({
59 | from_shadow: true,
60 | force_edges: true,
61 | onChange: function (data) {
62 | currentTime = data.from;
63 | viewer.setPose(currentTime);
64 | if (_oldIsPlay == null) {
65 | _oldIsPlay = isPlay;
66 | }
67 | stopAnimation();
68 | },
69 | onFinish: function () {
70 | if (_oldIsPlay) {
71 | startAnimation(_animationToken);
72 | }
73 | _oldIsPlay = null;
74 | }
75 | });
76 | $('#timeline-range input').ionRangeSlider({
77 | from_shadow: true,
78 | force_edges: true,
79 | type: 'double',
80 | drag_interval: true,
81 | grid: true,
82 | grid_num: 10,
83 | onChange: function (data) {
84 | duration = data.to - data.from;
85 | startTime = data.from;
86 | currentTime = Math.min(Math.max(data.from, currentTime), data.to);
87 | viewer.setPose(currentTime);
88 | progressSlider.update({
89 | from_min: data.from,
90 | from_max: data.to
91 | });
92 | }
93 | });
94 |
95 | var progressSlider = $('#timeline-progress input').data('ionRangeSlider');
96 | var rangeSlider = $('#timeline-range input').data('ionRangeSlider');
97 | progressSlider.update({
98 | min: 0,
99 | max: duration,
100 | from: currentTime,
101 | from_min: 0,
102 | from_max: duration
103 | });
104 | rangeSlider.update({
105 | min: 0,
106 | max: duration,
107 | from: 0,
108 | to: duration
109 | });
110 | }
111 | }
112 |
113 | function updateControlPosition() {
114 | var slider = $('#timeline-progress input').data('ionRangeSlider');
115 | if (slider) {
116 | slider.update({
117 | from: currentTime
118 | });
119 | }
120 | }
121 |
122 | function startAnimation(_animationToken) {
123 | if (isPlay) {
124 | return;
125 | }
126 |
127 | isPlay = true;
128 |
129 | var _time = Date.now();
130 |
131 | var pauseBtn = document.getElementById('timeline-pause-resume');
132 | pauseBtn.classList.remove('icon-resume');
133 | pauseBtn.classList.add('icon-pause');
134 |
135 | function update() {
136 | if (!isPlay) {
137 | return;
138 | }
139 | if (_animationToken !== animationToken) {
140 | return;
141 | }
142 |
143 | viewer.setPose(currentTime);
144 |
145 | var dTime = Math.min(Date.now() - _time, 20);
146 | _time = Date.now();
147 |
148 | updateControlPosition();
149 |
150 | currentTime += dTime;
151 | if (currentTime > startTime + duration) {
152 | currentTime = startTime;
153 | }
154 |
155 | requestAnimationFrame(update);
156 | }
157 |
158 | requestAnimationFrame(update);
159 | }
160 |
161 | function stopAnimation() {
162 | if (!isPlay) {
163 | return;
164 | }
165 |
166 | isPlay = false;
167 |
168 | var pauseBtn = document.getElementById('timeline-pause-resume');
169 | pauseBtn.classList.remove('icon-pause');
170 | pauseBtn.classList.add('icon-resume');
171 | }
172 |
173 | function setTimeRange(_startTime, _endTime) {
174 | startTime = _startTime;
175 | duration = _endTime - startTime;
176 | }
177 |
178 | export { updateAnimationUI, setTimeRange, hideTimeline, showTimeline };
--------------------------------------------------------------------------------
/editor/src/ui/Texture.js:
--------------------------------------------------------------------------------
1 | function TextureUI(parent, object, key, params) {
2 | ControlKit.ObjectComponent.apply(this, arguments);
3 |
4 | this._object = object;
5 | this._key = key;
6 |
7 | this._onChange = params.onChange || function () {};
8 | this._getFileNameByURL = params.getFileName || function (url) { return url; };
9 |
10 | var self = this;
11 |
12 | var wrap = this._wrapNode;
13 | wrap.setStyleClass('texture-wrap');
14 |
15 | var img = document.createElement('img');
16 | var deleteEl = document.createElement('div');
17 | deleteEl.className = 'texture-delete button';
18 | var uploadEl = document.createElement('div');
19 | uploadEl.className = 'texture-upload button';
20 |
21 | this._img = img;
22 | this._uploadEl = uploadEl;
23 |
24 | wrap.getElement().appendChild(img);
25 | wrap.getElement().appendChild(uploadEl);
26 | wrap.getElement().appendChild(deleteEl);
27 |
28 | this.update();
29 |
30 | var liEl = this._wrapNode.getParent().getElement();
31 |
32 | function uploadFiles(files) {
33 | var imgFile = files.filter(function (file) {
34 | return file.type.match(/image/);
35 | })[0];
36 |
37 | if (imgFile) {
38 | object[key] = URL.createObjectURL(imgFile);
39 |
40 | self._onChange(imgFile, object[key]);
41 |
42 | self.update();
43 | }
44 | }
45 | FileAPI.event.dnd(liEl, function (over) {
46 | over ? liEl.classList.add('drag-hover')
47 | : liEl.classList.remove('drag-hover');
48 | }, function (files) {
49 | uploadFiles(files);
50 | });
51 |
52 | // Clear
53 | deleteEl.addEventListener('click', function () {
54 | object[key] = 'none';
55 | self.update();
56 | self._onChange(null, 'none');
57 | });
58 | uploadEl.addEventListener('click', function () {
59 | var el = document.createElement('input');
60 | el.type = 'file';
61 | el.onchange = function (e) {
62 | uploadFiles(Array.prototype.slice.call(el.files));
63 | };
64 | el.click();
65 | });
66 | }
67 |
68 | TextureUI.prototype = Object.create(ControlKit.ObjectComponent.prototype);
69 | TextureUI.prototype.constructor = TextureUI;
70 |
71 | TextureUI.prototype.update = function () {
72 | var value = this._object[this._key];
73 | this._img.src = value && value.toLowerCase() !== 'none' ? value : './img/chessboard.jpg';
74 | this._img.style.opacity = (value && value != 'none') ? 1 : 0.5;
75 |
76 | var text = this._getFileNameByURL(value) || value || 'none';;
77 | this._uploadEl.innerHTML = text;
78 | this._uploadEl.title = text;
79 | };
80 |
81 |
82 | export default TextureUI;
--------------------------------------------------------------------------------
/editor/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 |
4 | let target = (process.env.TARGET || 'browser').toLowerCase();
5 | let isElectron = target === 'electron';
6 |
7 | module.exports = {
8 | entry: __dirname + '/src/main.js',
9 | resolve: {
10 | alias: {
11 | vendor: path.resolve(__dirname, isElectron ? 'src/electron' : 'src/browser')
12 | }
13 | },
14 | plugins: [new webpack.DefinePlugin({
15 | 'process.env.TARGET': JSON.stringify(process.env.TARGET)
16 | })],
17 | output: {
18 | filename: isElectron ? 'electron/bundle.js' : 'bundle.js'
19 | },
20 | target: isElectron ? 'electron-renderer' : 'web'
21 | };
--------------------------------------------------------------------------------
/examples/asset/DamagedHelmet/DamagedHelmet.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/examples/asset/DamagedHelmet/DamagedHelmet.bin
--------------------------------------------------------------------------------
/examples/asset/DamagedHelmet/DamagedHelmet.gltf:
--------------------------------------------------------------------------------
1 | {
2 | "accessors" : [
3 | {
4 | "bufferView" : 0,
5 | "componentType" : 5123,
6 | "count" : 46356,
7 | "max" : [
8 | 14358
9 | ],
10 | "min" : [
11 | 0
12 | ],
13 | "type" : "SCALAR"
14 | },
15 | {
16 | "bufferView" : 1,
17 | "componentType" : 5126,
18 | "count" : 14359,
19 | "max" : [
20 | 0.9449769854545593,
21 | 1.0,
22 | 0.9009739756584167
23 | ],
24 | "min" : [
25 | -0.9449769854545593,
26 | -1.0,
27 | -0.9009950160980225
28 | ],
29 | "type" : "VEC3"
30 | },
31 | {
32 | "bufferView" : 2,
33 | "componentType" : 5126,
34 | "count" : 14359,
35 | "max" : [
36 | 1.0,
37 | 1.0,
38 | 1.0
39 | ],
40 | "min" : [
41 | -1.0,
42 | -1.0,
43 | -1.0
44 | ],
45 | "type" : "VEC3"
46 | },
47 | {
48 | "bufferView" : 3,
49 | "componentType" : 5126,
50 | "count" : 14359,
51 | "max" : [
52 | 0.9999759793281555,
53 | 1.998665988445282
54 | ],
55 | "min" : [
56 | 0.002448640065267682,
57 | 1.0005531199858524
58 | ],
59 | "type" : "VEC2"
60 | }
61 | ],
62 | "asset" : {
63 | "generator" : "Khronos Blender glTF 2.0 exporter",
64 | "version" : "2.0"
65 | },
66 | "bufferViews" : [
67 | {
68 | "buffer" : 0,
69 | "byteLength" : 92712,
70 | "byteOffset" : 0,
71 | "target" : 34963
72 | },
73 | {
74 | "buffer" : 0,
75 | "byteLength" : 172308,
76 | "byteOffset" : 92712,
77 | "target" : 34962
78 | },
79 | {
80 | "buffer" : 0,
81 | "byteLength" : 172308,
82 | "byteOffset" : 265020,
83 | "target" : 34962
84 | },
85 | {
86 | "buffer" : 0,
87 | "byteLength" : 114872,
88 | "byteOffset" : 437328,
89 | "target" : 34962
90 | }
91 | ],
92 | "buffers" : [
93 | {
94 | "byteLength" : 552200,
95 | "uri" : "DamagedHelmet.bin"
96 | }
97 | ],
98 | "images" : [
99 | {
100 | "uri" : "Default_albedo.jpg"
101 | },
102 | {
103 | "uri" : "Default_normal.jpg"
104 | },
105 | {
106 | "uri" : "Default_metalRoughness.jpg"
107 | },
108 | {
109 | "uri" : "Default_emissive.jpg"
110 | },
111 | {
112 | "uri" : "Default_AO.jpg"
113 | }
114 | ],
115 | "materials" : [
116 | {
117 | "emissiveFactor" : [
118 | 1.0,
119 | 1.0,
120 | 1.0
121 | ],
122 | "emissiveTexture" : {
123 | "index" : 3
124 | },
125 | "name" : "glTF Material",
126 | "normalTexture" : {
127 | "index" : 1
128 | },
129 | "occlusionTexture" : {
130 | "index" : 4
131 | },
132 | "pbrMetallicRoughness" : {
133 | "baseColorTexture" : {
134 | "index" : 0
135 | },
136 | "metallicRoughnessTexture" : {
137 | "index" : 2
138 | }
139 | }
140 | }
141 | ],
142 | "meshes" : [
143 | {
144 | "name" : "mesh_helmet_LP_13930damagedHelmet",
145 | "primitives" : [
146 | {
147 | "attributes" : {
148 | "NORMAL" : 2,
149 | "POSITION" : 1,
150 | "TEXCOORD_0" : 3
151 | },
152 | "indices" : 0,
153 | "material" : 0
154 | }
155 | ]
156 | }
157 | ],
158 | "nodes" : [
159 | {
160 | "mesh" : 0,
161 | "name" : "node_damagedHelmet_-6514",
162 | "rotation" : [
163 | 0.7071068286895752,
164 | 0.0,
165 | -0.0,
166 | 0.7071068286895752
167 | ]
168 | }
169 | ],
170 | "samplers" : [
171 | {}
172 | ],
173 | "scene" : 0,
174 | "scenes" : [
175 | {
176 | "name" : "Scene",
177 | "nodes" : [
178 | 0
179 | ]
180 | }
181 | ],
182 | "textures" : [
183 | {
184 | "sampler" : 0,
185 | "source" : 0
186 | },
187 | {
188 | "sampler" : 0,
189 | "source" : 1
190 | },
191 | {
192 | "sampler" : 0,
193 | "source" : 2
194 | },
195 | {
196 | "sampler" : 0,
197 | "source" : 3
198 | },
199 | {
200 | "sampler" : 0,
201 | "source" : 4
202 | }
203 | ]
204 | }
205 |
--------------------------------------------------------------------------------
/examples/asset/DamagedHelmet/Default_AO.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/examples/asset/DamagedHelmet/Default_AO.jpg
--------------------------------------------------------------------------------
/examples/asset/DamagedHelmet/Default_albedo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/examples/asset/DamagedHelmet/Default_albedo.jpg
--------------------------------------------------------------------------------
/examples/asset/DamagedHelmet/Default_emissive.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/examples/asset/DamagedHelmet/Default_emissive.jpg
--------------------------------------------------------------------------------
/examples/asset/DamagedHelmet/Default_metalRoughness.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/examples/asset/DamagedHelmet/Default_metalRoughness.jpg
--------------------------------------------------------------------------------
/examples/asset/DamagedHelmet/Default_normal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/examples/asset/DamagedHelmet/Default_normal.jpg
--------------------------------------------------------------------------------
/examples/asset/texture/pisa.hdr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/examples/asset/texture/pisa.hdr
--------------------------------------------------------------------------------
/examples/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/examples/bg.jpg
--------------------------------------------------------------------------------
/examples/view.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
36 |
37 |
38 |
111 |
112 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import Viewer from './src/Viewer';
2 | export default Viewer;
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6"
4 | },
5 | "exclude": [
6 | "node_modules",
7 | "**/node_modules/*"
8 | ]
9 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "clay-viewer",
3 | "version": "0.2.1",
4 | "description": "Clay Viewer is built on top of WebGL, Supported FBX, DAE, OBJ, glTF2.0 model formats.",
5 | "main": "dist/clay-viewer.js",
6 | "dependencies": {
7 | "claygl": "^1.1.0",
8 | "zrender": "^4.0.0"
9 | },
10 | "devDependencies": {
11 | "case-sensitive-paths-webpack-plugin": "^2.0.0",
12 | "cross-env": "^5.1.1",
13 | "filer.js": "^0.4.5",
14 | "glob": "^7.1.2",
15 | "mime-types": "^2.1.17",
16 | "rollup-plugin-commonjs": "^8.2.1",
17 | "rollup-plugin-node-resolve": "^3.0.0",
18 | "rollup-watch": "^4.3.1",
19 | "san": "^3.2.9",
20 | "shelljs": "^0.7.8",
21 | "webpack": "^3.6.0"
22 | },
23 | "scripts": {
24 | "dev": "rollup -c -w",
25 | "glsl2js": "node build/glsl2js.js",
26 | "build": "npm run glsl2js && rollup -c && uglifyjs -c -m -- dist/clay-viewer.js > dist/clay-viewer.min.js",
27 | "dev:editor": "cd ./editor && npm run dev",
28 | "build:editor": "cd ./editor && npm run build",
29 | "build:electron": "cd ./editor && npm run build:electron",
30 | "build:fbx2gltf": "cd ./editor && npm run build:fbx2gltf",
31 | "dist:electron": "cd ./editor && npm run dist:electron",
32 | "cdist:electron": "cd ./editor && npm run cdist:electron"
33 | },
34 | "author": "Yi Shen"
35 | }
36 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import nodeResolve from 'rollup-plugin-node-resolve';
2 | import commonjs from 'rollup-plugin-commonjs';
3 |
4 | export default {
5 | input: __dirname + '/index.js',
6 | plugins: [
7 | nodeResolve(),
8 | commonjs()
9 | ],
10 | // sourceMap: true,
11 | output: [
12 | {
13 | format: 'umd',
14 | name: 'ClayViewer',
15 | file: 'dist/clay-viewer.js'
16 | }
17 | ]
18 | };
--------------------------------------------------------------------------------
/screenshots/editor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/screenshots/editor.jpg
--------------------------------------------------------------------------------
/screenshots/editor2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pissang/clay-viewer/86456edc4b28494e5facbe75fc4f009fe8cf3f3e/screenshots/editor2.jpg
--------------------------------------------------------------------------------
/src/GestureMgr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Only implements needed gestures for mobile.
3 | */
4 | var GestureMgr = function () {
5 |
6 | /**
7 | * @private
8 | * @type {Array.